Skip to content

Refactor(logger): Introduce handler pipeline (ADR-0078) #4466

Merged
hoangdat merged 26 commits into
masterfrom
feat/adr-0078-logger-pipeline-skeleton
Apr 28, 2026
Merged

Refactor(logger): Introduce handler pipeline (ADR-0078) #4466
hoangdat merged 26 commits into
masterfrom
feat/adr-0078-logger-pipeline-skeleton

Conversation

@dab246
Copy link
Copy Markdown
Member

@dab246 dab246 commented Apr 22, 2026

Context

Implements Phase 1–3 of ADR-0078.

app_logger.dart previously handled formatting, console output, Sentry routing, and exception capture in a single _internalLog function — untestable and non-extensible. This PR replaces it with an explicit handler pipeline where each log destination is an isolated, injectable unit.

What changed

  • LogRecord / LogHandler / AppLoggerRegistry — core pipeline abstractions
  • ConsoleLogHandler — console output with injected platform formatter (mobile: emoji, web: ANSI)
  • SentryEventHandlererror/criticalcaptureException or captureMessage
  • SentryBreadcrumbHandlertraceaddBreadcrumb (zero quota, attached to next error event)
  • SentryReporter — interface extracted from SentryManager for handler testability
  • app_logger.dart public API — unchanged, all 188+ call sites unaffected
  • registerLogHandlers() — single entry point for handler registration at startup

Behavior changes

Before After
logTrace captureMessage — consumes Sentry quota addBreadcrumb — zero quota
logError without exception Sentry exception event with raw string Sentry message event

Summary by CodeRabbit

  • New Features

    • Replaced in-file logger with a modular logging system: centralized dispatch, pluggable formatters (web colorized, mobile emoji) and handlers.
    • Sentry integration extended to record breadcrumbs and route errors/events via a reporting interface.
    • App startup now registers logging and Sentry handlers automatically.
  • Tests

    • Added unit tests covering the registry, record construction, formatters, and Sentry handlers.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR replaces the previous in-file logging logic with a handler-based logging pipeline. It adds LogRecord, Level enum, LogHandler interface, AppLoggerRegistry singleton dispatcher, LogFormatter interface with Web/Mobile implementations, Console/Sentry handlers, and a buildLogRecord helper. app_logger.dart now forwards public log functions to AppLoggerRegistry. Sentry integration gains a SentryReporter interface and SentryManager implements it with breadcrumb support. Runners register platform-appropriate formatters and handlers. Tests cover registry, formatters, and Sentry handlers.

Possibly related PRs

Suggested reviewers

  • hoangdat
  • tddang-linagora
  • codescene-delta-analysis
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main refactoring objective—introducing a handler pipeline architecture—and references the specific ADR (Architecture Decision Record) being implemented, which accurately reflects the extensive changes across logging abstractions and handler implementations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/adr-0078-logger-pipeline-skeleton

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
core/lib/utils/sentry/sentry_manager.dart (1)

108-117: ⚠️ Potential issue | 🟠 Major

Error/critical logs without an exception are silently reported to Sentry as info.

SentryReporter.captureMessage (core/lib/utils/sentry/sentry_reporter.dart:13-16) does not expose a level, and SentryEventHandler.handle (core/lib/utils/logging/handlers/sentry_event_handler.dart:33-37) invokes it through that interface without passing one. Because the override here defaults level to SentryLevel.info, every logError / logCritical that lacks an exception becomes an info event on Sentry — losing severity, alerting, and issue-grouping fidelity. This is a behavior regression vs. the previous monolithic logger and contradicts the intent stated in the PR description.

Either add level to the SentryReporter contract and map Level.error/Level.critical in SentryEventHandler, or have the handler call a dedicated reporter method that encodes the severity.

🔧 Proposed direction

In sentry_reporter.dart:

   void captureMessage(
     String message, {
+    SentryLevel level = SentryLevel.info,
     Map<String, dynamic>? extras,
   });

In sentry_event_handler.dart:

     } else {
       _sentry.captureMessage(
         record.rawMessage,
+        level: record.level == Level.critical
+            ? SentryLevel.fatal
+            : SentryLevel.error,
         extras: record.extras,
       );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/lib/utils/sentry/sentry_manager.dart` around lines 108 - 117, The
override captureMessage in sentry_manager.dart accepts a SentryLevel but
SentryReporter.captureMessage (and SentryEventHandler.handle) do not pass a
level, causing messages without exceptions to be sent as SentryLevel.info; fix
by adding an optional SentryLevel parameter to the SentryReporter.captureMessage
contract (and its implementations) and update SentryEventHandler.handle to map
the logging Level to the correct SentryLevel (e.g., Level.error ->
SentryLevel.error, Level.critical -> SentryLevel.fatal/critical) and pass that
into SentryReporter.captureMessage; keep the default fallback to
SentryLevel.info where callers don't supply one and update any call sites to
match the new signature.
core/lib/utils/app_logger.dart (1)

11-76: ⚠️ Potential issue | 🟡 Minor

webConsoleEnabled parameter is silently ignored at call sites — confirmed semantic regression.

The parameter is accepted on every public function but never forwarded to _dispatch, meaning it no longer reaches ConsoleLogHandler. This is a breaking change: at least three logError calls in authorization_interceptors.dart (lines 176, 259, 290) and one logInfo call in sentry_config.dart (line 87) explicitly pass webConsoleEnabled: true expecting those logs to appear in the production web console. With the new design, these calls silently have no effect.

Choose one approach:

  1. Forward webConsoleEnabled through _dispatch into LogRecord and have ConsoleLogHandler OR it with the global setting, restoring per-call override capability.
  2. Remove the parameter entirely and add @Deprecated to the old public functions, with a migration guide for callers to use global registerLogHandlers configuration only.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/lib/utils/app_logger.dart` around lines 11 - 76, The public log helpers
(logError, logCritical, logWarning, logInfo, logDebug, logTrace, log) currently
accept webConsoleEnabled but never forward it to _dispatch so ConsoleLogHandler
never sees per-call overrides; fix by threading webConsoleEnabled through: add a
webConsoleEnabled parameter to _dispatch and to the LogRecord payload it
creates, update all public helpers (logError, logCritical, logWarning, logInfo,
logDebug, logTrace, log) to pass their webConsoleEnabled argument into
_dispatch, and update ConsoleLogHandler to OR the incoming
LogRecord.webConsoleEnabled with the global web console setting when deciding
whether to write to the web console. Ensure signatures reference _dispatch,
LogRecord, and ConsoleLogHandler are consistently updated and adjust any
callers/tests accordingly.
🧹 Nitpick comments (1)
core/lib/utils/logging/log_record.dart (1)

9-17: Consider defensive copying for extras if API constraints allow.

The concern about mutable map contents is valid—final only prevents reassignment, not mutation of the map itself. However, the const constructor is a hard requirement based on active usage in tests. Removing const to implement Map.unmodifiable() would be a breaking change. The actual mutation risk appears low since extras is only passed through handlers without modification, but if the public const constructor can be removed in a future breaking release, defensive copying would improve true immutability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/lib/utils/logging/log_record.dart` around lines 9 - 17, Keep the
existing const LogRecord constructor unchanged for backwards compatibility but
add a new non-const factory (e.g., LogRecord.withDefensiveExtras or
LogRecord.defensive) that defensively wraps any provided extras with
Map.unmodifiable(...) before constructing the LogRecord; implement the factory
to delegate to a private (non-const) constructor or to the existing constructor
after wrapping, and document that callers who want true immutability should use
the new factory while tests and existing call sites can continue to use the
const constructor.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/lib/utils/logging/handlers/sentry_event_handler.dart`:
- Around line 21-38: Update the Sentry reporting API and handler to propagate
severity: modify the SentryReporter interface so both captureMessage(...) and
captureException(...) accept an additional SentryLevel parameter, update
SentryManager.captureException(...) (and captureMessage(...) if needed) to
accept and forward that level to Sentry, then change
SentryEventHandler.handle(LogRecord) to map Level.critical → SentryLevel.fatal
and Level.error → SentryLevel.error and pass the mapped SentryLevel into the
SentryManager calls (both exception and non-exception branches). Finally, update
the tests in sentry_event_handler_test.dart and
sentry_breadcrumb_handler_test.dart to call the new interface signatures and
assert the forwarded SentryLevel.

In `@core/test/utils/logging/app_logger_registry_test.dart`:
- Around line 45-87: The tests mistakenly only register one handler instance
where they intend to exercise type-based deduplication and multi-handler
dispatch; declare a second named fake subclass (e.g. _SecondFakeHandler or
_FakeHandlerAlt) at file scope and use it in the two failing tests: in the
"registers distinct handler types separately" test register both
registry.registerHandler(_FakeHandler(...)) and
registry.registerHandler(_SecondFakeHandler(...)) and assert
registry.handlerCount == 2, and in the "dispatches to multiple handlers when
both accept the level" test register two different handler types (h1 =
_FakeHandler(), h2 = _SecondFakeHandler()), call registry.dispatch(record) and
assert both h1.received and h2.received have length 1 (and their rawMessage
matches), leaving existing idempotency test unchanged.

In `@core/test/utils/logging/handlers/sentry_breadcrumb_handler_test.dart`:
- Around line 7-19: The fake reporter no-ops for captureException/captureMessage
so "does not call" tests aren't observable; update _FakeSentryReporter to either
record calls or fail fast: implement captureException and captureMessage to
append to new lists (e.g., capturedExceptions, capturedMessages) or throw an
AssertionError when invoked, and keep addBreadcrumb behavior; reference the
_FakeSentryReporter class and the methods captureException, captureMessage,
addBreadcrumb and the existing capturedBreadcrumbs to locate where to add
capturedExceptions/capturedMessages (or the throw) so tests can reliably detect
unwanted calls.

---

Outside diff comments:
In `@core/lib/utils/app_logger.dart`:
- Around line 11-76: The public log helpers (logError, logCritical, logWarning,
logInfo, logDebug, logTrace, log) currently accept webConsoleEnabled but never
forward it to _dispatch so ConsoleLogHandler never sees per-call overrides; fix
by threading webConsoleEnabled through: add a webConsoleEnabled parameter to
_dispatch and to the LogRecord payload it creates, update all public helpers
(logError, logCritical, logWarning, logInfo, logDebug, logTrace, log) to pass
their webConsoleEnabled argument into _dispatch, and update ConsoleLogHandler to
OR the incoming LogRecord.webConsoleEnabled with the global web console setting
when deciding whether to write to the web console. Ensure signatures reference
_dispatch, LogRecord, and ConsoleLogHandler are consistently updated and adjust
any callers/tests accordingly.

In `@core/lib/utils/sentry/sentry_manager.dart`:
- Around line 108-117: The override captureMessage in sentry_manager.dart
accepts a SentryLevel but SentryReporter.captureMessage (and
SentryEventHandler.handle) do not pass a level, causing messages without
exceptions to be sent as SentryLevel.info; fix by adding an optional SentryLevel
parameter to the SentryReporter.captureMessage contract (and its
implementations) and update SentryEventHandler.handle to map the logging Level
to the correct SentryLevel (e.g., Level.error -> SentryLevel.error,
Level.critical -> SentryLevel.fatal/critical) and pass that into
SentryReporter.captureMessage; keep the default fallback to SentryLevel.info
where callers don't supply one and update any call sites to match the new
signature.

---

Nitpick comments:
In `@core/lib/utils/logging/log_record.dart`:
- Around line 9-17: Keep the existing const LogRecord constructor unchanged for
backwards compatibility but add a new non-const factory (e.g.,
LogRecord.withDefensiveExtras or LogRecord.defensive) that defensively wraps any
provided extras with Map.unmodifiable(...) before constructing the LogRecord;
implement the factory to delegate to a private (non-const) constructor or to the
existing constructor after wrapping, and document that callers who want true
immutability should use the new factory while tests and existing call sites can
continue to use the const constructor.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1fb5bbc7-d7c2-4300-93ed-370da8aa367e

📥 Commits

Reviewing files that changed from the base of the PR and between 6ec654e and 4eb8884.

📒 Files selected for processing (21)
  • core/lib/utils/app_logger.dart
  • core/lib/utils/logging/app_logger_registry.dart
  • core/lib/utils/logging/formatters/log_formatter.dart
  • core/lib/utils/logging/formatters/mobile_console_formatter.dart
  • core/lib/utils/logging/formatters/web_console_formatter.dart
  • core/lib/utils/logging/handlers/console_log_handler.dart
  • core/lib/utils/logging/handlers/sentry_breadcrumb_handler.dart
  • core/lib/utils/logging/handlers/sentry_event_handler.dart
  • core/lib/utils/logging/log_handler.dart
  • core/lib/utils/logging/log_level.dart
  • core/lib/utils/logging/log_record.dart
  • core/lib/utils/sentry/sentry_manager.dart
  • core/lib/utils/sentry/sentry_reporter.dart
  • core/test/utils/logging/app_logger_registry_test.dart
  • core/test/utils/logging/formatters/mobile_console_formatter_test.dart
  • core/test/utils/logging/formatters/web_console_formatter_test.dart
  • core/test/utils/logging/handlers/sentry_breadcrumb_handler_test.dart
  • core/test/utils/logging/handlers/sentry_event_handler_test.dart
  • lib/main/runner/app_runner_base.dart
  • lib/main/runner/app_runner_mobile.dart
  • lib/main/runner/app_runner_web.dart

Comment thread core/lib/utils/logging/handlers/sentry_event_handler.dart Outdated
Comment thread core/test/utils/logging/app_logger_registry_test.dart
@github-actions
Copy link
Copy Markdown

This PR has been deployed to https://linagora.github.io/tmail-flutter/4466.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
core/lib/utils/sentry/sentry_manager.dart (2)

143-164: Consider propagating log level/category on the breadcrumb.

Breadcrumb is created with only message and data, which means every breadcrumb produced via the handler pipeline will land in Sentry with the default level (info) and no category. Since SentryBreadcrumbHandler is the trace-level sink per ADR-0078, surfacing that as level: SentryLevel.debug (and optionally a category like 'log' or the record's logger name) would make the breadcrumb trail on error events meaningfully more useful without changing quota characteristics. Happy to defer if you prefer to keep Phase 1–3 minimal.

♻️ Suggested change (requires plumbing level/category through the method signature)
-  void addBreadcrumb(
-    String message, {
-    Map<String, dynamic>? extras,
-  }) {
+  void addBreadcrumb(
+    String message, {
+    Map<String, dynamic>? extras,
+    SentryLevel level = SentryLevel.debug,
+    String? category,
+  }) {
     if (!_isSentryAvailable) return;
 
     try {
       Sentry.addBreadcrumb(
         Breadcrumb(
           message: message,
           data: extras,
+          level: level,
+          category: category,
         ),
       );
     } catch (e) {
       logWarning('[SentryManager] Add breadcrumb failed: $e');
     }
   }

Note: this also requires updating the SentryReporter interface and SentryBreadcrumbHandler callsite.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/lib/utils/sentry/sentry_manager.dart` around lines 143 - 164, The
addBreadcrumb method in SentryManager currently creates Breadcrumbs with only
message and data, so they all arrive as default level/info and no category;
update addBreadcrumb (and the SentryReporter interface and
SentryBreadcrumbHandler callsite) to accept and pass a SentryLevel (e.g.,
SentryLevel.debug for trace-level logs) and an optional category/logger name,
then construct Breadcrumb with level: providedLevel and category:
providedCategory before calling Sentry.addBreadcrumb, preserving the try/catch
and _isSentryAvailable check.

113-117: Align captureMessage named-parameter order with the SentryReporter interface.

The interface declares captureMessage(String, {extras, level}) (no default for level), while this implementation declares {level = SentryLevel.info, extras}. Dart allows this for named parameters, but the divergence in both order and default makes the contract harder to read and invites future confusion (e.g. when someone compares the two signatures to verify they match). Consider aligning the order (and either dropping the default here or adding a matching one in the interface) so the interface and implementation are obviously congruent.

♻️ Proposed alignment
   `@override`
   void captureMessage(
     String message, {
-    SentryLevel level = SentryLevel.info,
     Map<String, dynamic>? extras,
+    SentryLevel level = SentryLevel.info,
   }) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/lib/utils/sentry/sentry_manager.dart` around lines 113 - 117, The
implementation of captureMessage differs from the SentryReporter interface by
swapping the named-parameter order and providing a default for level; update the
implementation to match the interface signature exactly by changing
captureMessage to accept named parameters in the same order ({Map<String,
dynamic>? extras, SentryLevel? level}) and remove the SentryLevel.info default,
and then run a quick search for any call sites that relied on the previous
default and adjust them if necessary; reference captureMessage and the
SentryReporter interface when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@core/lib/utils/sentry/sentry_manager.dart`:
- Around line 143-164: The addBreadcrumb method in SentryManager currently
creates Breadcrumbs with only message and data, so they all arrive as default
level/info and no category; update addBreadcrumb (and the SentryReporter
interface and SentryBreadcrumbHandler callsite) to accept and pass a SentryLevel
(e.g., SentryLevel.debug for trace-level logs) and an optional category/logger
name, then construct Breadcrumb with level: providedLevel and category:
providedCategory before calling Sentry.addBreadcrumb, preserving the try/catch
and _isSentryAvailable check.
- Around line 113-117: The implementation of captureMessage differs from the
SentryReporter interface by swapping the named-parameter order and providing a
default for level; update the implementation to match the interface signature
exactly by changing captureMessage to accept named parameters in the same order
({Map<String, dynamic>? extras, SentryLevel? level}) and remove the
SentryLevel.info default, and then run a quick search for any call sites that
relied on the previous default and adjust them if necessary; reference
captureMessage and the SentryReporter interface when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d1e242ba-2644-44be-8a38-948d0fea09a9

📥 Commits

Reviewing files that changed from the base of the PR and between 4eb8884 and 812197a.

📒 Files selected for processing (7)
  • core/lib/utils/app_logger.dart
  • core/lib/utils/logging/handlers/sentry_event_handler.dart
  • core/lib/utils/sentry/sentry_manager.dart
  • core/lib/utils/sentry/sentry_reporter.dart
  • core/test/utils/logging/app_logger_registry_test.dart
  • core/test/utils/logging/handlers/sentry_breadcrumb_handler_test.dart
  • core/test/utils/logging/handlers/sentry_event_handler_test.dart
✅ Files skipped from review due to trivial changes (1)
  • core/test/utils/logging/handlers/sentry_event_handler_test.dart
🚧 Files skipped from review as they are similar to previous changes (5)
  • core/lib/utils/sentry/sentry_reporter.dart
  • core/test/utils/logging/handlers/sentry_breadcrumb_handler_test.dart
  • core/lib/utils/logging/handlers/sentry_event_handler.dart
  • core/test/utils/logging/app_logger_registry_test.dart
  • core/lib/utils/app_logger.dart

codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/lib/utils/logging/app_logger_registry.dart`:
- Around line 35-38: The loop in app_logger_registry.dart that iterates over
_handlers and calls handler.handle(record) can let one handler's exception abort
other handlers and bubble out of the central logging path; wrap each invocation
of handler.handle(record) in its own try/catch so a failing handler doesn't stop
dispatch or crash callers, and on catch report the failure (including the caught
error and stacktrace) to a safe fallback (e.g., a dedicated internal logger or
Zone.current.handleUncaughtError) but continue to the next handler; update the
for (final handler in _handlers) { if (handler.handles(record))
handler.handle(record); } block accordingly to isolate handler failures while
preserving the original record delivery semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88c70148-9b21-463b-9516-46aa1727414a

📥 Commits

Reviewing files that changed from the base of the PR and between 812197a and 09c28cf.

📒 Files selected for processing (13)
  • core/lib/utils/app_logger.dart
  • core/lib/utils/logging/app_logger_registry.dart
  • core/lib/utils/logging/handlers/console_log_handler.dart
  • core/lib/utils/logging/handlers/sentry_breadcrumb_handler.dart
  • core/lib/utils/logging/log_record.dart
  • core/lib/utils/sentry/sentry_config.dart
  • core/lib/utils/sentry/sentry_manager.dart
  • core/lib/utils/sentry/sentry_reporter.dart
  • core/test/utils/logging/app_logger_registry_test.dart
  • core/test/utils/logging/formatters/web_console_formatter_test.dart
  • core/test/utils/logging/handlers/sentry_breadcrumb_handler_test.dart
  • lib/main/runner/app_runner_base.dart
  • lib/main/runner/app_runner_web.dart
💤 Files with no reviewable changes (1)
  • core/lib/utils/sentry/sentry_config.dart
✅ Files skipped from review due to trivial changes (3)
  • core/lib/utils/sentry/sentry_reporter.dart
  • core/lib/utils/logging/handlers/console_log_handler.dart
  • core/test/utils/logging/app_logger_registry_test.dart
🚧 Files skipped from review as they are similar to previous changes (4)
  • lib/main/runner/app_runner_web.dart
  • lib/main/runner/app_runner_base.dart
  • core/test/utils/logging/formatters/web_console_formatter_test.dart
  • core/lib/utils/logging/log_record.dart

Comment thread core/lib/utils/logging/app_logger_registry.dart
dab246 added 16 commits April 22, 2026 16:07
feat(logger): add SentryEventHandler and SentryBreadcrumbHandler (ADR-0078)
…-0078)

refactor(logger): register Sentry handlers at startup for mobile and web (ADR-0078)
SentryReporter.captureException and captureMessage now accept a
SentryLevel parameter so handlers can forward the mapped severity.
SentryEventHandler maps Level.critical → fatal, Level.error → error.
SentryManager forwards the level via scope.level.
Web console output is now configured globally via ConsoleLogHandler
at startup. The parameter is retained for source compatibility.
- app_logger_registry: add _OtherFakeHandler subclass; fix
  'dispatches to multiple handlers' to register two distinct types
  and assert both receive the record; use const LogRecord
- sentry_breadcrumb_handler: track capturedExceptions/capturedMessages
  in fake; assert both are empty in 'does not call' test
- sentry_event_handler: add SentryLevel to fake captured records;
  assert mapped severity in captureException/captureMessage tests
webConsoleEnabled was deprecated in favour of a global constructor flag,
but that removed the ability to force specific log calls visible on web
in release/profile mode — a legitimate need for auth and Sentry init
diagnostics.

Carry webConsoleEnabled through the full pipeline:
LogRecord → buildLogRecord → _dispatch → public log functions.
ConsoleLogHandler reads the flag from the record rather than from its
constructor, so per-call opt-in works independently of build mode.

On web:
- handles() always returns true (filter deferred to handle())
- handle() prints only when record.webConsoleEnabled || isDebugMode

Existing call sites (authorization_interceptors, sentry_config) that
pass webConsoleEnabled: true are preserved and functional again.
…essage signature

- addBreadcrumb gains level (default SentryLevel.debug) and category
  params in SentryReporter interface and SentryManager implementation;
  SentryBreadcrumbHandler passes level: SentryLevel.debug, category: 'log'
  so breadcrumbs on error events are clearly labelled in Sentry UI
- captureMessage param order aligned to {extras, level} in SentryManager
  to match the SentryReporter interface declaration
- Test: _FakeSentryReporter updated; new test case asserts level/category
- Wrap each handler.handle() in try/catch so a failing handler does not
  abort the remaining handlers or bubble exceptions to callers
- Fix _FakeSentryReporter.addBreadcrumb signature to match SentryReporter
  interface (add level and category named parameters)
@dab246 dab246 force-pushed the feat/adr-0078-logger-pipeline-skeleton branch from 09c28cf to c0303f8 Compare April 22, 2026 09:08
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
core/lib/utils/sentry/sentry_manager.dart (1)

10-10: LGTM — clean SentryReporter implementation.

The interface adoption, level propagation into scope.level for both capture paths, and the new addBreadcrumb with explicit recursion-safety guidance in the catch block all look correct and match the SentryReporter contract (parameter order extras before level).

One thing worth double-checking: logWarning in the addBreadcrumb catch (Line 168) is safe today only because no registered handler subscribes to warning via breadcrumb/event paths. If a future handler ever maps warningSentry.addBreadcrumb or Sentry.captureMessage, a throwing Sentry SDK here could recurse. The inline note at Lines 166–167 captures this well; consider also asserting it in a test (e.g., a throwing SentryReporter fake that records no re-entry) to lock the invariant.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/lib/utils/logging/app_logger_registry.dart`:
- Around line 13-15: The doc comment incorrectly states that registerHandler is
a no-op for duplicate handler registrations; update the comment in
AppLoggerRegistry to say that registerHandler checks by runtimeType and will
replace any existing handler of the same type (rather than preserving it) to
reflect current behavior and avoid relying on stale instances.
- Around line 44-54: The dispatch method iterates the mutable _handlers list
directly which can throw ConcurrentModificationError if a handler
registers/unregisters during handling; modify dispatch (in
AppLoggerRegistry.dispatch) to first snapshot the handlers (e.g., final handlers
= List.of(_handlers)) and iterate that snapshot so mutations during
handler.handle/handler.handles (or calls to registerHandler/unregisterHandler)
won't cause iteration to fail; keep the existing per-handler try/catch behavior
and only change the iteration source to the copied list.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0cbdd090-075d-410b-bdc5-5e54d7d393dd

📥 Commits

Reviewing files that changed from the base of the PR and between c0303f8 and cae649f.

📒 Files selected for processing (2)
  • core/lib/utils/logging/app_logger_registry.dart
  • core/lib/utils/sentry/sentry_manager.dart

Comment thread core/lib/utils/logging/app_logger_registry.dart Outdated
Comment thread core/lib/utils/logging/app_logger_registry.dart
Comment thread core/lib/utils/logging/app_logger_registry.dart
Comment thread core/lib/utils/logging/app_logger_registry.dart Outdated
Comment thread core/lib/utils/logging/app_logger_registry.dart
Comment thread core/lib/utils/logging/handlers/console_log_handler.dart
Comment thread core/lib/utils/logging/app_logger_registry.dart
Comment thread core/test/utils/logging/handlers/sentry_event_handler_test.dart
Comment thread core/test/utils/logging/handlers/sentry_event_handler_test.dart
dab246 added 2 commits April 23, 2026 15:50
Add handlerKey to LogHandler so that two instances of the same concrete
class can coexist in AppLoggerRegistry (e.g. two ConsoleLogHandlers with
different formatters, or two Sentry handlers targeting different DSNs).

Default key is runtimeType.toString(), preserving existing dedup behaviour
for handlers that do not override it.

Also add @VisibleForTesting on resetForTesting() and convert LogHandler
from abstract interface class to abstract class so the default handlerKey
implementation is inherited without requiring each implementor to redeclare it.
…verage

- handlerKey: verify two instances of same class with distinct keys coexist,
  same key replaces existing handler, both handlers receive dispatched records
- fault-isolation: verify handles()/handle() throwing does not crash dispatch
  and remaining handlers still receive the record
- critical level: verify Level.critical maps to SentryLevel.fatal in both
  captureException and captureMessage paths of SentryEventHandler
codescene-delta-analysis[bot]

This comment was marked as outdated.

Comment thread core/lib/utils/logging/handlers/console_log_handler.dart Outdated
Comment thread core/lib/utils/app_logger.dart Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Comment thread core/lib/utils/logging/handlers/console_log_handler.dart Outdated
Comment thread core/lib/utils/logging/app_logger_registry.dart Outdated
codescene-delta-analysis[bot]

This comment was marked as outdated.

Copy link
Copy Markdown

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Choose a reason for hiding this comment

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

Gates Passed
3 Quality Gates Passed

See analysis details in CodeScene

Quality Gate Profile: The Bare Minimum
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

@hoangdat hoangdat merged commit b0e3490 into master Apr 28, 2026
26 checks passed
@hoangdat hoangdat deleted the feat/adr-0078-logger-pipeline-skeleton branch May 28, 2026 02:32
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.

3 participants