Skip to content

feat(core): Apply ignoreSpans to streamed spans#19934

Merged
Lms24 merged 14 commits intolms/feat-span-firstfrom
lms/feat-core-ignorespans-streaming
Apr 2, 2026
Merged

feat(core): Apply ignoreSpans to streamed spans#19934
Lms24 merged 14 commits intolms/feat-span-firstfrom
lms/feat-core-ignorespans-streaming

Conversation

@Lms24
Copy link
Copy Markdown
Member

@Lms24 Lms24 commented Mar 23, 2026

This PR Implements applying the ignoreSpans option to streamed spans (previously this option had no effect). It covers both, our core/browser- as well as the OTel/Node-based implementation.

Behaviour on all implementations:

  • ignoring a segment span ignores the entire span tree
    • we record an ignored client outcome for each dropped span (segment + children)
  • ignoring a child span only ignores the child. Potential grandchildren are naturally parented to the dropped span's parent. No post-creation reparenting logic is used anymore.
    • we record an ignored client outcome for the dropped child
  • in both cases, for ignored spans, NonRecording spans are started instead of the filtered span so that a span in a callback can always be accessed as we do with sampling.
  • this PR also adds sample_rate client report outcomes for negatively sampled child spans. Meaning, when our sampling logic makes a negative decision, we record outcomes for the segment span + every created child span of the negatively sampled segment. This is additional logic to ignoreSpans but we need to differentiate between outcome reason (ignored vs. sample_rate), so I had to add this to this PR. This is expected behaviour and something we would have needed to do for span streaming anyway.

Implementation Node/OTel:

  • We use the SentrySampler to effectively make sampling decisions for ignoreSpans prior to applying our actual sampling options. So to be clear: There's no actual sampling logic applied but using the sampler allows us to modify the OTel context, which we can then later use to correctly either drop all spans of a segment or just one span (see above). We do this by adding flags to the traceState, which the context manager picks up and changes its context accordingly.

Implementation Core/Browser:

  • We apply ignoreSpans right on the start*span* APIs, prior to sampling.
  • We store a dropReason on the SentrySpan class that allows us to report the correct client outcome for dropped child spans, both for sampling as well as ignoreSpans

@Lms24 Lms24 changed the base branch from develop to lms/feat-span-first March 23, 2026 13:41
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 23, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Core

  • Apply ignoreSpans to streamed spans by Lms24 in #19934
  • Support embedding APIs in google-genai by nicohrubec in #19797

Deps

  • Bump babel-loader from 10.0.0 to 10.1.1 by dependabot in #19997
  • Bump handlebars from 4.7.7 to 4.7.9 by dependabot in #20008

Nuxt

  • Add middleware instrumentation compatibility for Nuxt 5 by s1gr1d in #19968
  • Support parametrized SSR routes in Nuxt 5 by s1gr1d in #19977

Other

  • (browser) Replace element timing spans with metrics by logaretm in #19869
  • (bun) Add bunRuntimeMetricsIntegration by chargome in #19979
  • (node) Add nodeRuntimeMetricsIntegration by chargome in #19923
  • (node-core) Add OTLP integration for node-core/light by andreiborza in #19729
  • (solid) Add route parametrization for Solid Router by andreiborza in #20031
  • Span Streaming (WIP) by Lms24 in #19119

Bug Fixes 🐛

Ci

  • Update validate-pr action to remove draft enforcement by stephanie-anderson in #20037
  • Update validate-pr action to remove draft enforcement by stephanie-anderson in #20035

Other

  • (core) Guard nullish response in supabase PostgREST handler by antonis in #20033
  • (e2e) Pin @opentelemetry/api to 1.9.0 in ts3.8 test app by logaretm in #19992
  • (node) Ensure startNewTrace propagates traceId in OTel environments by logaretm in #19963
  • (nuxt) Use virtual module for Nuxt pages data (SSR route parametrization) by s1gr1d in #20020
  • (opentelemetry) Convert seconds timestamps in span.end() to milliseconds by logaretm in #19958

Documentation 📚

  • (release) Update publishing-a-release.md by nicohrubec in #19982

Internal Changes 🔧

Core

  • Introduce instrumented method registry for AI integrations by nicohrubec in #19981
  • Consolidate getOperationName into one shared utility by nicohrubec in #19971

Deps

  • Bump amqplib from 0.10.7 to 0.10.9 by dependabot in #20000
  • Bump actions/upload-artifact from 6 to 7 by dependabot in #19569
  • Bump srvx from 0.11.12 to 0.11.13 by dependabot in #20001
  • Bump @apollo/server from 5.4.0 to 5.5.0 by dependabot in #20007

Deps Dev

  • Remove esbuild override in astro-5-cf-workers E2E test by isaacs in #20024
  • Bump node-forge from 1.3.2 to 1.4.0 by dependabot in #20012
  • Bump yaml from 2.8.2 to 2.8.3 by dependabot in #19985

Other

  • (browser) Reduce browser package bundle size by HazAT in #19856
  • (deno) Expand Deno E2E test coverage by chargome in #19957
  • (e2e) Add e2e tests for nodeRuntimeMetricsIntegration by chargome in #19989

🤖 This preview updates automatically when you update the PR.

@Lms24 Lms24 force-pushed the lms/feat-span-first branch from 5963170 to de2e194 Compare March 30, 2026 11:47
@Lms24 Lms24 force-pushed the lms/feat-core-ignorespans-streaming branch from e90d6d3 to 11cfe2b Compare March 30, 2026 11:48
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 30, 2026

size-limit report 📦

Path Size % Change Change
@sentry/browser 25.72 kB added added
@sentry/browser - with treeshaking flags 24.21 kB added added
@sentry/browser (incl. Tracing) 42.69 kB added added
@sentry/browser (incl. Tracing, Profiling) 47.31 kB added added
@sentry/browser (incl. Tracing, Replay) 81.51 kB added added
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 71.08 kB added added
@sentry/browser (incl. Tracing, Replay with Canvas) 86.22 kB added added
@sentry/browser (incl. Tracing, Replay, Feedback) 98.42 kB added added
@sentry/browser (incl. Feedback) 42.5 kB added added
@sentry/browser (incl. sendFeedback) 30.38 kB added added
@sentry/browser (incl. FeedbackAsync) 35.36 kB added added
@sentry/browser (incl. Metrics) 27.03 kB added added
@sentry/browser (incl. Logs) 27.17 kB added added
@sentry/browser (incl. Metrics & Logs) 27.85 kB added added
@sentry/react 27.48 kB added added
@sentry/react (incl. Tracing) 45.01 kB added added
@sentry/vue 30.56 kB added added
@sentry/vue (incl. Tracing) 44.55 kB added added
@sentry/svelte 25.74 kB added added
CDN Bundle 28.38 kB added added
CDN Bundle (incl. Tracing) 43.68 kB added added
CDN Bundle (incl. Logs, Metrics) 29.75 kB added added
CDN Bundle (incl. Tracing, Logs, Metrics) 44.77 kB added added
CDN Bundle (incl. Replay, Logs, Metrics) 68.56 kB added added
CDN Bundle (incl. Tracing, Replay) 80.56 kB added added
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 81.59 kB added added
CDN Bundle (incl. Tracing, Replay, Feedback) 86.1 kB added added
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 87.13 kB added added
CDN Bundle - uncompressed 82.86 kB added added
CDN Bundle (incl. Tracing) - uncompressed 129.5 kB added added
CDN Bundle (incl. Logs, Metrics) - uncompressed 87 kB added added
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 132.91 kB added added
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 209.99 kB added added
CDN Bundle (incl. Tracing, Replay) - uncompressed 246.38 kB added added
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 249.78 kB added added
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 259.29 kB added added
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 262.68 kB added added
@sentry/nextjs (client) 47.44 kB added added
@sentry/sveltekit (client) 43.16 kB added added
@sentry/node-core 58.64 kB added added
@sentry/node 175.99 kB added added
@sentry/node - without tracing 98.47 kB added added
@sentry/aws-serverless 115.95 kB added added

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 30, 2026

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 9,038 - - added
GET With Sentry 1,623 18% - added
GET With Sentry (error only) 5,873 65% - added
POST Baseline 1,182 - - added
POST With Sentry 568 48% - added
POST With Sentry (error only) 1,040 88% - added
MYSQL Baseline 3,155 - - added
MYSQL With Sentry 361 11% - added
MYSQL With Sentry (error only) 2,539 80% - added

@Lms24 Lms24 marked this pull request as ready for review March 31, 2026 11:31
{
category: 'transaction',
quantity: 4,
category: 'span',
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

a couple of pre-existing tests (like this one) needed adjustments because they previously reported the wrong telemetry type (for span streaming). Also the number of dropped spans increased because we now also count child spans.

dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [Sentry.spanStreamingIntegration()],
ignoreSpans: [/ignore/],
parentSpanIsAlwaysRootSpan: false,
Copy link
Copy Markdown
Member Author

@Lms24 Lms24 Mar 31, 2026

Choose a reason for hiding this comment

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

worth noting: For this test, I enabled parentSpanIsAlwaysRootSpan which due to the lack of async context is our default behaviour in browser. I disabled it here to ensure that the parenting order in the test (see file below) works as expected in core/browser.

Copy link
Copy Markdown

@cursor cursor bot left a comment

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 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Core path reads op from wrong property for ignore check
    • Fixed _shouldIgnoreStreamedSpan to check spanArguments.op before spanArguments.attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP], aligning with the OTel implementation and ensuring op-based ignoreSpans patterns work correctly in the core/browser streaming path.

Create PR

Or push these changes by commenting:

@cursor push 6b34ec41de
Preview (6b34ec41de)
diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts
--- a/packages/core/src/tracing/trace.ts
+++ b/packages/core/src/tracing/trace.ts
@@ -594,7 +594,10 @@
   }
 
   return shouldIgnoreSpan(
-    { description: spanArguments.name || '', op: spanArguments.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] },
+    {
+      description: spanArguments.name || '',
+      op: spanArguments.op || spanArguments.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP],
+    },
     ignoreSpans,
   );
 }

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Copy link
Copy Markdown
Member

@isaacs isaacs left a comment

Choose a reason for hiding this comment

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

AIUI, it looks like this dodges the issue of "don't have all the data to decide to ignore spans until they're already started, which is too late to ignore them" by making the decision limited to description and op? If so, lgtm, pending investigating/resolving the nit from cursor about reading from the attributes vs the settings object directly.

@Lms24
Copy link
Copy Markdown
Member Author

Lms24 commented Apr 1, 2026

The Bugbot review was partially correct. We did indeed ignore sentry.op attributes, so I made sure we pass them in correctly. Also adjusted the tests to have this covered. However, we should always prioritize sentry.op (the attribute) over the top-level op param, especially in span streaming. Our plan is to remove the op property from the SpanContext so that we/users only rely on the attribute in the future.

@Lms24
Copy link
Copy Markdown
Member Author

Lms24 commented Apr 1, 2026

by making the decision limited to description and op?

The ignoreSpans spec specifies that ignoreSpan MAY be extended to allow filtering on any attribute. We already implemented ignoreSpans prior to this spec change for transactions and used name/description and op. We can extend this pretty easily later on but for now I wanted to establish parity with the existing fields for span streaming. Thanks for asking though, I'll update the PR description to point this out.

Even description/name and op could still change later on fwiw, name being the much more likely candidate here 😅
Applying ignoreSpans any time before span end is far from ideal IMHO but it's what we settled on. As long as the Sentry backend doesn't allow reparenting (either in the buffer or by mutating stored data, or at query time), we need to ensure that we maintain a correct parent/child relationship in the SDK.

Comment on lines +113 to +116
if (!parentSampled) {
const parentSegmentIgnored = parentContext?.traceState?.get(SENTRY_TRACE_STATE_SEGMENT_IGNORED) === '1';
this._client.recordDroppedEvent(parentSegmentIgnored ? 'ignored' : 'sample_rate', 'span');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The check !parentSampled incorrectly treats a traceId mismatch (undefined) the same as an unsampled parent (false), leading to incorrect client outcome reporting for dropped spans.
Severity: MEDIUM

Suggested Fix

Change the condition from if (!parentSampled) to a strict equality check, if (parentSampled === false). This correctly distinguishes between a parent that was explicitly not sampled and a parent with a mismatched trace ID. Alternatively, handle the undefined case separately to record a more accurate outcome.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: packages/opentelemetry/src/sampler.ts#L113-L116

Potential issue: In the OTel sampler, when span streaming is enabled, the
`getParentSampled()` function can return `undefined` if a parent span's `traceId` does
not match the child's. The conditional check `if (!parentSampled)` incorrectly evaluates
to `true` for this `undefined` case, treating it the same as a `false` (unsampled) case.
This causes a `sample_rate` client outcome to be recorded for spans dropped due to a
trace ID mismatch, which is a trace integrity issue, not a sampling decision. This
corrupts client outcome metrics by misattributing the reason for the dropped span.

Copy link
Copy Markdown

@cursor cursor bot left a comment

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 1 potential issue.

Fix All in Cursor

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

shouldIgnoreSpan(
{ description: inferredSpanName, op: mergedAttributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] ?? op },
ignoreSpans,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Root span ignoreSpans check uses overridden op, not user's

Low Severity

For root/segment spans, the shouldIgnoreSpan call uses mergedAttributes[SEMANTIC_ATTRIBUTE_SENTRY_OP], but mergedAttributes may have had the user's explicit sentry.op attribute overwritten by the inferred op from inferSpanData (line 139). This means a user-provided sentry.op attribute on an HTTP/RPC/messaging root span is ignored for ignoreSpans filtering. The child span path correctly uses spanAttributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] ?? childOp, preserving the user's attribute. Using spanAttributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] ?? op here would be consistent and match the stated intent to prioritize the sentry.op attribute.

Additional Locations (1)
Fix in Cursor Fix in Web

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is fine, since it only concerns span-first. I've yet to deprecate the op property from the SpanContext interface in favour of directly setting the 'sentry.op' attribute.

@Lms24 Lms24 self-assigned this Apr 2, 2026
@Lms24 Lms24 merged commit 8613474 into lms/feat-span-first Apr 2, 2026
466 of 468 checks passed
@Lms24 Lms24 deleted the lms/feat-core-ignorespans-streaming branch April 2, 2026 13:49
Lms24 added a commit that referenced this pull request Apr 2, 2026
This PR Implements applying the `ignoreSpans` option to streamed spans
(previously this option had no effect). It covers both, our
core/browser- as well as the OTel/Node-based implementation.
See PR for details
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.

4 participants