Skip to content

Sentry SDKs for errors while OpenTelemetry owns tracing

One tracing owner and the bridge that puts trace ids on errors: Sentry 10 and 11 for Node.js, OTLPIntegration for Python, sentry-go and sentry-java.

Markdown
On this page

Many services already use a Sentry SDK for errors. Keep it — Belvador receives those errors as they are — and let OpenTelemetry own tracing. Two things are separate and both matter:

  1. One tracing owner. Only OpenTelemetry creates spans. The Sentry SDK has tracing off: no tracesSampleRate, traces_sample_rate or TracesSampleRate, no Sentry performance integration.

  2. Errors carry the active span's ids. Turning Sentry tracing off does not by itself make errors read OpenTelemetry's context. Each SDK has a small bridge for that; without it errors and traces are both stored but do not line up.

Keep your existing OpenTelemetry exporter pointed at Belvador. Sentry's examples that build an OTLP endpoint from the DSN send spans to Sentry; skip those parts.

Belvador SDKs#

@belvador/* packages send errors only and never create spans. Pass the OpenTelemetry API with the otel option and errors carry the active span's ids — tested in Node.js and Cloudflare Workers: Link requests.

Node.js (@sentry/node)#

Sentry 11 (released September 2026) has a dedicated integration: it links events to the active OpenTelemetry context, sends no spans and leaves propagation to OpenTelemetry. Start your OpenTelemetry SDK first, then:

JavaScript
import * as Sentry from "@sentry/node";Sentry.init({  dsn: process.env.SENTRY_DSN,  enableOpenTelemetrySetup: false,  integrations: [Sentry.openTelemetryIntegration()],  // no tracesSampleRate});

Sentry 10 has no such integration. Its errors-only custom setup uses skipOpenTelemetrySetup: true and installs Sentry's context manager, sampler and propagator into your OpenTelemetry SDK, which changes how your process samples and propagates. Prefer upgrading to 11, or use the Belvador SDK's otel option.

Python (sentry-sdk)#

OTLPIntegration (sentry-sdk 2.45.0 or later) makes errors use the active OpenTelemetry trace and span. By default it also installs an exporter to Sentry and replaces the global propagator; switch both off so your setup stays the owner:

Python
import sentry_sdkfrom sentry_sdk.integrations.otlp import OTLPIntegrationsentry_sdk.init(    dsn=os.environ["SENTRY_DSN"],    integrations=[        OTLPIntegration(            setup_otlp_traces_exporter=False,            setup_propagator=False,        ),    ],    # no traces_sample_rate)

Install it with pip install "sentry-sdk[opentelemetry-otlp]". The older instrumenter="otel" with SentrySpanProcessor and OpenTelemetryIntegration are deprecated.

Go (sentry-go)#

sentryotel.NewOtelIntegration() (sentry-go 0.45.0 or later) resolves trace ids from a Go context.Context — Go has no implicit active span, so the context must reach the capture call:

go
sentry.Init(sentry.ClientOptions{    Dsn: os.Getenv("SENTRY_DSN"),    Integrations: func(in []sentry.Integration) []sentry.Integration {        return append(in, sentryotel.NewOtelIntegration())    },})// ctx carries the request's span (from otelhttp)hub := sentry.GetHubFromContext(ctx)if hub == nil {    hub = sentry.CurrentHub()}hub.Client().CaptureException(err, &sentry.EventHint{Context: ctx}, hub.Scope())

A bare sentry.CaptureException(err) does not see the span. NewSentrySpanProcessor was deprecated in 0.45.0 and removed in 0.47.0.

Java (sentry-java)#

With the plain OpenTelemetry Java agent owning spans, add the sentry-opentelemetry-otlp module (8.34.0 or later) and its event processor:

java
Sentry.init(options -> {    options.setDsn(System.getenv("SENTRY_DSN"));    options.addEventProcessor(new OpenTelemetryOtlpEventProcessor());    // no tracesSampleRate});

Do not also attach sentry-opentelemetry-agent: it is a second agent with its own instrumentation. SENTRY_AUTO_INIT=false only stops that agent from initialising Sentry; it is not an errors-only switch.

What Belvador has run#

The Belvador SDK's otel bridge is tested, and so is each Sentry setup above. In every case a small HTTP service with OpenTelemetry owning spans keeps the Sentry SDK for errors exactly as shown, sends one error per request to Belvador, and the error sits on the request's span; without the integration (or event processor) the same error lands outside the trace:

SDKTested withResult
Node.js (@sentry/node)node 22.23.4, @opentelemetry/sdk-node 0.300.0, @opentelemetry/auto-instrumentations-node 0.82.0, pino 10.4.0, @sentry/node 11.8.0Error on the request's span (2026-10-25)
Python (sentry-sdk)python 3.12.15, opentelemetry-distro 0.66b1, opentelemetry-sdk 1.45.1, flask 3.1.3, sentry-sdk 2.72.0Error on the request's span (2026-10-25)
Go (sentry-go)go 1.27.3, go.opentelemetry.io/otel v1.47.0, otelhttp v0.72.0, otelslog v0.21.0, sentry-go v0.50.0, sentry-go/otel v0.50.0Error on the request's span (2026-10-25)
Java (sentry-java)java 21.0.12.1, opentelemetry-javaagent 2.32.0, sentry-java 8.61.0, sentry-opentelemetry-otlp 8.61.0, opentelemetry-api 1.66.0Error on the request's span (2026-10-25)

The capability table lists them under Trace headers. With Maven or Gradle the Java module brings opentelemetry-api and its dependencies; on a hand-built classpath, opentelemetry-context also needs opentelemetry-common. Check one error on its trace after setting up: the issue's Trace link should open the request's waterfall.

Spotted something wrong or missing? Tell us: hello@belvador.dev