feat(server-utils): Add otlpIntegration to connect Sentry to an existing OpenTelemetry setup - #23099
Conversation
…sting OpenTelemetry setup Adds `otlpIntegration()` and `getOtlpTracesEndpoint()` to `@sentry/server-utils`, re-exported from the server SDKs so no extra install or import is needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
size-limit report 📦
|
Logs, metrics and check-ins already flow through the same trace context as errors. Assert it end-to-end and say so in the integration's docs.
OpenTelemetry returns a span wrapping INVALID_SPAN_CONTEXT when tracing is suppressed or when a span is started before a tracer provider is registered. Its all-zero ids were being stamped onto everything Sentry sends, breaking trace linkage instead of falling back to the Sentry scope.
Astro's server entry cannot `export * from '@sentry/node'` (Vite moves the exports onto `default` in prod builds), so it enumerates them. The node-exports-test-app E2E check caught the gap.
Fills the TODO left in the OpenTelemetry interoperability section, and covers the migration for users of the v10 `@sentry/node-core/light/otlp` integration, whose options and built-in exporter setup are gone.
Adds entries under Removed APIs for the dropped `@sentry/node-core/light/otlp` entry point and the removed `setupOtlpTracesExporter` / `collectorUrl` options, and under Renames for the integration name change. The narrative section now links to both instead of repeating them.
Lms24
left a comment
There was a problem hiding this comment.
I like the clean implementation and user-facing setup of this. IMHO much nicer than the intertwined OTel setup users had to go through before.
Q: What happens to the DSC / trace envelope header? I believe it should still be constructed from the scope in combination with the external propagation context, correct? Maybe we can assert on this in the test?
With an external propagation context active, events are stamped with the OpenTelemetry trace id while the DSC was still built from the Sentry scope, so every envelope header named a trace that appeared nowhere else. There is no transaction semantic to describe the OpenTelemetry trace with, so send no sampling context at all, matching sentry-python.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5f3b902. Configure here.
The TwP placeholder path spread the scope DSC, which is now undefined while an external propagation
context is active, so it collapsed to an empty object and emitted a `trace: {}` envelope header. A
Sentry span means we are head of its trace, so fall through and derive the DSC from the span.
Lms24
left a comment
There was a problem hiding this comment.
Good to ship from my end, given there's precedence for the trace header omission. As discussed offline, a quick cross-check with Python/Ingest on omitting the trace header would be appreciated 🙏
| // OpenTelemetry span), the SDK is not the head of the trace and has no transaction semantics to | ||
| // describe it with, so there is no sampling context to send. The scope's own DSC would name a |
There was a problem hiding this comment.
l: I would probably rephrase this to something like
| // OpenTelemetry span), the SDK is not the head of the trace and has no transaction semantics to | |
| // describe it with, so there is no sampling context to send. The scope's own DSC would name a | |
| // OpenTelemetry span), the SDK lacks most of the DSC information, like sampled, sample_rate, sample_rand and transaction. |
but there might also be other reasons depending on Python's reasoning
…#23149) ## What Restructures the "Better OpenTelemetry interoperability" section of the v11 migration guide around the three setups users can actually run, so people can find the one that matches them instead of piecing it together from prose. - **Sentry only, OpenTelemetry ignored** (the new default): native Sentry spans, no tracer provider, `@opentelemetry/api` spans ignored. - **OpenTelemetry-compatible mode** (`skipOpenTelemetrySetup: false`): a minimal tracer provider picks up `@opentelemetry/api` spans and turns them into Sentry spans. Everything goes to Sentry and only to Sentry. - **Your own OpenTelemetry, Sentry linked to it**: Sentry tracing off, `otlpIntegration()` for trace linking, `getOtlpTracesEndpoint()` to point your exporter at Sentry. Also adds an "Avoiding duplicate spans" subsection covering what happens when Sentry's instrumentation overlaps with your own, and spells out that the v10 bridge built from `SentryContextManager`, `SentrySampler` and `SentrySpanProcessor` is gone. This replaces the prose that previously covered `skipOpenTelemetrySetup`, the removed bridge and the HTTP/fetch example, rather than adding alongside it. Everything unique from that block is preserved: the flipped default and per-SDK lists, and the v10 change where `skipOpenTelemetrySetup: true` no longer disables HTTP and fetch spans. ## Why The old section explained the mechanism but never said which configuration a given user wants, so the common questions (does Sentry pick up my OpenTelemetry spans, do I get duplicates, where did `SentrySpanProcessor` go) had to be inferred. Naming the three setups makes that a lookup. Note this touches the same region as #23099, which adds its own OTLP integration section, so one of the two will need a rebase. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Nicolas Hrubec <nicolas.hrubec@outlook.com> Co-authored-by: Charly Gomez <charly.gomez1310@gmail.com> Co-authored-by: Lukas Stracke <lukas.stracke@sentry.io>

What
Adds
otlpIntegration()to@sentry/server-utils, re-exported from the server SDKs so it works with no extra install and no extra import.Sentry.otlpIntegration()takes no options. Everything Sentry sends that carries trace information (errors, logs, metrics, check-ins) is attached to whatever OpenTelemetry span is active, so it lands on the same trace as the spans your own OpenTelemetry SDK exports.Sentry.getOtlpTracesEndpoint(dsn)returns the URL and auth headers for Sentry's OTLP traces endpoint, to configure your ownOTLPTraceExporterwith.export * from '@sentry/node'.node-express-otlpE2E app covering a real OpenTelemetry Node SDK setup end to end.The integration does not set up a span exporter, span processor, or tracer provider. Users keep full ownership of their OpenTelemetry pipeline. Outgoing request propagation is left to their OpenTelemetry propagator, and an active Sentry span still takes precedence, so this only changes behavior when Sentry has no span of its own.
Why
Users running their own OpenTelemetry setup had no supported way to correlate Sentry telemetry with their OpenTelemetry traces. The integration lives in
@sentry/server-utilsbecause it is the shared base for every server SDK and no browser SDK, so@opentelemetry/apireaches exactly the packages that can use it and never@sentry/browser. Putting it in@sentry/corewas rejected: core is not a package users depend on directly, so@sentry/core/otlpwould not resolve under pnpm, and core would own an@opentelemetry/apiversion range on behalf of every browser and edge user.Closes: #23082