Integrate rust logging#423
Conversation
ab97f6e to
18b85d2
Compare
There was a problem hiding this comment.
Pull request overview
This PR integrates Rust tracing-based logging into the Node.js driver by forwarding Rust log events across the N-API boundary and exposing a types.logLevels string enum to configure filtering from JavaScript/TypeScript.
Changes:
- Added a Rust global
tracingsubscriber layer that forwards events to per-client registered JS callbacks with level filtering. - Introduced
logLevelclient option andtypes.logLevelsenum (JS + TS typings), plus tests covering API surface and behavior. - Added end-user documentation and migration guide updates for the new logging system.
Reviewed changes
Copilot reviewed 17 out of 19 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
src/logging.rs |
New Rust forwarding layer + per-client callback registry + level filtering. |
lib/client.js |
Registers/unregisters Rust log callback based on options.logLevel. |
lib/client-options.js |
Adds logLevel option default + validation and JSDoc. |
lib/types/index.js |
Adds types.logLevels enum values. |
lib/types/index.d.ts |
Adds logLevels TypeScript enum. |
main.d.ts |
Adds logLevel?: types.logLevels to ClientOptions. |
src/tests/logging_tests.rs / src/tests/mod.rs |
Adds Rust test helpers to emit tracing events for JS tests. |
test/unit/logging-tests.js |
Unit tests for Rust->JS forwarding, multi-callback, and filtering. |
test/integration/supported/client-logging-tests.js |
Integration coverage for real Rust driver log events emitted via Client. |
test/unit-not-supported/api-tests.js |
Ensures types.logLevels exists in the public API surface. |
test/typescript/* |
Type-level coverage for types.logLevels and Client({ logLevel }). |
docs/src/logging.md / docs/src/migration_guide.md / docs/src/SUMMARY.md |
Documentation for logging and migration notes. |
Cargo.toml |
Adds tracing and tracing-subscriber dependencies. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
18b85d2 to
1589d55
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 20 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
I have doubts. You say that you change the default logging level from (?) to "off". Why? |
You still need to add some logic to be able to see any logs (
Considering the above, I assumed that users when creating the logic will also set the desired log level. |
Yep. Let's default to WARN. |
| /// Returns `None` for `"off"` or unrecognized strings. | ||
| fn parse_level(level: &str) -> Option<Level> { | ||
| match level { | ||
| "trace" => Some(Level::TRACE), | ||
| "debug" => Some(Level::DEBUG), | ||
| "info" => Some(Level::INFO), | ||
| "warning" => Some(Level::WARN), | ||
| "error" => Some(Level::ERROR), | ||
| _ => None, | ||
| } |
There was a problem hiding this comment.
🤔 Shouldn't this treat unknown strings as Result::Err, while off as Option::None?
There was a problem hiding this comment.
Off is filtered at the JS code.
Invalid strings are also filtered
https://github.com/scylladb/nodejs-rs-driver/blob/7eb8b18f9f75800449889af441dd213c2d4f2448/lib/client-options.js#L737-L744
So this is a quiet ignoring of path that should not be taken
wprzytula
left a comment
There was a problem hiding this comment.
For now, reviewed up till the second commit, inclusively.
|
In-person review:
|
1589d55 to
64017f8
Compare
|
Rebase (no changes) |
dc99462 to
50e2b46
Compare
|
Rebase (again) |
Looking a bit more at it, I'm not sure it's a good idea: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry#notes_on_cleanup_callbacks |
0060f73 to
0d05018
Compare
|
Partial changes
Done |
aa81fa5 to
463df12
Compare
Done, with a new test |
463df12 to
05934f7
Compare
|
The only remaining thing now should be logging format (which we discussed during in person review).
|
dc55048 to
ad14fec
Compare
|
Since I'm not exactly sure about the "Rework logging" commit, I didn't merge it to a proper place yet - will do it once those changes are be accepted |
wprzytula
left a comment
There was a problem hiding this comment.
The commit structure and descriptions are bad. Please order the commits more neatly, describe them better, and make sure commits do not introduce bad code that you fix in a later commit.
| useBigIntAsLong: true, | ||
| useBigIntAsVarint: true, | ||
| }, | ||
| logLevel: undefined, |
There was a problem hiding this comment.
🤔 This changes nothing, right? The key set to undefined is equivalent to no such key set, right?
| /** | ||
| * Validates the logLevel option. | ||
| * @param {string} logLevel | ||
| * @private | ||
| */ | ||
| function validateLogLevel(logLevel) { | ||
| if ( | ||
| logLevel !== undefined && | ||
| (typeof logLevel !== "string" || !validLogLevels.includes(logLevel)) | ||
| ) { | ||
| throw new TypeError( | ||
| `logLevel must be one of ${validLogLevels.map((l) => `'${l}'`).join(", ")}`, | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
💭 This seems buggy.
- What if a non-string junk object is passed?
- What if a value of the log level enum is passed?
| // Collects all fields and values in a single log event into a single String, similarly to C++-rs driver | ||
| impl Visit for MessageVisitor { | ||
| fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { | ||
| if field.name() == "message" { | ||
| let _ = write!(&mut self.message, "{:?}", value); | ||
| if self.log_message.is_empty() { | ||
| write!(self.log_message, "{field}: {value:?}").unwrap(); | ||
| } else { | ||
| if !self.extras.is_empty() { | ||
| self.extras.push_str(", "); | ||
| } | ||
|
|
||
| let _ = write!(&mut self.extras, "{}={:?}", field.name(), value); | ||
| } | ||
| } | ||
|
|
||
| fn record_str(&mut self, field: &Field, value: &str) { |
There was a problem hiding this comment.
🔧 Explain these changes in the commit message.
This commit introduces rust logging cleanup on client finalization. Since we cannot deterministically predict when the client will be finalized, this is done as a back-up to manual logging cleanup on client shutdown. This new cleanup will trigger only when the client didn't shut down yet.
Rust unit tests: - MessageVisitor field formatting - rust_level_to_js and parse_js_level_to_rust mapping correctness JS unit tests: - FFI forwarding delivers events at all 5 tracing levels - Message content survives FFI boundary intact - Target field contains Rust module path - Multiple registered callbacks each receive events (shared subscriber) - removeLogging stops delivery without affecting other callbacks - Level filtering only passes events at or above registered level - Visitor field formatting end-to-end (comma separation, debug quotes, int/bool repr) - Level-to-message pairing is correct across FFI - Default logLevel registers Rust callback and receives events - logLevel 'off' suppresses all events Integration tests (against real cluster): - Real Rust driver produces log events during connection - JS and Rust log events coexist during connection - cleanup both for shutdown and gc cleanup
At some point the code was changed, in a way that fixes scylladb#44. This commit fixes scylladb#44 by removing that redundant flag
This PR was created with Claude Opus and reviewed by me.
This PR introduces new API for logging. As discussed in person, we cannot keep the original interface, which we change with this PR.
Add configurable log forwarding from Rust driver to JS
Introduces a logging system that forwards internal driver log events to the
JS
Clientas'log'EventEmitter events, with configurable severityfiltering.
Highlights
logLevelclient option: Controls the minimum severity of forwardedevents. Set to one of
trace,debug,info,warning,error, oroff(default). Filtering happens on the native side before crossing theFFI boundary.
types.logLevelsenum: Provides type-safe access to log level valuesin both JS and TypeScript.
Clientregisters its own callback onconnect()and unregisters onshutdown(). Multiple clients can coexistwith independent log levels.
'log'event delivers(level, target, message, furtherInfo)as separate arguments, matchingthe
cassandra-driverevent signature.tracingsubscriber in Rust maintainsa registry of per-client NAPI callbacks.
setupLogging()returns anOption<u32>id (ornullforoff/invalid), used byremoveLogging()to unregister.
Documentation
arguments, event sources, multi-client behavior, and usage examples.
documenting differences from
cassandra-driver: logging off by default,verbosereplaced bytrace+debug,targetreplacesclassName,cross-client event visibility.
Tests
setupLogging/removeLogging: level filtering, all fivelevels delivered,
furtherInfowith extra fields, multiple callbacks,removal stops delivery.
connect(), query execution, andDDL operations.
logLevelsenum values in JS and TypeScript.Fixes: #16