feat(node)!: Default most SDKs to a no-tracer-provider setup - #22957
Merged
Conversation
Flip the default OpenTelemetry setup for server SDKs: most now run without a Sentry tracer provider, using an AsyncLocalStorage context strategy for scope isolation. Reuses the existing `skipOpenTelemetrySetup` option with a flipped default (true for node/cloudflare, false for nextjs/sveltekit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…er-provider # Conflicts: # packages/cloudflare/src/sdk.ts
Contributor
size-limit report 📦
|
In the no-provider default, setupEventContextTrace read the OpenTelemetry active span before scope data was applied, so a user's own OTel span could override the Sentry trace on error events. Only set up this hook when Sentry owns the provider.
Assert the actual user-visible effect: a foreign OpenTelemetry active span must not override the Sentry trace on error events in the no-provider default, and is adopted when the tracer provider is enabled. Verified to fail without the gating.
Channel-based instrumentation stamps OTel semantic attributes on native spans but leaves the Sentry-convention fields (e.g. sentry.op) to be inferred by the provider pipeline. Without a tracer provider that inference never ran, so outgoing http/fetch and other channel spans were half-formed. Run the same applyOtelSpanData / backfillStreamedSpanDataFromOtel hooks via the client in no-provider mode.
tracer-start-active-span-error drives the raw OpenTelemetry tracer, and http-otel-double-instrumentation exercises coexistence with a user-owned OTel HttpInstrumentation whose spans reach Sentry via the tracer provider. Both require the provider, so they opt into it explicitly under the new no-provider default.
A trace continued without incoming baggage freezes an empty DSC. Reading it back short-circuited before sample_rand was added, so downstream requests in no-provider mode propagated baggage without sentry-sample_rand. Backfill it from the captured scope's propagation context, matching what the OTel span sampler writes to trace state.
In no-provider mode the AsyncLocalStorage tracing-channel binding planted every channel span as the active span, including ignored ones. Children and outgoing requests then propagated from the ignored span, dropping the continued positive sampling decision. Skip ignored spans so propagation falls back to the nearest emitted parent, matching the OTel context manager.
The previous approach backfilled sample_rand in getDynamicSamplingContextFromSpan, which also ran in OTel mode and wrongly added sample_rand to remote-parent DSCs (breaking opentelemetry unit tests). Move the backfill to the root-span freeze site and gate it on a genuinely empty DSC, so only continued traces without incoming baggage get sample_rand and populated frozen DSCs are left untouched. Also opt the Cloudflare Vercel AI v6 integration test into the tracer provider: the AI SDK emits spans via @opentelemetry/api, which need the provider to be captured.
The `otel` transaction context carries the OpenTelemetry SDK resource attributes, which are only set when Sentry owns the tracer provider. Under the no-provider default these Astro server SDKs no longer emit it, so the assertion is removed.
The event-loop-block watchdog reads the active scope out of the client's `asyncLocalStorageLookup`, which was only populated when the OpenTelemetry context strategy was set up. Without a tracer provider it was undefined, so ANR events fell back to the global scope and dropped per-isolation-scope user data and breadcrumbs. Return the AsyncLocalStorage from `setAsyncLocalStorageAsyncContextStrategy` and set `asyncLocalStorageLookup` in the no-provider branch. The lookup now carries a generic `stateLookup` key path (empty for the ALS store, which already is the scopes object; `['_currentContext', ...]` for the OTel context) instead of an OTel-specific symbol.
Continuing a trace without an incoming Sentry DSC now folds the scope's sample_rand into the propagated (otherwise empty) DSC so downstream sampling stays consistent. Update the sveltekit handle and browser tracing tests that asserted a strictly empty DSC in that case.
The server middleware index counter was stored on the OpenTelemetry context, so it reset to 0 on every middleware under the no-provider default (no context propagation without a tracer provider), producing indices like [0,0,0]. Key it by the incoming Request in a WeakMap instead, mirroring the client instrumentation, so it works in both modes.
Keying the per-request middleware counter by the incoming Request did not increment in the real server (each middleware hook sees a distinct request object), so indices stayed [0,0,0]. Key by the request's root span instead, which is the single stable transaction all of a request's middlewares run under, in both provider modes.
…er-provider # Conflicts: # packages/browser/test/tracing/browserTracingIntegration.test.ts # packages/core/src/asyncContext/tracing-channel-binding.ts # packages/sveltekit/test/server-common/handle.test.ts
Channel-based spans need the Sentry-convention backfill in every mode, so run it unconditionally in _init instead of only in the no-provider branch. This also covers the case where the provider fails to register (setupOtel early-returns), which previously skipped backfill. setupOtel no longer needs the client argument.
initCloudflareSentryHandle inherited Cloudflare's new no-provider default, so Kit-emitted OpenTelemetry spans (and svelteKitSpansIntegration) were no longer captured on Cloudflare. Default skipOpenTelemetrySetup to false here, matching the Node SvelteKit SDK.
The pure AsyncLocalStorage store already is the `{ scope, isolationScope }` object, so
its lookup needs no key path. Make `stateLookup` optional on `AsyncLocalStorageLookup`
and drop the explicit empty array; only the OTel context strategy supplies a path.
Keep the original `contextSymbol` field on `AsyncLocalStorageLookup` and just make it optional: the OpenTelemetry strategy still supplies it, and the pure AsyncLocalStorage strategy omits it. node-native builds the `_currentContext` path only when it is present.
The docs implied `false` picks up spans from a user's own tracer provider. It does not: it registers Sentry's `SentryTracerProvider` as the global provider, so spans created via `@opentelemetry/api` become Sentry spans. Users running their own tracer provider should keep `skipOpenTelemetrySetup: true`. Fixed the JSDoc and MIGRATION.md.
The SDK no longer ships a SentrySpanProcessor or other components to route a user's own OpenTelemetry spans into Sentry, so spans from a user-owned tracer provider stay in their pipeline and are not sent to Sentry. Remove the stale example and clarify this in the option JSDoc and MIGRATION.md.
andreiborza
marked this pull request as ready for review
August 5, 2026 10:02
andreiborza
requested review from
a team,
JPeer264,
Lms24,
chargome,
mydea,
nicohrubec and
s1gr1d
and removed request for
a team,
mydea and
s1gr1d
August 5, 2026 10:02
andreiborza
marked this pull request as draft
August 5, 2026 10:25
Member
Author
|
Drafting because I want to add some e2e test variants that ensure apps still work when opting into setting up a tracer provider via |
Guard the opt-in `skipOpenTelemetrySetup: false` path so the flipped default does not silently break it. Add a `sentryTest.variants` entry to node-express, node-fastify-5, nestjs-basic and nuxt-4 that re-runs each app with the Sentry OpenTelemetry tracer provider enabled via an `E2E_TEST_OTEL_SETUP` env flag read in `Sentry.init`. The shared transaction tests assert the `otel` context is present only in that variant.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2b120b5. Configure here.
`contexts.otel` was removed in v11 (#23013), so the assertion would always fail. The variant's value is re-running the full app with the provider registered and confirming the existing assertions still pass, so no extra provider-specific assertion is needed.
andreiborza
marked this pull request as ready for review
August 5, 2026 10:59
JPeer264
approved these changes
Aug 5, 2026
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What
Flip the default OpenTelemetry setup for server SDKs:
@sentry/node(and inheritors) and@sentry/cloudflarenow default toskipOpenTelemetrySetup: true(no tracer provider; scopes isolated via AsyncLocalStorage, spans still emitted via corestartSpan), while@sentry/nextjsand@sentry/sveltekitdefault tofalsesince they rely on OpenTelemetry span pickup.Why
Most SDKs do not need a tracer provider to produce spans, so defaulting to none lets users run their own OpenTelemetry cleanly alongside Sentry without Sentry spans leaking into their pipeline.
Closes: #22486