Part of Wrap all SDK user callbacks in a try catch (triggered by INC-2332, where a throwing traces_sampler in the Python SDK took down ingest).
Current state in the JS SDK
beforeSendSpan is the only per-hook isolation (applyBeforeSendSpanCallback → log + keep original span).
beforeSend, beforeSendTransaction, beforeSendLog, beforeSendMetric, and event processors are covered only by the pipeline-level catch in Client._processEvent, which drops the event and captures the throw as a new handled: false / mechanism: internal event — burning quota and surfacing as a product issue. Logs/metrics don't go through that path at all.
tracesSampler (packages/core/src/tracing/sampling.ts), beforeBreadcrumb (packages/core/src/breadcrumbs.ts), profilesSampler, initialScope, integrations callback, ignoreSpans-style filters and friends are not isolated: a throw propagates into the host application.
Target behavior
Per the project's matrix:
| Callback |
On throw / rejection |
beforeSend, beforeSendTransaction |
debug-log internal error, drop event |
beforeSendLog |
debug-log, drop log |
beforeSendMetric |
debug-log, drop metric |
beforeSendSpan |
debug-log, emit original span (already done) |
beforeBreadcrumb |
debug-log, drop breadcrumb |
tracesSampler |
debug-log, fall back to parent decision, else tracesSampleRate |
profilesSampler |
debug-log, fall back to profilesSampleRate |
| event processors |
debug-log, drop event |
initialScope / integrations fn / other option callbacks |
debug-log, fall back to default |
Rules: never re-throw into the app, never capture the SDK's own failure as an event (see never capture your own exceptions). Async rejections must behave like sync throws. Record drops as client_report outcomes where a drop happens.
Scope notes
- Audit
packages/core for every option that accepts a function and every client.on(...) hook path; add a shared helper rather than per-site try/catch.
- Replace the
captureException(..., mechanism: internal) in _processEvent with log + drop. This is a behavior change worth a changelog entry; decide whether it needs a major.
- Consider unifying with
@sentry/react-native's safeFactory / safeTracesSampler, which already implement part of this.
- Add tests for each callback: sync throw, async rejection, invalid return value.
Part of Wrap all SDK user callbacks in a try catch (triggered by INC-2332, where a throwing
traces_samplerin the Python SDK took down ingest).Current state in the JS SDK
beforeSendSpanis the only per-hook isolation (applyBeforeSendSpanCallback→ log + keep original span).beforeSend,beforeSendTransaction,beforeSendLog,beforeSendMetric, and event processors are covered only by the pipeline-level catch inClient._processEvent, which drops the event and captures the throw as a newhandled: false/mechanism: internalevent — burning quota and surfacing as a product issue. Logs/metrics don't go through that path at all.tracesSampler(packages/core/src/tracing/sampling.ts),beforeBreadcrumb(packages/core/src/breadcrumbs.ts),profilesSampler,initialScope,integrationscallback,ignoreSpans-style filters and friends are not isolated: a throw propagates into the host application.Target behavior
Per the project's matrix:
beforeSend,beforeSendTransactionbeforeSendLogbeforeSendMetricbeforeSendSpanbeforeBreadcrumbtracesSamplertracesSampleRateprofilesSamplerprofilesSampleRateinitialScope/integrationsfn / other option callbacksRules: never re-throw into the app, never capture the SDK's own failure as an event (see never capture your own exceptions). Async rejections must behave like sync throws. Record drops as
client_reportoutcomes where a drop happens.Scope notes
packages/corefor every option that accepts a function and everyclient.on(...)hook path; add a shared helper rather than per-site try/catch.captureException(..., mechanism: internal)in_processEventwith log + drop. This is a behavior change worth a changelog entry; decide whether it needs a major.@sentry/react-native'ssafeFactory/safeTracesSampler, which already implement part of this.