Skip to content

fix: exceptions thrown from BeforeBreadcrumb, log filters and BeforeScreenshotCapture no longer reach the application - #5606

Merged
jamescrosswell merged 6 commits into
mainfrom
fix/isolate-user-callbacks
Sep 28, 2026
Merged

jamescrosswell merged 6 commits into
mainfrom
fix/isolate-user-callbacks

Conversation

@jamescrosswell

@jamescrosswell jamescrosswell commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes work item 1 ("isolate the callbacks that can reach the host app") of #5535. #5545 already covered TracesSampler on all three paths; this covers the remaining five boxes.

Each callback now runs inside a recovery boundary that logs an error naming the callback and applies the fallback the Callback Error Isolation spec asks for. Nothing changes for anyone whose callbacks don't throw, and there's no public API change.

Callback Site Fallback on failure
BeforeBreadcrumb Scope.AddBreadcrumb drop the breadcrumb
BeforeBreadcrumb Platforms/Android/Callbacks/BeforeBreadcrumbCallback return null — drop the breadcrumb
BeforeBreadcrumb Platforms/Cocoa/SentrySdk return null — drop the breadcrumb
ILogEntryFilter.Filter Sentry.Extensions.Logging/SentryLogger drop the log entry
SetBeforeScreenshotCapture Sentry.Maui/Internal/SentryMauiScreenshotProcessor skip the screenshot, keep the event

Notes for review:

  • Scope.AddBreadcrumb was only accidentally isolated before: HubExtensions.AddBreadcrumb happens to route through Hub.ConfigureScope's catch-all, but Scope.AddBreadcrumb is public and a direct call was unprotected — and the catch-all's "Failure to ConfigureScope" doesn't tell anyone their breadcrumb callback is broken.
  • A throwing ILogEntryFilter drops the entry, rather than letting it through as "did not filter". That was review feedback, and the argument for it is that before this PR the exception reached the app and nothing was sent — so failing open would have changed the data behaviour on top of fixing the crash, in the direction that leaks. A filter written to exclude entries would, once broken, start sending them. Dropping keeps the old outcome and only removes the crash. It also matches the spec rationale for filters and what fix: an exception in BeforeSend or BeforeSendTransaction now drops the item instead of sending it #5610 does for BeforeSend. (Conform to the callback error isolation spec (wrap all user callbacks) #5535 proposed "treat as did not filter"; ILogEntryFilter isn't in the spec matrix, so that line was a judgement call rather than anything normative, and it was the wrong one.)
  • Filters are now evaluated once per log entry instead of twice. ShouldCaptureEvent and ShouldAddBreadcrumb each ran the whole chain, so user filter code was invoked twice for any entry clearing both thresholds — and, once failures are logged, logged twice. Their shared tail is now a single early return in Log, leaving the two predicates to test only their own level threshold. Never zero: IsEnabled already guarantees at least one threshold is met, so anything reaching that line evaluated the chain at least once before.
  • The internal screenshot-callback names now match the public SetBeforeScreenshotCapture (BeforeCaptureInternal → BeforeScreenshotCaptureInternal), and a doc sample calling a non-existent SetBeforeCapture is fixed. The public parameter is still named beforeCapture — parameter names are in the API approval snapshots and renaming breaks named arguments, so that's left alone.
  • SetBeforeScreenshotCapture previously unwound to the Hub.CaptureEvent catch-all and dropped the whole event, losing the error the user was trying to report.
  • No client-report changes here: BeforeBreadcrumb has no report in the spec matrix, and ILogEntryFilter/SetBeforeScreenshotCapture aren't in it. Work items 2 (fix: telemetry dropped by user callbacks and by validation is now counted in client reports #5607) and 3 (fix: an exception in BeforeSend or BeforeSendTransaction now drops the item instead of sending it #5610) of Conform to the callback error isolation spec (wrap all user callbacks) #5535 cover the reports and the BeforeSend alignment, so this doesn't close the issue.
  • The Android bridge is covered by device tests (BeforeBreadcrumbCallbackTests, run on API 34 and 36 across net9.0/net10.0). The Cocoa bridge isn't: its BeforeBreadcrumb is a lambda assigned inline in InitSentryCocoaSdk with no seam to invoke without initialising the native SDK. Extracting it would make it testable the same way — worth doing, but as its own change.

🤖 Generated with Claude Code

…creenshotCapture no longer reach the application

Completes the callback-isolation work item from #5535 that #5545 started for
TracesSampler. Each callback now runs inside a recovery boundary that logs an
error naming the callback and applies the spec's fallback:

- BeforeBreadcrumb (Scope.AddBreadcrumb, Android JNI, Cocoa) drops the breadcrumb
- ILogEntryFilter.Filter treats a failing filter as "did not filter"
- SetBeforeScreenshotCapture skips the screenshot and keeps the event

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.91%. Comparing base (7a6db80) to head (5e6a369).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...try.Maui/Internal/SentryMauiScreenshotProcessor.cs 75.00% 1 Missing and 1 partial ⚠️
src/Sentry.Extensions.Logging/SentryLogger.cs 92.85% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5606      +/-   ##
==========================================
+ Coverage   74.84%   74.91%   +0.07%     
==========================================
  Files         515      515              
  Lines       18962    18966       +4     
  Branches     3694     3696       +2     
==========================================
+ Hits        14192    14209      +17     
+ Misses       3892     3877      -15     
- Partials      878      880       +2     

☔ View full report in Codecov by Harness.
📢 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.

Comment thread src/Sentry/Platforms/Cocoa/SentrySdk.cs Outdated
…ndroid device tests

Addresses review feedback on #5606: distinguish native callback failures from
managed ones in the diagnostic log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jamescrosswell jamescrosswell linked an issue Sep 22, 2026 that may be closed by this pull request
11 tasks
@jamescrosswell
jamescrosswell marked this pull request as ready for review September 23, 2026 01:14
@github-actions github-actions Bot added the risk: medium PR risk score: medium label Sep 23, 2026
@jamescrosswell
jamescrosswell added this pull request to stack #5618 September 23, 2026 08:04

@ric-oliv ric-oliv left a comment

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.

Looks good! Just one comment about the naming.

Comment thread src/Sentry.Extensions.Logging/SentryLogger.cs Outdated
@ric-oliv
ric-oliv self-requested a review September 23, 2026 11:20
jamescrosswell and others added 3 commits September 24, 2026 10:18
…ng it through

Review feedback on #5606.

Failing open meant a filter written to exclude entries would, once broken,
start sending them. Before this PR the exception reached the app and nothing
was sent, so dropping keeps that outcome while removing the crash, and matches
the filter rule applied to BeforeSend in #5610.

Also aligns the internal screenshot callback names with the public
SetBeforeScreenshotCapture, and fixes a doc sample naming a method that no
longer exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ShouldCaptureEvent and ShouldAddBreadcrumb each ran the whole filter chain, so
a user filter was invoked twice for every entry that cleared both thresholds —
and, since the previous commit, logged its failure twice. The two predicates
now test only their level threshold, and the shared guard runs once in Log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread src/Sentry.Extensions.Logging/SentryLogger.cs
@jamescrosswell
jamescrosswell removed this pull request from stack #5618 September 27, 2026 22:01
@jamescrosswell
jamescrosswell added this pull request to stack #5628 September 27, 2026 22:01
@jamescrosswell
jamescrosswell removed this pull request from stack #5628 September 27, 2026 22:01
@jamescrosswell
jamescrosswell merged commit 051ee2c into main Sep 28, 2026
50 checks passed
@jamescrosswell
jamescrosswell deleted the fix/isolate-user-callbacks branch September 28, 2026 22:02
jamescrosswell added a commit that referenced this pull request Sep 28, 2026
…nted in client reports (#5607)

* fix: exceptions thrown from BeforeBreadcrumb, log filters and BeforeScreenshotCapture no longer reach the application

Completes the callback-isolation work item from #5535 that #5545 started for
TracesSampler. Each callback now runs inside a recovery boundary that logs an
error naming the callback and applies the spec's fallback:

- BeforeBreadcrumb (Scope.AddBreadcrumb, Android JNI, Cocoa) drops the breadcrumb
- ILogEntryFilter.Filter treats a failing filter as "did not filter"
- SetBeforeScreenshotCapture skips the screenshot and keeps the event

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ref: name the platform in native BeforeBreadcrumb failure logs, add Android device tests

Addresses review feedback on #5606: distinguish native callback failures from
managed ones in the diagnostic log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: record client reports for items dropped by user callbacks

Work item 2 of #5535. Items lost to a user callback — whether it returned
null or threw — were disappearing without a client report, and a throwing
event or transaction processor skipped the report entirely by unwinding to
the Hub catch-all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: record client reports for logs and metrics dropped by validation

A log whose template doesn't match its arguments, and a metric with an
unsupported value type or an empty name, were dropped with a diagnostic
log and no client report. Records the spec's `invalid` reason, which the
SDK did not previously carry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: log when BeforeSendLog or BeforeSendMetric drops an item

Every other deliberate drop by a user callback logs at info level naming
the callback; logs and metrics were the exception.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: a throwing log entry filter now drops the entry instead of letting it through

Review feedback on #5606.

Failing open meant a filter written to exclude entries would, once broken,
start sending them. Before this PR the exception reached the app and nothing
was sent, so dropping keeps that outcome while removing the crash, and matches
the filter rule applied to BeforeSend in #5610.

Also aligns the internal screenshot callback names with the public
SetBeforeScreenshotCapture, and fixes a doc sample naming a method that no
longer exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ref: evaluate log entry filters once per log call instead of twice

ShouldCaptureEvent and ShouldAddBreadcrumb each ran the whole filter chain, so
a user filter was invoked twice for every entry that cleared both thresholds —
and, since the previous commit, logged its failure twice. The two predicates
now test only their level threshold, and the shared guard runs once in Log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: report callback failures with the callback_error discard reason

Follows the hooks spec revision in getsentry/sentry-docs#19189
(b53c3009): a throwing callback must be distinguishable from an explicit
drop, so every item a failure drops is recorded as callback_error with
the item's category. Null returns keep their existing reasons.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore: rebuild the pull request merge ref

GitHub stopped rebuilding refs/pull/5607/merge, so no pull_request
workflow could be created and the PR reported a conflict that git shows
does not exist. A new head commit forces the ref to be recomputed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
jamescrosswell added a commit that referenced this pull request Sep 29, 2026
…e item instead of sending it (#5610)

* fix: exceptions thrown from BeforeBreadcrumb, log filters and BeforeScreenshotCapture no longer reach the application

Completes the callback-isolation work item from #5535 that #5545 started for
TracesSampler. Each callback now runs inside a recovery boundary that logs an
error naming the callback and applies the spec's fallback:

- BeforeBreadcrumb (Scope.AddBreadcrumb, Android JNI, Cocoa) drops the breadcrumb
- ILogEntryFilter.Filter treats a failing filter as "did not filter"
- SetBeforeScreenshotCapture skips the screenshot and keeps the event

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ref: name the platform in native BeforeBreadcrumb failure logs, add Android device tests

Addresses review feedback on #5606: distinguish native callback failures from
managed ones in the diagnostic log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: record client reports for items dropped by user callbacks

Work item 2 of #5535. Items lost to a user callback — whether it returned
null or threw — were disappearing without a client report, and a throwing
event or transaction processor skipped the report entirely by unwinding to
the Hub catch-all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: an exception in BeforeSend or BeforeSendTransaction now drops the item

Work item 3 of #5535. Both callbacks previously demystified the exception,
stapled its message and stack trace onto the item as a breadcrumb, and sent
the item anyway. The spec says a callback failure MUST NOT be attached to the
item, and that filters drop — a callback that threw part-way may not have
applied the redaction the user wrote it to apply, so the partially-scrubbed
item plus the exception detail were both reaching Sentry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: record client reports for logs and metrics dropped by validation

A log whose template doesn't match its arguments, and a metric with an
unsupported value type or an empty name, were dropped with a diagnostic
log and no client report. Records the spec's `invalid` reason, which the
SDK did not previously carry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: log when BeforeSendLog or BeforeSendMetric drops an item

Every other deliberate drop by a user callback logs at info level naming
the callback; logs and metrics were the exception.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: a throwing log entry filter now drops the entry instead of letting it through

Review feedback on #5606.

Failing open meant a filter written to exclude entries would, once broken,
start sending them. Before this PR the exception reached the app and nothing
was sent, so dropping keeps that outcome while removing the crash, and matches
the filter rule applied to BeforeSend in #5610.

Also aligns the internal screenshot callback names with the public
SetBeforeScreenshotCapture, and fixes a doc sample naming a method that no
longer exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ref: evaluate log entry filters once per log call instead of twice

ShouldCaptureEvent and ShouldAddBreadcrumb each ran the whole filter chain, so
a user filter was invoked twice for every entry that cleared both thresholds —
and, since the previous commit, logged its failure twice. The two predicates
now test only their level threshold, and the shared guard runs once in Log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: a throwing BeforeSend on the Android bridge no longer sends the unscrubbed native event

The JNI bridge wrapped its whole body in a catch that returns the original Java
event, so a scrubber that threw part-way sent the completely unredacted native
event. That catch was added for serialization safety, so the guard goes around
the user callback inside BeforeSendWrapper instead: it returns null, which the
Java SDK turns into a drop and accounts for in its own client report, and
Execute's catch keeps failing open only for serialization.

Also covers the Cocoa native-crash path, where a throwing callback now drops the
event and previously had no test at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test: move the Cocoa BeforeSend tests in with the existing ProcessOnBeforeSend family

Sentry.Tests.SentrySdkTests already has six ProcessOnBeforeSend tests inside an
#if __IOS__ block. A second SentrySdkTests class under Platforms/iOS was a
confusing duplicate name in a parallel namespace.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: report callback failures with the callback_error discard reason

Follows the hooks spec revision in getsentry/sentry-docs#19189
(b53c3009): a throwing callback must be distinguishable from an explicit
drop, so every item a failure drops is recorded as callback_error with
the item's category. Null returns keep their existing reasons.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: report BeforeSend and BeforeSendTransaction failures as callback_error

Follows the hooks spec revision in getsentry/sentry-docs#19189
(b53c3009). BeforeSendTransaction now records its own discard so a throw
is distinguishable from a null return, which the shared caller could not
tell apart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore: rebuild the pull request merge ref

GitHub stopped rebuilding refs/pull/5607/merge, so no pull_request
workflow could be created and the PR reported a conflict that git shows
does not exist. A new head commit forces the ref to be recomputed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Conform to the callback error isolation spec (wrap all user callbacks)

2 participants