Skip to content

feat(core): Add __SENTRY_EXCLUDE_TRANSACTIONS__ flag to tree-shake transaction code - #26144

Open
sentry-junior[bot] wants to merge 3 commits into
developfrom
feat/exclude-transactions-flag
Open

sentry-junior[bot] wants to merge 3 commits into
developfrom
feat/exclude-transactions-flag

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Why: Span streaming is the default in v11, but a streaming app still ships the code that builds and processes transaction events (_convertSpanToTransaction, the transaction branch of processBeforeSend, gen_ai extraction, transaction-event conversion helpers, the web vitals measurements branch). Runtime hasSpanStreamingEnabled(client) / traceLifecycle checks keep it alive. __SENTRY_TRACING__: false (#22835) only helps apps that don't trace at all.

This adds an opt-in build flag. Replacing __SENTRY_EXCLUDE_TRANSACTIONS__ with true means the SDK always streams spans: traceLifecycle: 'static' is ignored (normalized to 'stream', and server clients still add spanStreamingIntegration), and the transaction code paths become dead code. Segments are still sent as span envelopes. When the flag isn't defined, behavior is unchanged.

The guard sits inline at each call site, like __SENTRY_TRACING__. A shared module-level constant doesn't work: esbuild inlines it across modules too late to remove the dead branches, and doesn't inline it within a module that has imports.

Size (esbuild 0.27, minify, ESM, browser, __SENTRY_DEBUG__: false; BrowserClient + browserTracingIntegration, tracesSampleRate: 1, setCurrentClient, client.init()):

raw gzip brotli-11
develop 131,185 45,437 40,286
this PR, flag unset 131,791 (+606) 45,513 (+76) 40,331 (+45)
this PR, flag true 127,648 (−3,537) 44,358 (−1,079) 39,362 (−924)

With webpack via size-limit (init + browserTracingIntegration), the flag saves 1,405 B gzip (49,304 → 47,899). The new size-limit entry tracks it.

I ran an end-to-end check against the built packages: with the flag, a startSpan segment produces a span envelope item, including when traceLifecycle: 'static' is passed. Without the flag, behavior is unchanged (span for stream, transaction for static).

Left in:

  • Transaction bookkeeping in _processEvent (dropped-span outcomes, transaction_info source), which is small and still handles manually captured type: 'transaction' events
  • maybeWarnAboutIgnoredTransactionOptions
  • measurements in getSpanJSON/static span JSON
  • the hasSpanStreamingEnabled(client) ? … : … ternaries in integrations (span names, attribute keys), plus static branches in browser-utils web-vitals/spans.ts and interactions.ts
  • standalone span machinery
  • the Cloudflare client's own traceLifecycle !== 'static' check
  • a bundleSizeOptimizations.excludeTransactions option in the bundler plugins (left for a follow-up, once the flag name is settled)

How to review: Start with sentrySpan.ts and client.ts. excludeTransactions.test.ts covers the flag semantics.

Unsure about:

Refs #22580

via @BYK.

--

View Session in Sentry

…transaction code

With span streaming, the code that builds and processes transaction events is dead
but still ships. Replacing `__SENTRY_EXCLUDE_TRANSACTIONS__` with `true` makes the SDK
always stream spans and lets bundlers drop those paths. Defaults are unchanged.

Refs #22580

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
@sentry-junior
sentry-junior Bot requested review from a team as code owners November 5, 2026 13:30
@sentry-junior
sentry-junior Bot requested a review from a team as a code owner November 5, 2026 13:30
@sentry-junior
sentry-junior Bot requested review from Lms24, isaacs, logaretm, mydea and s1gr1d and removed request for a team November 5, 2026 13:30

@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 3 potential issues.

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 e6aa1e9. Configure here.


expect(client.getOptions().integrations.map(integration => integration.name)).toContain('SpanStreaming');
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Feature PR lacks integration tests

Low Severity

This feature PR only adds unit tests. Flagged because the review guidelines require at least one integration or E2E test for feat PRs. The new file covers flag semantics in isolation, but not a built-package path that proves a startSpan segment still becomes a span envelope item when the flag is true, including with traceLifecycle: 'static'.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit e6aa1e9. Configure here.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

size-limit report 📦

Compared: 4846463 (PR test merge using develop at 8c2bc97)

Against: 8c2bc97 (develop) · base run #65215

✅ Baseline matches: this diff contains only this PR's changes.

Path Size % Change Change
@sentry/browser 30.46 kB +0.2% +60 B 🔺
@sentry/browser - with treeshaking flags 28.6 kB +0.2% +55 B 🔺
@sentry/browser - with treeshaking flags tracing without tracing 28.49 kB +0.22% +60 B 🔺
@sentry/browser (incl. Tracing) 52.84 kB +0.23% +117 B 🔺
@sentry/browser (incl. Tracing + Span Streaming) 52.85 kB +0.24% +122 B 🔺
@sentry/browser (incl. Tracing, Profiling) 55.82 kB +0.22% +118 B 🔺
@sentry/browser (incl. Tracing, Replay) 93.15 kB +0.13% +114 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 81.74 kB +0.15% +117 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 97.89 kB +0.12% +110 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 110.88 kB +0.11% +111 B 🔺
@sentry/browser (incl. Feedback) 48.05 kB +0.14% +65 B 🔺
@sentry/browser (incl. sendFeedback) 35.57 kB +0.19% +64 B 🔺
@sentry/browser (incl. FeedbackAsync) 40.67 kB +0.16% +62 B 🔺
@sentry/browser (incl. Metrics) 31.49 kB +0.19% +58 B 🔺
@sentry/browser (incl. Logs) 31.78 kB +0.17% +53 B 🔺
@sentry/browser (incl. Metrics & Logs) 32.51 kB +0.2% +62 B 🔺
@sentry/react 32.35 kB +0.18% +58 B 🔺
@sentry/react (incl. Tracing) 55.28 kB +0.33% +179 B 🔺
@sentry/vue 38.8 kB +0.32% +123 B 🔺
@sentry/vue (incl. Tracing) 55.89 kB +0.27% +145 B 🔺
@sentry/svelte 30.49 kB +0.22% +66 B 🔺
@sentry/remix (Remix 3 client bundle) 57.94 kB +0.21% +120 B 🔺
CDN Bundle 32.2 kB - -
CDN Bundle (incl. Tracing) 53.26 kB -0.01% -1 B 🔽
CDN Bundle (incl. Logs, Metrics) 34.47 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 55.2 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 75.88 kB - -
CDN Bundle (incl. Tracing, Replay) 91.48 kB +0.01% +3 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 93.41 kB +0.01% +1 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 97.58 kB +0.01% +3 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 99.58 kB +0.01% +3 B 🔺
CDN Bundle - uncompressed 94.67 kB - -
CDN Bundle (incl. Tracing) - uncompressed 157.67 kB +0.01% +3 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 101.14 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 163.41 kB +0.01% +3 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 232.71 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 279.37 kB +0.01% +3 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 285.09 kB +0.01% +3 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 293.07 kB +0.01% +3 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 298.78 kB +0.01% +3 B 🔺
@sentry/nextjs (client) 58.18 kB +0.22% +127 B 🔺
@sentry/sveltekit (client) 53.35 kB +0.21% +107 B 🔺
@sentry/core/server 44.31 kB +0.15% +63 B 🔺
@sentry/core/browser 13.98 kB +0.39% +54 B 🔺
@sentry/node 157.06 kB +0.08% +112 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 82.28 kB - -
@sentry/node - without tracing 97.12 kB +0.1% +89 B 🔺
@sentry/node - without channel injection 137.4 kB +0.09% +116 B 🔺
@sentry/aws-serverless 105.51 kB +0.12% +121 B 🔺
@sentry/cloudflare (withSentry) - minified 201.37 kB +0.35% +692 B 🔺
@sentry/cloudflare (withSentry) 499.84 kB +0.16% +785 B 🔺
@sentry/nextjs/cloudflare (withSentry) - minified 223.37 kB +0.32% +692 B 🔺
@sentry/browser (incl. Tracing) - with treeshaking flags excluding transactions 47.9 kB added added

View base workflow run

…ns are excluded

With `__SENTRY_EXCLUDE_TRANSACTIONS__`, `traceLifecycle: 'static'` is ignored, so the
Cloudflare client must still add its own `spanStreamingIntegration` with
`flushOnSegmentEnd: false` instead of inheriting the default one from
`ServerRuntimeClient`. Also use the conventions constants in the web vitals test.
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Optional E2E tests

Some optional checks were not successful. They do not block the merge. 6 not successful, 50 successful. See the run.

Check Status
❌ CI: Optional E2E Tests / E2E nextjs-16-cacheComponents (cloudflare) Test (optional) Failing after 10m Details
❌ CI: Optional E2E Tests / E2E node-flue (latest) Test (optional) Failing after 36s Details
❌ CI: Optional E2E Tests / E2E cloudflare-pi-durable (latest) Test (optional) Failing after 50s Details
❌ CI: Optional E2E Tests / E2E node-eve (orchestrion) Test (optional) Failing after 2m Details
❌ CI: Optional E2E Tests / E2E node-eve (latest) Test (optional) Failing after 58s Details
❌ CI: Optional E2E Tests / E2E node-eve Test (optional) Failing after 2m Details
Successful checks
Check Status
✅ CI: Optional E2E Tests / E2E cloudflare-pi-durable Test (optional) Successful in 48s Details
✅ CI: Optional E2E Tests / E2E deno (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-workers-send-to-sentry Test (optional) Successful in 11m Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (cloudflare, latest) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps (client source maps kept) Test (optional) Successful in 49s Details
✅ CI: Optional E2E Tests / E2E nextjs-15 (latest) Test (optional) Successful in 4m Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-orpc Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nestjs-12 (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-express-mcp-v1 Test (optional) Successful in 46s Details
✅ CI: Optional E2E Tests / E2E nextjs-14 (latest) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E cloudflare-think (ai v6) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-16-tunnel (cloudflare) Test (optional) Successful in 10m Details
✅ CI: Optional E2E Tests / E2E cloudflare-flue-send-to-sentry Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E astro-7-static Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-flue Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E astro-7 Test (optional) Successful in 11m Details
✅ CI: Optional E2E Tests / E2E node-fastify-6 (tracer provider) Test (optional) Successful in 54s Details
✅ CI: Optional E2E Tests / E2E node-mastra Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-mastra Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E debug-id-sourcemaps Test (optional) Successful in 33s Details
✅ CI: Optional E2E Tests / E2E node-express-mcp-v2 Test (optional) Successful in 47s Details
✅ CI: Optional E2E Tests / E2E node-express-send-to-sentry Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E supabase-nextjs Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nextjs-15 (turbo) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries (cloudflare) Test (optional) Successful in 52s Details
✅ CI: Optional E2E Tests / E2E nuxt-5 Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-mastra (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react (latest) Test (optional) Successful in 10m Details
✅ CI: Optional E2E Tests / E2E node-pi-durable Test (optional) Successful in 9m Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps Test (optional) Successful in 44s Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (deno) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E cloudflare-think Test (optional) Successful in 55s Details
✅ CI: Optional E2E Tests / E2E node-fastify-6 Test (optional) Successful in 48s Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (vinext, bun) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react-sourcemaps Test (optional) Successful in 11m Details
✅ CI: Optional E2E Tests / E2E node-openai-decisions (latest) Test (optional) Successful in 43s Details
✅ CI: Optional E2E Tests / E2E react-router-7-framework-instrumentation Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-openai-decisions Test (optional) Successful in 45s Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (vinext) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E solidstart-2 Test (optional) Successful in 9m Details
✅ CI: Optional E2E Tests / E2E nuxt-5 (tunnel-dev) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E vue-3 (no Options API) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E react-send-to-sentry Test (optional) Successful in 4m Details
✅ CI: Optional E2E Tests / E2E node-pi-durable (latest) Test (optional) Successful in 48s Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps (static / nuxt generate) Test (optional) Successful in 9m Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react-cloudflare Test (optional) Successful in 46s Details
✅ CI: Optional E2E Tests / E2E cloudflare-agent Test (optional) Successful in 57s Details
✅ CI: Optional E2E Tests / E2E node-express-send-to-sentry-static Test (optional) Successful in 1m Details

@timfish

timfish commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Why does this PR result in larger bundle sizes when this flag isn't set? 🤔

This is expected for the npm output. The flags remain so users can choose what they want to filter, but this shouldn't be impacting our CDN bundles.

@sentry-junior

sentry-junior Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

The extra bytes come from the guards themselves. The browser path has 7 inline guards: 3 in client.ts, 3 in sentrySpan.ts and 1 in web-vitals tracking.ts. When the flag isn't defined, each one minifies to typeof __SENTRY_EXCLUDE_TRANSACTIONS__=="boolean"&&__SENTRY_EXCLUDE_TRANSACTIONS__. The name is a free global, so minifiers can't rename it, and they can't drop the check because the value isn't known at build time. I measured about 84–90 B raw per site.

I replaced all 7 guards with false, which is what a build-time replacement would do, and measured BrowserClient + browserTracingIntegration:

raw gzip
esbuild 0.27 −603 B −77 B
webpack + terser −609 B −66 B
size-limit @sentry/browser −60 B (CI: +60)
size-limit @sentry/browser (incl. Tracing) −116 B (CI: +117)

So the guards account for basically the whole CI delta, and the raw numbers line up with the +612 B on the uncompressed CDN tracing bundles. __SENTRY_TRACING__ uses the same inline pattern. The CDN build only replaces __SENTRY_DEBUG__ and __SENTRY_BROWSER_BUNDLE__ (dev-packages/rollup-utils/plugins/bundlePlugins.mjs), so CDN bundles currently ship __SENTRY_TRACING__ checks unreplaced as well.

Cheaper guard forms I tried:

  • typeof … !== 'undefined' && … still drops all the dead code when the flag is set, but saves almost nothing: 49 B raw / 8 B gzip on esbuild, 8 B in the size-limit tracing entry, and 3 B more in the plain @sentry/browser entry.
  • A per-package module constant, like DEBUG_BUILD. This cuts the unset cost by about 400 B raw on webpack and webpack still removes the branches. esbuild 0.27 doesn't inline it, though, so an esbuild build with the flag set loses the whole saving (131,164 vs 127,648 B). That's why the guards are inline.

How much of the flag's saving each site carries (esbuild raw, flag set):

  • the transaction branch in processBeforeSend: 1,322 B
  • _convertSpanToTransaction: 1,145 B
  • the static beforeSendSpan check in sendStandaloneSpan: 604 B
  • extractGenAiSpansFromEvent: 558 B
  • the other 3 sites together: 222 B. Those 3 force the streaming path rather than shrinking code.

Ways to cut the default cost:

  1. Replace the flag with false in the CDN bundle build, the way __SENTRY_DEBUG__ is handled. This removes the cost for CDN bundles entirely. npm consumers who don't define the flag would still pay it.
  2. Reduce the number of guard sites. The 4 large sites carry about 3.6 KB of the 4.1 KB saving. The 3 semantic sites cost about 255 B raw unset, and could maybe be collapsed into the single traceLifecycle override.

I'm happy to do either one, or both. Just say which you'd prefer.

@timfish

timfish commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

Replace the flag with false in the CDN bundle build

This is the correct route!

…lse` in CDN bundles

CDN bundles always keep transaction support, so the unresolved runtime
checks for the flag only added bytes there.

This branch has not been deployed

No deployments
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.

2 participants