Skip to content

ref(node-core): Move node fetch instrumentation into node-core - #21873

Merged
mydea merged 3 commits into
fn/node-fetch-enhance-undicifrom
fn/node-fetch-to-node-core
Jul 1, 2026
Merged

ref(node-core): Move node fetch instrumentation into node-core#21873
mydea merged 3 commits into
fn/node-fetch-enhance-undicifrom
fn/node-fetch-to-node-core

Conversation

@mydea

@mydeamydea commented Jul 1, 2026

Copy link
Copy Markdown
Member

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

Move the `undici-instrumentation` (spans + breadcrumbs + trace propagation) and the vendored
`node-fetch` types from the `node` package into `node-core`, and expose `instrumentUndici` /
`NodeFetchOptions` from `@sentry/node-core`.
`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`.
`SentryNodeFetchInstrumentation` is now unused internally and is marked `@deprecated`; it stays
exported for backwards compatibility and will be removed in a future major version.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mydea
mydea requested a review from a team as a code ownerJuly 1, 2026 08:23
@mydea
mydea requested review from JPeer264 and andreiborza and removed request for a teamJuly 1, 2026 08:23
Comment threadpackages/node-core/src/integrations/node-fetch/index.ts
SentryNodeFetchInstrumentation,
type SentryNodeFetchInstrumentationOptions,
} from './integrations/node-fetch/SentryNodeFetchInstrumentation';
export { addFetchRequestBreadcrumb, addTracePropagationHeadersToFetchRequest } from './utils/outgoingFetchRequest';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Removed public fetch helper exports

Medium Severity

This change drops addFetchRequestBreadcrumb and addTracePropagationHeadersToFetchRequest from the @sentry/node-core package entry without a deprecation period, which is a breaking public API removal for anyone importing those helpers from the main export.

Fix in CursorFix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit d4e9512. Configure here.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

this was just added in the base-stack PR so should be fine

@mydea
mydea marked this pull request as draft July 1, 2026 08:28

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

There are 2 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 db92a29. Configure here.

Comment threadpackages/node-core/src/integrations/node-fetch/undici-instrumentation.ts Outdated
@mydea
mydea marked this pull request as ready for review July 1, 2026 08:33
// When spans are disabled we do not create a span, but we still inject trace propagation headers
// directly (unless disabled via `tracePropagation`).
if (config.spans === false) {
if (!config.spans) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The check !config.spans incorrectly disables span creation when the spans option is undefined, which is the default for nativeNodeFetchIntegration().
Severity: HIGH

Suggested Fix

Revert the condition at line 193 from !config.spans to config.spans === false. This will correctly handle the undefined case as not explicitly disabled, aligning with the documented default behavior of creating spans.

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/node-core/src/integrations/node-fetch/undici-instrumentation.ts#L193
Potential issue: The condition to check if spans should be created for `undici`
instrumentation was changed from `config.spans === false` to `!config.spans`. When
`nativeNodeFetchIntegration()` is called without options, as is common in packages like
`@sentry/bun` and `@sentry/aws-serverless`, the `spans` option is `undefined`. The new
check `!undefined` evaluates to `true`, causing the function to return early and skip
the creation of fetch spans. This contradicts the documentation, which states that spans
are enabled by default, and will break distributed tracing for outgoing HTTP requests in
multiple packages.

Also affects:

  • packages/bun/src/sdk.ts:43
  • packages/node-core/src/sdk/index.ts:63
  • dev-packages/e2e-tests/test-applications/node-express/src/app.ts:18

Did we get this right? 👍 / 👎 to inform future reviews.

@JPeer264JPeer264Jul 1, 2026

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.

If I'm not mistaken it has nothing to do with the nativeNodeFetchIntegration, the function just has the same name

Edit: Ok, I just checked (I just got confused with another with the same name) - this shouldn't be a problem, as spans should always be set by the in the node-fetch.ts and it will always be a boolean.

But I do wonder if there are differences now between nativeNodeFetchIntegration from node-core/light - but I think it is the plan to get rid of that if I understand #21778 correctly

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.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

yeah, this is on purpose and is this way to remain the behavior we have today - which is that in node-core we do not emit spans for this by default. So in node, we default this to true (same as before) generally speaking, and in node-core it is always default false, but users can now opt-in to it if they want (theoretically)

@github-actions

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize% ChangeChange
@sentry/browser27.62 kB--
@sentry/browser - with treeshaking flags26.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 flags74.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/react29.41 kB--
@sentry/react (incl. Tracing)48.38 kB--
@sentry/vue32.85 kB--
@sentry/vue (incl. Tracing)47.93 kB--
@sentry/svelte27.64 kB--
CDN Bundle30.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 - uncompressed89.42 kB--
CDN Bundle (incl. Tracing) - uncompressed145.35 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed94.12 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed149.32 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed218.66 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed264.36 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed268.32 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed278.06 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed282.01 kB--
@sentry/nextjs (client)50.76 kB--
@sentry/sveltekit (client)46.46 kB--
@sentry/core/server77.75 kB--
@sentry/core/browser64.06 kB--
@sentry/node-core62.37 kB+1.47%+901 B 🔺
@sentry/node121.5 kB-0.47%-570 B 🔽
@sentry/node/import (ESM hook with diagnostics-channel injection)69.95 kB--
@sentry/node/light50.46 kB+0.03%+11 B 🔺
@sentry/node - without tracing72.68 kB-0.72%-527 B 🔽
@sentry/aws-serverless83.52 kB-0.69%-575 B 🔽
@sentry/cloudflare (withSentry) - minified180.62 kB--
@sentry/cloudflare (withSentry)446.93 kB--

View base workflow run

const clientOptions = getClient()?.getOptions();
instrumentUndici({
...options,
spans: _shouldInstrumentSpans(options, clientOptions),

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.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

yes, in normal node setup we want to generally enable this, while in node-core it should always be opt-in to maintain the current behavior (where spans are never emitted)

@mydea
mydea merged commit 03f75c5 into developJul 1, 2026
206 checks passed
@mydea
mydea deleted the fn/node-fetch-to-node-core branch July 1, 2026 11:08
mydea added a commit that referenced this pull request Jul 1, 2026
…mentation (#21872)
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](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for freeto 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

@mydea@JPeer264