Skip to content

ref(node): Fold breadcrumb & trace propagation into node fetch instrumentation#21872

Merged
mydea merged 1 commit into
developfrom
fn/node-fetch-enhance-undici
Jul 1, 2026
Merged

ref(node): Fold breadcrumb & trace propagation into node fetch instrumentation#21872
mydea merged 1 commit into
developfrom
fn/node-fetch-enhance-undici

Conversation

@mydea

@mydea mydea commented Jul 1, 2026

Copy link
Copy Markdown
Member

Enhances the node undici-instrumentation into a single instrumentation that emits http.client spans, records breadcrumbs, and propagates traces for outgoing fetch/undici requests. Previously this was split across two subscribers on the same diagnostics channels: instrumentUndici (spans) and SentryNodeFetchInstrumentation (breadcrumbs + trace propagation).

What changes:

  • Span creation is gated by a spans option (default true). When spans are disabled, breadcrumbs are still recorded and trace propagation headers are still injected (gated by tracePropagation).
  • Trace propagation now goes through the dedup-aware addTracePropagationHeadersToFetchRequest, which gains an optional span argument so outgoing headers reference the http.client span while still de-duplicating against trace headers the user set manually (e.g. via getTraceData()). This preserves the double-baggage prevention that the two-instrumentation setup previously provided.
  • @sentry/node-core now exports addFetchRequestBreadcrumb and addTracePropagationHeadersToFetchRequest so the node instrumentation can reuse them.

Root cause: folding the two subscribers into one dropped the de-dup pass that ran after span-based header injection; routing propagation through addTracePropagationHeadersToFetchRequest (now span-aware) restores it.

This PR is intentionally scoped to the node package (plus the minimal node-core support above) to keep the diff reviewable. A stacked follow-up (#21873) moves the instrumentation into node-core.

🤖 Generated with Claude Code

…instrumentation

Enhance the node `undici-instrumentation` so it is a single instrumentation that emits
`http.client` spans (gated by a `spans` option, default `true`), records breadcrumbs, and
propagates traces for outgoing fetch/undici requests. This replaces the previous setup where
span creation (`instrumentUndici`) and breadcrumb/trace-propagation (`SentryNodeFetchInstrumentation`)
ran as two separate subscribers on the same diagnostics channels.

Trace propagation now goes through the dedup-aware `addTracePropagationHeadersToFetchRequest`, which
gained an optional `span` argument so the outgoing headers reference the `http.client` span while
still de-duplicating against any trace headers the user set manually (e.g. via `getTraceData()`).

This keeps the change contained to the `node` package to ease review; a follow-up moves the
instrumentation into `node-core`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mydea mydea requested a review from a team as a code owner July 1, 2026 08:23
@mydea mydea requested review from JPeer264 and andreiborza and removed request for a team July 1, 2026 08:23

@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 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.

Reviewed by Cursor Bugbot for commit d43ca81. Configure here.

// Ignore span creation if:
// - the outgoing request is ignored via config
// - method is 'CONNECT'
if (request.method === 'CONNECT' || ignoredByCallback) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Skipped requests lose trace headers

Medium Severity

With spans enabled, onRequestCreated returns before calling addTracePropagationHeadersToFetchRequest for CONNECT requests and when new URL fails, so outgoing headers are never injected. The removed SentryNodeFetchInstrumentation still ran on those requests and propagated trace data from the active scope.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d43ca81. Configure here.

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.

I think this is actually correct/expected.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 27.62 kB - -
@sentry/browser - with treeshaking flags 26.05 kB - -
@sentry/browser (incl. Tracing) 46.07 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 47.82 kB - -
@sentry/browser (incl. Tracing, Profiling) 50.84 kB - -
@sentry/browser (incl. Tracing, Replay) 85.31 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 74.91 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 89.99 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 102.67 kB - -
@sentry/browser (incl. Feedback) 44.8 kB - -
@sentry/browser (incl. sendFeedback) 32.42 kB - -
@sentry/browser (incl. FeedbackAsync) 37.55 kB - -
@sentry/browser (incl. Metrics) 28.68 kB - -
@sentry/browser (incl. Logs) 28.93 kB - -
@sentry/browser (incl. Metrics & Logs) 29.61 kB - -
@sentry/react 29.41 kB - -
@sentry/react (incl. Tracing) 48.38 kB - -
@sentry/vue 32.85 kB - -
@sentry/vue (incl. Tracing) 47.93 kB - -
@sentry/svelte 27.64 kB - -
CDN Bundle 30.02 kB - -
CDN Bundle (incl. Tracing) 48.02 kB - -
CDN Bundle (incl. Logs, Metrics) 31.58 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 49.35 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 70.79 kB - -
CDN Bundle (incl. Tracing, Replay) 85.51 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 86.79 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 91.32 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 92.56 kB - -
CDN Bundle - uncompressed 89.42 kB - -
CDN Bundle (incl. Tracing) - uncompressed 145.35 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 94.12 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 149.32 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 218.66 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 264.36 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 268.32 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 278.06 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 282.01 kB - -
@sentry/nextjs (client) 50.76 kB - -
@sentry/sveltekit (client) 46.46 kB - -
@sentry/core/server 77.75 kB - -
@sentry/core/browser 64.06 kB - -
@sentry/node-core 61.48 kB +0.03% +13 B 🔺
@sentry/node 121.99 kB -0.07% -78 B 🔽
@sentry/node/import (ESM hook with diagnostics-channel injection) 69.95 kB - -
@sentry/node/light 50.46 kB +0.03% +11 B 🔺
@sentry/node - without tracing 73.16 kB -0.07% -47 B 🔽
@sentry/aws-serverless 84.02 kB -0.09% -70 B 🔽
@sentry/cloudflare (withSentry) - minified 180.62 kB - -
@sentry/cloudflare (withSentry) 446.93 kB - -

View base workflow run

SentryNodeFetchInstrumentation,
type SentryNodeFetchInstrumentationOptions,
} from './integrations/node-fetch/SentryNodeFetchInstrumentation';
export { addFetchRequestBreadcrumb, addTracePropagationHeadersToFetchRequest } from './utils/outgoingFetchRequest';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

super-l: Is this meant for users to be used as well? If not, I think prefixing it with _INTERNAL_ would be nice - not sure if we have a best practice for that, at least I always did it. And/or mark them as @internal in the JSDoc

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah. It seems it will be dropped again in the next PR :D

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.

yeah, this is a bit tricky 😅 should be cleaned up when I merge the stack together!

@mydea mydea merged commit a693fb5 into develop Jul 1, 2026
207 checks passed
@mydea mydea deleted the fn/node-fetch-enhance-undici branch July 1, 2026 11:08
mydea added a commit that referenced this pull request Jul 1, 2026
Stacked on #21872.

Moves the `undici-instrumentation` (spans + breadcrumbs + trace
propagation) and the vendored `node-fetch` types from the `node` package
into `node-core`, and exposes `instrumentUndici` / `NodeFetchOptions`
from `@sentry/node-core`.

What changes:
- `node-core`'s `nativeNodeFetchIntegration` now instruments via
`instrumentUndici` directly (spans default to `true`).
- The `node` package keeps a thin `nativeNodeFetchIntegration` wrapper
(`node-fetch.ts`) that derives the `spans` default from the client
options (`skipOpenTelemetrySetup` / `hasSpansEnabled`) before delegating
to `instrumentUndici`. In v11 this will become the only implementation.
- `SentryNodeFetchInstrumentation` is now unused internally and is
marked `@deprecated`. It stays exported for backwards compatibility and
will be removed in a future major version.

Review the first PR in the stack (#21872) first — this PR is purely the
relocation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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