Skip to content

fix(logger): Reduce Sentry noise with targeted error logging (ADR-0076)#4467

Open
dab246 wants to merge 8 commits intomasterfrom
feat/adr-0078-callsite-fixes
Open

fix(logger): Reduce Sentry noise with targeted error logging (ADR-0076)#4467
dab246 wants to merge 8 commits intomasterfrom
feat/adr-0078-callsite-fixes

Conversation

@dab246
Copy link
Copy Markdown
Member

@dab246 dab246 commented Apr 22, 2026

Context

Implements the call-site fixes from ADR-0076.
Requires ADR-0078 PR to be merged first.

What changed

RemoteExceptionThrower

  • Removed blanket logError at the top of throwException
  • Per-branch logging:
    • Network errors (NoNetwork, ConnectionTimeout, ConnectionError, SocketException) → logWarning
    • HTTP 401 → no log (handled by auth retry)
    • HTTP 4xx/5xx (known) → logWarning
    • Unknown/unrecognised errors → logError

SendEmailExceptionThrower

  • Changed logError for no realtime network → logWarning
    (network loss is a normal user-facing condition)

SentryInitializer

  • Added SocketException to ignoredExceptionsForType
    (second line of defence against accidental logError regressions at call sites)

Blocker

#4466

Summary by CodeRabbit

  • Bug Fixes

    • Suppress noisy socket/network errors from external reporting; better handle connection timeouts, offline cases, and HTTP error categories (including 500/502 and 4xx/5xx).
    • Downgraded “no network” reporting to a user-facing warning.
  • Refactor

    • Split error handling into distinct response vs no-response paths for clearer behavior and more consistent error mapping.
  • Chores

    • Removed redundant error-level logs and updated internal sanitization/filtering notes.

@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

Sentry initializer now imports dart:io and configures the Sentry SDK to ignore SocketException types; its _beforeSend doc comment was expanded to state it sanitizes request headers and deminifies exception stack traces (no functional change). remote_exception_thrower.dart was refactored to delegate Dio response/no-response cases to new DioResponseErrorHandler and DioNoResponseErrorHandler, add an optional StackTrace? parameter to handlers, change control flow to dispatch by error type, and adjust logging. send_email_exception_thrower.dart removed an initial error log, logs a warning when offline, and forwards both error and stackTrace when online.

Possibly related PRs

Suggested reviewers

  • tddang-linagora
  • hoangdat
  • 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 PR title accurately summarizes the main change: reducing Sentry noise through targeted error logging by refactoring exception handlers to use contextual logging levels instead of blanket errors.
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-callsite-fixes

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.

@dab246 dab246 marked this pull request as draft April 22, 2026 07:05
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

Caution

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

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

84-103: ⚠️ Potential issue | 🟠 Major

Docstring describes event-filtering behavior that the implementation doesn't perform.

The doc comment claims _beforeSendHandler will "drop events unless tagged as auth-critical" and provides an extras: {'auth_critical': true} usage example. However, the actual implementation only sanitizes headers and deminifies exceptions—there is no check for event.tags['auth_critical'] or event.extra['auth_critical'], and the method returns all events unconditionally.

No call sites in the codebase currently use this pattern, so the promise hasn't been relied upon yet. This mismatch still risks confusion for developers reading the docs expecting event filtering that won't occur.

Since this PR references ADR-0078 for event filtering logic, either:

  1. Move the auth-critical filtering description to ADR-0078's implementation, or
  2. Defer the allowlist documentation and simplify the comment to describe only what currently runs: header sanitization and exception deminification.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/lib/utils/sentry/sentry_initializer.dart` around lines 84 - 103, The
docstring for _beforeSendHandler claims event filtering/allowlisting for
auth-critical events but the implementation only calls _sanitizeRequest and
_deminifyExceptions and returns the event; update the comment to accurately
describe current behavior by removing the allowlist/filtering text and the
extras example (or reference ADR-0078 if you prefer to document filtering
there), and keep only a short description that this handler sanitizes request
headers via _sanitizeRequest and deminifies exceptions via _deminifyExceptions
before returning the event.
🧹 Nitpick comments (1)
lib/main/exceptions/thrower/remote_exception_thrower.dart (1)

70-100: Minor: redundant UnknownRemoteException construction and redundant statusCode parameter.

Two small cleanups:

  1. In the default branch the same UnknownRemoteException(code: statusCode, message: response.statusMessage) is constructed twice — once as logError's exception: argument and once as the thrown value. Build it once and reuse.
  2. _handleDioResponseError receives both statusCode and response, but statusCode is just response.statusCode. Passing only response keeps the contract tighter and avoids any future drift between the two.
♻️ Proposed refactor
-    if (response != null) {
-      return _handleDioResponseError(statusCode, response);
-    }
+    if (response != null) {
+      return _handleDioResponseError(response);
+    }
@@
-  void _handleDioResponseError(int? statusCode, Response response) {
+  void _handleDioResponseError(Response response) {
+    final statusCode = response.statusCode;
     switch (statusCode) {
       case HttpStatus.unauthorized:
         // 401 is handled by auth retry flow — no log needed
         throw const BadCredentialsException();
       case HttpStatus.internalServerError:
         logWarning('RemoteExceptionThrower: HTTP 500');
         throw const InternalServerError();
       case HttpStatus.badGateway:
         logWarning('RemoteExceptionThrower: HTTP 502');
         throw BadGateway();
       default:
+        final exception = UnknownRemoteException(
+          code: statusCode,
+          message: response.statusMessage,
+        );
         if (statusCode != null && statusCode >= 400 && statusCode < 500) {
           logWarning('RemoteExceptionThrower: HTTP 4xx ($statusCode)');
         } else if (statusCode != null && statusCode >= 500) {
           logWarning('RemoteExceptionThrower: HTTP 5xx ($statusCode)');
         } else {
-          logError(
-            'RemoteExceptionThrower: unknown HTTP status $statusCode',
-            exception: UnknownRemoteException(
-              code: statusCode,
-              message: response.statusMessage,
-            ),
-          );
+          logError(
+            'RemoteExceptionThrower: unknown HTTP status $statusCode',
+            exception: exception,
+          );
         }
-        throw UnknownRemoteException(
-          code: statusCode,
-          message: response.statusMessage,
-        );
+        throw exception;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/main/exceptions/thrower/remote_exception_thrower.dart` around lines 70 -
100, Refactor _handleDioResponseError to accept only the Dio Response (remove
the redundant statusCode parameter) and inside the function compute finalStatus
= response.statusCode; in the default branch construct a single
UnknownRemoteException once (e.g., final ex = UnknownRemoteException(code:
finalStatus, message: response.statusMessage)), pass ex to logError(...,
exception: ex) and then throw ex; update any callers of _handleDioResponseError
to pass the Response object only, and keep existing switch cases
(HttpStatus.unauthorized, HttpStatus.internalServerError, HttpStatus.badGateway)
unchanged while switching on finalStatus.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/main/exceptions/thrower/remote_exception_thrower.dart`:
- Around line 51-68: The log in _handleDioException currently interpolates the
full DioException.response (which serialises headers and body) — change the
logWarning call in _handleDioException to only include non-sensitive
discriminants needed for routing such as error.type, response?.statusCode, and a
minimal request identifier like response?.requestOptions?.path or method, and
never include response?.data or response.toString(); keep full response/body
logging only inside the branch handlers (_handleDioResponseError or
_handleDioErrorWithoutResponse) if those handlers decide richer context is safe,
and preserve the existing RefreshTokenFailedException short-circuit behavior.

---

Outside diff comments:
In `@core/lib/utils/sentry/sentry_initializer.dart`:
- Around line 84-103: The docstring for _beforeSendHandler claims event
filtering/allowlisting for auth-critical events but the implementation only
calls _sanitizeRequest and _deminifyExceptions and returns the event; update the
comment to accurately describe current behavior by removing the
allowlist/filtering text and the extras example (or reference ADR-0078 if you
prefer to document filtering there), and keep only a short description that this
handler sanitizes request headers via _sanitizeRequest and deminifies exceptions
via _deminifyExceptions before returning the event.

---

Nitpick comments:
In `@lib/main/exceptions/thrower/remote_exception_thrower.dart`:
- Around line 70-100: Refactor _handleDioResponseError to accept only the Dio
Response (remove the redundant statusCode parameter) and inside the function
compute finalStatus = response.statusCode; in the default branch construct a
single UnknownRemoteException once (e.g., final ex =
UnknownRemoteException(code: finalStatus, message: response.statusMessage)),
pass ex to logError(..., exception: ex) and then throw ex; update any callers of
_handleDioResponseError to pass the Response object only, and keep existing
switch cases (HttpStatus.unauthorized, HttpStatus.internalServerError,
HttpStatus.badGateway) unchanged while switching on finalStatus.
🪄 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: 0546413f-4366-4fb0-af9d-57af2f0d04b3

📥 Commits

Reviewing files that changed from the base of the PR and between 6ec654e and 4696a12.

📒 Files selected for processing (3)
  • core/lib/utils/sentry/sentry_initializer.dart
  • lib/main/exceptions/thrower/remote_exception_thrower.dart
  • lib/main/exceptions/thrower/send_email_exception_thrower.dart

Comment thread lib/main/exceptions/thrower/remote_exception_thrower.dart Outdated
@github-actions
Copy link
Copy Markdown

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

codescene-delta-analysis[bot]

This comment was marked as outdated.

@dab246 dab246 marked this pull request as ready for review April 22, 2026 09:20
…code complexity (ADR-0078)

Extract DioResponseErrorHandler and DioNoResponseErrorHandler to separate files,
reducing aggregate branch count in RemoteExceptionThrower per CodeScene ADR-0078.
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: 2

🧹 Nitpick comments (1)
lib/main/exceptions/thrower/dio_response_error_handler.dart (1)

9-46: LGTM on the response-status split.

Logging levels align with the ADR-0076 intent (401 silent, 4xx/5xx warnings, truly unknown → error). Nullable statusCode is safely funneled to _logUnhandledStatusCode, which logs as error only when the code is null or outside 4xx/5xx — appropriate.

Minor nit: BadGateway() at Line 21 isn't marked const while InternalServerError() at Line 18 is — worth making both const if BadGateway's constructor allows it, for consistency.

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

In `@lib/main/exceptions/thrower/dio_response_error_handler.dart` around lines 9 -
46, The BadGateway instantiation in DioResponseErrorHandler.handle should be
made const for consistency with InternalServerError (i.e., change BadGateway()
to const BadGateway()) if the BadGateway class has a const constructor; update
the call in the switch case that currently throws BadGateway() so both 500 and
502 branches use const constructors, leaving other logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/main/exceptions/thrower/dio_no_response_error_handler.dart`:
- Around line 11-24: The switch in RemoteExceptionThrower.handle currently sends
DioExceptionType.sendTimeout and receiveTimeout to the default path which ends
up calling _handleUnknownUnderlying/_throwUnknownRemoteException and logError;
update the switch in dio_no_response_error_handler.dart to treat sendTimeout and
receiveTimeout the same as connectionTimeout by grouping them into the same
case, calling logWarning('RemoteExceptionThrower: connection timeout') and
throwing ConnectionTimeout(message: error.message) so timeouts are handled as
expected and do not trigger logError/Sentry noise.
- Around line 19-20: The switch branch that maps DioExceptionType.badResponse to
BadCredentialsException is incorrect for the null-response path; remove the
explicit case in dio_no_response_error_handler.dart (the "case
DioExceptionType.badResponse: throw const BadCredentialsException();" in the
handler) so the code falls through to the existing _handleUnknownUnderlying
logic (or explicitly call _handleUnknownUnderlying(...) there) instead, ensuring
we do not silently trigger the auth-retry path for an unknown/edge state and
that the condition is logged/handled by the unknown-error flow referenced by
DioResponseErrorHandler/_handleUnknownUnderlying.

---

Nitpick comments:
In `@lib/main/exceptions/thrower/dio_response_error_handler.dart`:
- Around line 9-46: The BadGateway instantiation in
DioResponseErrorHandler.handle should be made const for consistency with
InternalServerError (i.e., change BadGateway() to const BadGateway()) if the
BadGateway class has a const constructor; update the call in the switch case
that currently throws BadGateway() so both 500 and 502 branches use const
constructors, leaving other logic unchanged.
🪄 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: 6be1f6fe-def7-48e6-ba9e-1d473cde0a2d

📥 Commits

Reviewing files that changed from the base of the PR and between d7efc61 and 90da751.

📒 Files selected for processing (3)
  • lib/main/exceptions/thrower/dio_no_response_error_handler.dart
  • lib/main/exceptions/thrower/dio_response_error_handler.dart
  • lib/main/exceptions/thrower/remote_exception_thrower.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/main/exceptions/thrower/remote_exception_thrower.dart

Comment thread lib/main/exceptions/thrower/dio_no_response_error_handler.dart Outdated
Comment thread lib/main/exceptions/thrower/dio_no_response_error_handler.dart Outdated
…ndler (ADR-0078)

- Group sendTimeout/receiveTimeout with connectionTimeout to avoid logError
  noise for normal network conditions (fixes Sentry regression risk)
- Remove badResponse → BadCredentialsException mapping when response is null,
  which was silently triggering auth-retry for genuinely unknown states
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 `@lib/main/exceptions/thrower/dio_no_response_error_handler.dart`:
- Around line 20-22: The default branch currently forwards
DioExceptionType.cancel into
_handleUnknownUnderlying/_throwUnknownRemoteException which calls logError;
change the switch (or add a case) to explicitly handle DioExceptionType.cancel
by calling logWarning (or suppressing logs) and throwing a dedicated
cancellation exception similar to the timeout path (mirror the behavior used for
timeout handling), i.e., add a case for DioExceptionType.cancel that avoids
logError and invokes the same known exception type or a new
CancelledRequestException so cancellations do not generate Sentry error noise;
update references in _handleUnknownUnderlying/_throwUnknownRemoteException usage
as needed.
🪄 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: 80d24f30-f502-43aa-9f31-b79fc792cded

📥 Commits

Reviewing files that changed from the base of the PR and between 90da751 and 0ed62b4.

📒 Files selected for processing (1)
  • lib/main/exceptions/thrower/dio_no_response_error_handler.dart

Comment thread lib/main/exceptions/thrower/dio_no_response_error_handler.dart
logWarning('RemoteExceptionThrower: connection error');
throw ConnectionError(message: error.message);
default:
_handleUnknownUnderlying(error.error);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should put also error.stackTrace

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

added

_throwUnknownRemoteException(underlyingError);
}

void _throwUnknownRemoteException(dynamic underlyingError) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should put also error.stackTrace

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

added

code: statusCode,
message: response.statusMessage,
);
_logUnhandledStatusCode(statusCode, exception);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

idem

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

added

@@ -37,70 +29,50 @@ class RemoteExceptionThrower extends ExceptionThrower {

void handleDioError(dynamic error) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
void handleDioError(dynamic error) {
void handleDioError(dynamic error, [StackTrace? stackTrace]) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

added


final response = error.response;
final statusCode = response?.statusCode;
logError(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

try to put stack trace in every logError

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

added

Signed-off-by: dab246 <tdvu@linagora.com>
@dab246 dab246 requested a review from hoangdat May 4, 2026 06:57
codescene-delta-analysis[bot]

This comment was marked as outdated.

Signed-off-by: dab246 <tdvu@linagora.com>
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.

Code Health Improved (1 files improve in Code Health)

Gates Passed
3 Quality Gates Passed

See analysis details in CodeScene

View Improvements
File Code Health Impact Categories Improved
remote_exception_thrower.dart 9.01 → 10.00 Complex Method, Bumpy Road Ahead

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.

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