Skip to content

feat(node): Avoid OTEL instrumentation for outgoing requests on Node 22+#17355

Open
mydea wants to merge 17 commits intodevelopfrom
fn/avoid-otel-http-instrumentation
Open

feat(node): Avoid OTEL instrumentation for outgoing requests on Node 22+#17355
mydea wants to merge 17 commits intodevelopfrom
fn/avoid-otel-http-instrumentation

Conversation

@mydea
Copy link
Member

@mydea mydea commented Aug 8, 2025

Registers diagnostics channels for outgoing requests on Node >= 22 that takes
care of creating spans, rather than relying on OTEL instrumentation.

Closes #18497 (added automatically)

@mydea mydea self-assigned this Aug 8, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Aug 8, 2025

size-limit report 📦

Path Size % Change Change
@sentry/browser 25.56 kB - -
@sentry/browser - with treeshaking flags 24.08 kB - -
@sentry/browser (incl. Tracing) 42.36 kB - -
@sentry/browser (incl. Tracing, Profiling) 47.03 kB - -
@sentry/browser (incl. Tracing, Replay) 81.18 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 70.8 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 85.87 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 98.03 kB - -
@sentry/browser (incl. Feedback) 42.29 kB - -
@sentry/browser (incl. sendFeedback) 30.23 kB - -
@sentry/browser (incl. FeedbackAsync) 35.22 kB - -
@sentry/browser (incl. Metrics) 26.74 kB - -
@sentry/browser (incl. Logs) 26.88 kB - -
@sentry/browser (incl. Metrics & Logs) 27.56 kB - -
@sentry/react 27.33 kB - -
@sentry/react (incl. Tracing) 44.72 kB - -
@sentry/vue 30.01 kB - -
@sentry/vue (incl. Tracing) 44.22 kB - -
@sentry/svelte 25.58 kB - -
CDN Bundle 28.11 kB - -
CDN Bundle (incl. Tracing) 43.2 kB - -
CDN Bundle (incl. Logs, Metrics) 28.95 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 44.03 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 68.02 kB - -
CDN Bundle (incl. Tracing, Replay) 80.07 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 80.94 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 85.5 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 86.4 kB - -
CDN Bundle - uncompressed 82.22 kB - -
CDN Bundle (incl. Tracing) - uncompressed 127.93 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 85.05 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 130.76 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 208.71 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 244.81 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 247.63 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 257.61 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 260.42 kB - -
@sentry/nextjs (client) 47.12 kB - -
@sentry/sveltekit (client) 42.81 kB - -
@sentry/node-core 56.22 kB +7.83% +4.08 kB 🔺
@sentry/node 167.61 kB +0.66% +1.09 kB 🔺
@sentry/node - without tracing 95.71 kB +1.89% +1.77 kB 🔺
@sentry/aws-serverless 112.69 kB +2.97% +3.25 kB 🔺

View base workflow run

@mydea mydea force-pushed the fn/avoid-otel-http-instrumentation branch from 67942ee to b8b33f4 Compare August 8, 2025 09:29
@andreiborza andreiborza force-pushed the fn/avoid-otel-http-instrumentation branch 2 times, most recently from a66e597 to 002fb1e Compare December 14, 2025 17:10
@github-actions
Copy link
Contributor

github-actions bot commented Dec 14, 2025

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,234 - 9,053 +2%
GET With Sentry 1,665 18% 1,684 -1%
GET With Sentry (error only) 6,313 68% 6,116 +3%
POST Baseline 1,196 - 1,210 -1%
POST With Sentry 590 49% 595 -1%
POST With Sentry (error only) 1,057 88% 1,069 -1%
MYSQL Baseline 3,246 - 3,320 -2%
MYSQL With Sentry 454 14% 453 +0%
MYSQL With Sentry (error only) 2,657 82% 2,676 -1%

View base workflow run

@andreiborza andreiborza force-pushed the fn/avoid-otel-http-instrumentation branch 2 times, most recently from dcb74c7 to 4023787 Compare December 14, 2025 18:48
mydea and others added 2 commits December 14, 2025 19:55
Registers diagnostics channels for outgoing requests on Node >= 22 that takes
care of creating spans, rather than relying on OTEL instrumentation.
@andreiborza andreiborza force-pushed the fn/avoid-otel-http-instrumentation branch from 4023787 to 1dad574 Compare December 14, 2025 18:57
Comment on lines 67 to 68
* This is a feature flag that should be enabled by SDKs when the runtime supports it (Node 22+).
* Individual users should not need to configure this directly.
Copy link
Member

Choose a reason for hiding this comment

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

This comment sound very directed to us as SDK maintainers. As this comment is public-facing I would write that a bit differently as it can be confusing how to act on this as a user.

Copy link
Member

Choose a reason for hiding this comment

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

Do you have a suggestion? The comment already calls out individual users should not set this.

*
* @default `true`
*/
spans?: boolean;
Copy link
Member

Choose a reason for hiding this comment

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

Q: I'm wondering why we need this second option...would someone ever want to set this to false?

Copy link
Member

Choose a reason for hiding this comment

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

Users with custom OTel setups that add @opentelemetry/instrumentation-http will want to set this, see: https://docs.sentry.io/platforms/javascript/guides/node/opentelemetry/custom-setup/#custom-http-instrumentation

Copy link
Member

Choose a reason for hiding this comment

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

Ahh, thanks!

// In this case, `http.client.response.finish` is not triggered
subscribe('http.client.request.error', onHttpClientRequestError);

if (this.getConfig().createSpansForOutgoingRequests) {
Copy link
Member

Choose a reason for hiding this comment

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

With the span option, we should also check for this.getConfig().spans (if I understood that correctly).

Copy link
Member

Choose a reason for hiding this comment

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

This is checked in the handler itself but I agree, we can check this earlier. Currently it can be misleading to set spans: false and then still get the Handling started outgoing request log.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 2, 2026

Codecov Results 📊


Generated by Codecov Action

@github-actions
Copy link
Contributor

github-actions bot commented Feb 2, 2026

Codecov Results 📊

23 passed | ⏭️ 7 skipped | Total: 30 | Pass Rate: 76.67% | Execution Time: 10.18s

All tests are passing successfully.


Generated by Codecov Action

@andreiborza andreiborza requested a review from s1gr1d February 18, 2026 14:52
propagateTraceInOutgoingRequests: !useOtelHttpInstrumentation,
propagateTraceInOutgoingRequests: FULLY_SUPPORTS_HTTP_DIAGNOSTICS_CHANNEL || !useOtelHttpInstrumentation,
createSpansForOutgoingRequests: FULLY_SUPPORTS_HTTP_DIAGNOSTICS_CHANNEL,
spans: options.spans,
Copy link

Choose a reason for hiding this comment

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

Span creation overhead when tracing is disabled on Node 22.12+

Medium Severity

On Node 22.12+ when tracing is globally disabled (no tracesSampleRate/tracesSampler), createSpansForOutgoingRequests is unconditionally true and spans is passed as undefined (defaulting to true). This causes _startSpanForOutgoingRequest to run for every outgoing HTTP request, creating non-recording spans and setting up a Proxy on request.once, registering multiple event listeners, and computing span data — all of which is wasted work. Before this change, OTEL instrumentation was explicitly not set up in this scenario, resulting in zero span-creation overhead. The spans option doesn't account for hasSpansEnabled(clientOptions), so error-only SDK users on Node 22.12+ get unnecessary per-request overhead.

Additional Locations (1)

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Copy link
Member

Choose a reason for hiding this comment

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

This is checked within the instrumentation and handled there.

breadcrumbs: options.breadcrumbs,
propagateTraceInOutgoingRequests: !useOtelHttpInstrumentation,
propagateTraceInOutgoingRequests: FULLY_SUPPORTS_HTTP_DIAGNOSTICS_CHANNEL || !useOtelHttpInstrumentation,
createSpansForOutgoingRequests: FULLY_SUPPORTS_HTTP_DIAGNOSTICS_CHANNEL,
Copy link

Choose a reason for hiding this comment

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

Missing integration test for new diagnostics channel spans

Low Severity

This is a feat PR that introduces diagnostics-channel-based outgoing request span creation, but the diff does not include a new integration or E2E test that specifically exercises this code path. The existing http-basic integration test may cover it implicitly on Node 22.12+ CI runners, but there's no test that explicitly verifies the createSpansForOutgoingRequests / diagnostics channel flow or differentiates it from the OTEL-based flow. Adding a targeted integration test would guard against regressions in this specific feature.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

ignoreOutgoingRequests: options.ignoreOutgoingRequests,
outgoingRequestHook: (span, request) => {
// Sanitize data URLs to prevent long base64 strings in span attributes
const url = getRequestUrl(request);
Copy link

Choose a reason for hiding this comment

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

Bug: The outgoingRequestHook incorrectly calls getRequestUrl with a ClientRequest object, but the function expects RequestOptions, likely causing a runtime error.
Severity: HIGH

Suggested Fix

The getClientRequestUrl function, which correctly handles ClientRequest objects, should be used instead of getRequestUrl. This requires exporting getClientRequestUrl from @sentry/node-core so it can be imported and used in packages/node/src/integrations/http.ts.

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/src/integrations/http.ts#L262

Potential issue: In `packages/node/src/integrations/http.ts`, the `outgoingRequestHook`
receives a `http.ClientRequest` object but passes it to `getRequestUrl`. This function
is designed to accept `RequestOptions`, not `ClientRequest`. This type mismatch will
likely lead to a runtime error when `getRequestUrl` attempts to access properties that
do not exist on the `ClientRequest` object, such as `requestOptions.hostname`. A similar
incorrect call is also present in the regular OTEL HTTP instrumentation's `requestHook`.

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

Copy link

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

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

*/
private _onOutgoingRequestCreated(request: http.ClientRequest): void {
DEBUG_BUILD && debug.log(INSTRUMENTATION_NAME, 'Handling outgoing request created');

Copy link

Choose a reason for hiding this comment

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

Ignored outgoing requests lose trace propagation on Node 22.12+

Medium Severity

On Node 22.12+, _onOutgoingRequestCreated returns early when _shouldIgnoreOutgoingRequest is true, which skips both span creation AND trace propagation. Previously, OTEL's HttpInstrumentation handled outgoing propagation independently—its ignoreOutgoingRequestHook only suppressed span creation while still propagating trace context. Now that disableOutgoingRequestInstrumentation: FULLY_SUPPORTS_HTTP_DIAGNOSTICS_CHANNEL disables OTEL's outgoing handling entirely, requests matching ignoreOutgoingRequests no longer receive sentry-trace/baggage headers. This is a behavioral regression for users who ignore spans/breadcrumbs for certain URLs but still expect distributed tracing to work for those endpoints.

Additional Locations (1)

Fix in Cursor Fix in Web

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.

feat(node): Avoid OTEL instrumentation for outgoing requests on Node 22+

4 participants

Comments