Skip to content

Add Rust-to-Dart logging bridge#3079

Open
fzyzcjy wants to merge 23 commits into
masterfrom
codex/logging-overhaul-redesign
Open

Add Rust-to-Dart logging bridge#3079
fzyzcjy wants to merge 23 commits into
masterfrom
codex/logging-overhaul-redesign

Conversation

@fzyzcjy
Copy link
Copy Markdown
Owner

@fzyzcjy fzyzcjy commented May 10, 2026

continue from @patmuk #2766

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a logging bridge that routes Rust log records to the Dart logging package, including a new enable_frb_logging! macro and a Dart-side FrbDartLogging utility. Feedback focuses on several critical areas: the generated Dart initialization code needs to await asynchronous calls to prevent type errors, and the Rust logger initialization must handle Flutter Hot Restarts gracefully to avoid panics. Additionally, improvements were suggested for adding error handling to the Dart stream subscription and adopting more idiomatic log level mappings between the two languages.

Comment on lines +265 to +270
let max_level = max_level_func
.map(|f| format!("api.{}()", f.name_dart_wire()))
.unwrap_or_else(|| "3".to_owned());
let setup_default_output = setup_default_output_func
.map(|f| format!("api.{}()", f.name_dart_wire()))
.unwrap_or_else(|| "true".to_owned());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The calls to api.frbLoggingMaxLevel() and api.frbLoggingSetupDartLoggingOutput() are likely asynchronous in the generated Dart code (as they are not explicitly marked with #[frb(sync)] in the Rust macro). Since these are used as arguments to api.frbInitLogger, they must be awaited to avoid passing a Future where a value is expected.

Note: await is safe to use here even if the functions were sync, as Dart allows awaiting non-future values.

Suggested change
let max_level = max_level_func
.map(|f| format!("api.{}()", f.name_dart_wire()))
.unwrap_or_else(|| "3".to_owned());
let setup_default_output = setup_default_output_func
.map(|f| format!("api.{}()", f.name_dart_wire()))
.unwrap_or_else(|| "true".to_owned());
let max_level = max_level_func
.map(|f| format!("await api.{}()", f.name_dart_wire()))
.unwrap_or_else(|| "3".to_owned());
let setup_default_output = setup_default_output_func
.map(|f| format!("await api.{}()", f.name_dart_wire()))
.unwrap_or_else(|| "true".to_owned());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't recall this - but can look into the code if needed to remember. I think making it #[frb(sync)] would be better - but if adding this attribute if hard, await works as well.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes probably the agent does sth wrong (the code is still in a somehow early stage), and we should fix it

Comment thread frb_rust/src/misc/frb_logging.rs Outdated
Comment on lines +77 to +79
log::set_boxed_logger(Box::new(FrbDartLogger { sink }))
.map(|()| log::set_max_level(max_level))
.expect("FRB logging should only be initialized once");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using .expect() on log::set_boxed_logger will cause the application to crash if frb_init_logger is called more than once. This is a critical issue for Flutter's Hot Restart, where the Dart state is reset but the Rust process (and its global logger) persists. Subsequent calls to init will trigger this panic.

Consider handling the error gracefully. Ideally, the logger should be initialized only once (e.g., using std::sync::Once), and the StreamSink should be stored in a static RwLock so it can be updated on each call to frb_init_logger to support re-connection after a hot restart.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this on purpose: My thinking is that nobody would call frb_init_logger a second time intentionally.
Thus, if done accidentally, it should crash rather than silently swallow log messages.

If there is a reason for a second intentional init, the suggested changes sounds fine.

throw StateError('FRB logging should not be initialized twice');
}

_subscription = rustLogStream.listen((record) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The stream subscription lacks error handling. If the Rust-side stream encounters an error, it could lead to unhandled exceptions in the Dart zone. Consider adding an onError handler to the listen call to log or handle bridge-level errors.

Comment on lines +74 to +77
case 'TRACE':
return Level.FINE;
case 'DEBUG':
return Level.CONFIG;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The mapping of TRACE to Level.FINE and DEBUG to Level.CONFIG is non-standard for the Dart logging package. Level.CONFIG is typically intended for static configuration messages, not general debug logs. A more idiomatic mapping would be DEBUG -> Level.FINE and TRACE -> Level.FINER or Level.FINEST.

Suggested change
case 'TRACE':
return Level.FINE;
case 'DEBUG':
return Level.CONFIG;
case 'TRACE':
return Level.FINER;
case 'DEBUG':
return Level.FINE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggestion sounds good. I mapped it this way, as Level.CONFIG is between INFO and FINE. But semantics wise gemini could be right.
AFAIRecall, this is just the default mapping a user can override. So not as critical, but the defaults should feel right.

@fzyzcjy
Copy link
Copy Markdown
Owner Author

fzyzcjy commented May 10, 2026

docs LGTM

Copy link
Copy Markdown
Owner Author

@fzyzcjy fzyzcjy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pr LGTM

@codecov
Copy link
Copy Markdown

codecov Bot commented May 10, 2026

Codecov Report

❌ Patch coverage is 81.19658% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.24%. Comparing base (fcc26ee) to head (f9b512a).
⚠️ Report is 20 commits behind head on master.

Files with missing lines Patch % Lines
frb_dart/lib/src/logging/frb_logging.dart 65.38% 9 Missing ⚠️
frb_rust/src/misc/frb_logging.rs 62.50% 9 Missing ⚠️
frb_dart/test/frb_logging_test.dart 94.02% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           master    #3079       +/-   ##
===========================================
+ Coverage   33.47%   98.24%   +64.76%     
===========================================
  Files          51      473      +422     
  Lines        1198    19546    +18348     
===========================================
+ Hits          401    19202    +18801     
+ Misses        797      344      -453     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@patmuk
Copy link
Copy Markdown
Contributor

patmuk commented May 11, 2026

@fzyzcjy that worked great :) I left some comments. Tell me if I can help further!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants