Skip to content

feat(core): Add deferred segment-span transaction capture#21839

Open
andreiborza wants to merge 20 commits into
ab/sentry-trace-provider-otelfrom
ab/sentry-trace-provider-core-capture
Open

feat(core): Add deferred segment-span transaction capture#21839
andreiborza wants to merge 20 commits into
ab/sentry-trace-provider-otelfrom
ab/sentry-trace-provider-core-capture

Conversation

@andreiborza

@andreiborza andreiborza commented Jun 29, 2026

Copy link
Copy Markdown
Member

What

Adds the ability to defer the assembly of transactions to avoid dropping spans from transactions that end shortly after the segment span itself. Additionally, it also handles children that end after the debounce fired and transactions have already been sent. Spans that don't quite make it will end up as their own transaction in the same trace instead of being dropped .

This mimics what is already done today in the span exporter (a buffer + debounced flush).

Why

SentrySpan assembles a transaction synchronously from the span tree the instant the segment span ends. But some child spans are closed by their instrumentation after the root ends.

For example:

  • Same tick: diagnostics-channel instrumentations (HTTP, undici) end the child in an asyncEnd/response callback that runs after the root handler returns.
  • Later tick: some instrumentations replay spans asynchronously, notably @prisma/instrumentation emits its engine spans on a later tick once it receives the engine trace data.

Without deferral those children aren't in the tree yet at root-end, so they're silently dropped from the transaction. With the OTel SDK, this never happened because the SentrySpanExporter already buffers finished spans and flushes on a debounced timer. The SentryTracerProvider has no exporter, so defer reinstates that buffering window so late-ending children land before the snapshot.

  • Orphan emission handles the tail: a child that ends after the debounce fired and the transaction was already sent can't join it, so it is emitted as its own transaction in the same trace instead of being dropped (mirroring the exporter).

This is used and tested in #21680's integration/e2e tests.

Comment thread packages/core/src/tracing/sentrySpan.ts
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 27.48 kB - -
@sentry/browser - with treeshaking flags 25.91 kB - -
@sentry/browser (incl. Tracing) 46.19 kB +0.4% +184 B 🔺
@sentry/browser (incl. Tracing + Span Streaming) 47.94 kB +0.39% +182 B 🔺
@sentry/browser (incl. Tracing, Profiling) 50.97 kB +0.35% +174 B 🔺
@sentry/browser (incl. Tracing, Replay) 85.45 kB +0.23% +189 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 75.05 kB +0.27% +197 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 90.14 kB +0.21% +182 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 102.81 kB +0.2% +197 B 🔺
@sentry/browser (incl. Feedback) 44.66 kB - -
@sentry/browser (incl. sendFeedback) 32.26 kB - -
@sentry/browser (incl. FeedbackAsync) 37.4 kB - -
@sentry/browser (incl. Metrics) 28.54 kB - -
@sentry/browser (incl. Logs) 28.78 kB - -
@sentry/browser (incl. Metrics & Logs) 29.47 kB - -
@sentry/react 29.27 kB - -
@sentry/react (incl. Tracing) 48.5 kB +0.39% +185 B 🔺
@sentry/vue 32.86 kB +0.62% +201 B 🔺
@sentry/vue (incl. Tracing) 48.1 kB +0.47% +223 B 🔺
@sentry/svelte 27.5 kB - -
CDN Bundle 29.89 kB +0.03% +8 B 🔺
CDN Bundle (incl. Tracing) 48.18 kB +0.49% +231 B 🔺
CDN Bundle (incl. Logs, Metrics) 31.44 kB +0.01% +1 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) 49.48 kB +0.37% +178 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 70.78 kB +0.01% +6 B 🔺
CDN Bundle (incl. Tracing, Replay) 85.66 kB +0.24% +203 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 86.92 kB +0.23% +191 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 91.44 kB +0.22% +197 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 92.7 kB +0.23% +206 B 🔺
CDN Bundle - uncompressed 88.95 kB - -
CDN Bundle (incl. Tracing) - uncompressed 145.75 kB +0.39% +566 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 93.65 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 149.72 kB +0.38% +566 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 218.63 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 264.76 kB +0.22% +566 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 268.72 kB +0.22% +566 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 278.46 kB +0.21% +567 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 282.41 kB +0.21% +567 B 🔺
@sentry/nextjs (client) 50.91 kB +0.39% +196 B 🔺
@sentry/sveltekit (client) 46.59 kB +0.4% +185 B 🔺
@sentry/core/server 78.06 kB +0.5% +383 B 🔺
@sentry/core/browser 64.35 kB +0.6% +382 B 🔺
@sentry/node-core 61.68 kB +0.43% +264 B 🔺
@sentry/node 123.03 kB +0.23% +278 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 69.95 kB - -
@sentry/node/light 50.61 kB +0.45% +222 B 🔺
@sentry/node - without tracing 73.45 kB +0.43% +311 B 🔺
@sentry/aws-serverless 84.33 kB +0.37% +308 B 🔺
@sentry/cloudflare (withSentry) - minified 181.09 kB +0.35% +625 B 🔺
@sentry/cloudflare (withSentry) 448.21 kB +0.39% +1.74 kB 🔺

View base workflow run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1397906. Configure here.

Comment thread packages/core/src/tracing/sentrySpan.ts
Comment thread packages/core/src/tracing/sentrySpan.ts
@andreiborza andreiborza changed the title feat(core): Add deferred segment-span transaction capture, orphan emission, and provider-span sealing feat(core): Add deferred segment-span transaction capture Jun 29, 2026
Comment thread packages/core/src/tracing/sentrySpan.ts
Add a minimal OpenTelemetry `TracerProvider` that creates native Sentry spans
instead of bridging through the full OTel SDK.
A root span with no parent and no remote (incoming) parent previously continued
the scope's propagation context, so manually-started parallel root spans in the
same scope all collapsed into a single shared trace. The OpenTelemetry SDK
instead mints a fresh trace id per such root span.

Wrap the no-parent branch of `_startSentrySpan` in `startNewTrace` (matching the
existing `options.root` branch) so each parentless root span gets its own trace.
Incoming traces are unaffected, since `continueTrace` sets a remote parent and
takes the `_startRootSpanWithRemoteParent` branch instead.
…race

When `SentryTracer` continues a remote trace whose incoming headers carried no
baggage, `_startRootSpanWithRemoteParent` froze a derived-but-incomplete dynamic
sampling context (missing `sample_rand` and `transaction`) onto the span, which
then propagated downstream.

Only freeze the DSC when the remote parent actually carried one (its trace state
has the `sentry.dsc` key); otherwise leave it unset so it is derived dynamically
from the span, matching the OpenTelemetry SDK path, which never freezes the DSC
there and resolves it lazily (picking up `transaction` and `sample_rand`).
@andreiborza andreiborza force-pushed the ab/sentry-trace-provider-otel branch from a1613c2 to 6bd79ac Compare June 29, 2026 15:46
@andreiborza andreiborza requested a review from a team as a code owner June 29, 2026 15:46
@andreiborza andreiborza requested review from JPeer264 and mydea and removed request for a team June 29, 2026 15:46
Add per-client deferral of the segment-span transaction capture. The transaction is
otherwise assembled synchronously from the live span tree when the root span ends,
dropping child spans whose instrumentation closes them after it - in the same tick
(diagnostics-channel `asyncEnd`) or on a later tick (e.g. prisma engine spans). When a
client opts in via `_INTERNAL_setDeferSegmentSpanCapture`, a debounced timer (the one the
OpenTelemetry span exporter uses) delays the snapshot so those children land first, and
drains on the client `flush` hook so `Sentry.flush()` / `close()` stays safe. The browser
keeps its synchronous capture.

The opt-in call is wired separately (the Node SDK enables it on the SentryTracerProvider path).
@andreiborza andreiborza force-pushed the ab/sentry-trace-provider-core-capture branch from 29ce501 to c741940 Compare June 29, 2026 15:46
@andreiborza andreiborza force-pushed the ab/sentry-trace-provider-otel branch from 6bd79ac to e005612 Compare June 29, 2026 21:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant