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 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, 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.