Skip to content

Wrap all user-provided callbacks in try/catch and degrade gracefully #23755

Description

@msonnb

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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions