Skip to content

fix(node): Avoid double-wrapping http module #16177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 5, 2025

Conversation

mydea
Copy link
Member

@mydea mydea commented Apr 30, 2025

@s1gr1d and @Lms24 figured out that the duplicate spans we sometimes see are related to esm and the http module. Especially, it seems to be related to us using the stealthWrap function to wrap server.emit for request isolation purposes.

While we still don't really know why this is making such problems, this PR seems to fix it (at least in integration tests) by avoiding using import-in-the-middle here, and instead using diagnostics channel with good old-fashioned monkey patching on the passed-in server instance.

Some note: We need to make sure to still call this in the otel-wrapping code of init(), otherwise there are weird timing issues in top-level-import scenarios 😬

Hopefully fixes #15830, and fixes #15803

@mydea mydea requested review from Lms24 and s1gr1d April 30, 2025 14:10
@mydea mydea self-assigned this Apr 30, 2025
@@ -451,6 +369,17 @@ function patchRequestToCaptureBody(req: IncomingMessage, isolationScope: Scope):
}
}

function getRequestOptions(request: http.ClientRequest): http.RequestOptions {
Copy link
Member Author

Choose a reason for hiding this comment

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

this is technically kind-of breaking as we do not have all the possible fields here, but IMHO it should be fine and we have the most relevant things here. We only use this as second argument passed to ignoreOutgoingRequests(), for which this should be fine I'd say - the type does not change, at the very least 😅

Copy link
Member

Choose a reason for hiding this comment

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

Is this something we have to call out when releasing?

Copy link
Member Author

Choose a reason for hiding this comment

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

probably does not hurt, I will actually add a section to changelog right away!

@mydea
Copy link
Member Author

mydea commented Apr 30, 2025

Follow-up tasks: Can we also replace the before... instrumentation we need for vercel? 🤔 we can do this in a separate PR. cc @lforst

Another follow-up task: This actually makes #15735 much easier to implement, because we can use the http.client.request.created channel which is triggered before the request is started, and use request.setHeader() to modify headers in a safe way (theoretically, at least)

Copy link
Contributor

github-actions bot commented Apr 30, 2025

size-limit report 📦

Path Size % Change Change
@sentry/browser 23.32 KB - -
@sentry/browser - with treeshaking flags 23.13 KB - -
@sentry/browser (incl. Tracing) 36.98 KB - -
@sentry/browser (incl. Tracing, Replay) 74.15 KB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 67.49 KB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 78.8 KB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 90.59 KB - -
@sentry/browser (incl. Feedback) 39.71 KB - -
@sentry/browser (incl. sendFeedback) 27.94 KB - -
@sentry/browser (incl. FeedbackAsync) 32.7 KB - -
@sentry/react 25.12 KB - -
@sentry/react (incl. Tracing) 38.95 KB - -
@sentry/vue 27.55 KB - -
@sentry/vue (incl. Tracing) 38.73 KB - -
@sentry/svelte 23.34 KB - -
CDN Bundle 24.52 KB - -
CDN Bundle (incl. Tracing) 36.99 KB - -
CDN Bundle (incl. Tracing, Replay) 72.03 KB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 77.34 KB - -
CDN Bundle - uncompressed 71.57 KB - -
CDN Bundle (incl. Tracing) - uncompressed 109.41 KB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 220.69 KB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 233.22 KB - -
@sentry/nextjs (client) 40.55 KB - -
@sentry/sveltekit (client) 37.45 KB - -
@sentry/node 143.05 KB -0.28% -407 B 🔽
@sentry/node - without tracing 96.12 KB -0.43% -425 B 🔽
@sentry/aws-serverless 120.48 KB -0.32% -390 B 🔽

View base workflow run

@mydea mydea force-pushed the fn/http-instrumentation-diagnostics-channel branch from 72ddee3 to 596dcee Compare May 2, 2025 11:14
@@ -62,7 +62,6 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
});

expect(serverPageRequestTxn).toMatchObject({
breadcrumbs: expect.any(Array),
Copy link
Member Author

Choose a reason for hiding this comment

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

this used to be the breadcrumbs of other errors from other tests.. which seems wrong to me, actually? So I wonder if this actually fixes something with astro, which is possible as now this is unrelated to when this is initialized, I guess..

Copy link
Member

@Lms24 Lms24 May 2, 2025

Choose a reason for hiding this comment

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

I guess this could fix some kind of isolation problem since request isolation in Astro was limited to the middleware lifecycle previously. Maybe something didn't work as expected in there or there was a timing issue. Gonna say it's finde until proven otherwise :D

@mydea mydea marked this pull request as ready for review May 2, 2025 11:40
@@ -451,6 +369,17 @@ function patchRequestToCaptureBody(req: IncomingMessage, isolationScope: Scope):
}
}

function getRequestOptions(request: http.ClientRequest): http.RequestOptions {
Copy link
Member

Choose a reason for hiding this comment

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

Is this something we have to call out when releasing?

Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

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

Thanks for fixing this! Very interesting way to use diagnostics channels. Let's give it a shot and see how this works out!

@@ -62,7 +62,6 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
});

expect(serverPageRequestTxn).toMatchObject({
breadcrumbs: expect.any(Array),
Copy link
Member

@Lms24 Lms24 May 2, 2025

Choose a reason for hiding this comment

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

I guess this could fix some kind of isolation problem since request isolation in Astro was limited to the middleware lifecycle previously. Maybe something didn't work as expected in there or there was a timing issue. Gonna say it's finde until proven otherwise :D

@mydea mydea force-pushed the fn/http-instrumentation-diagnostics-channel branch from aff551f to 0cd1834 Compare May 5, 2025 08:27
Comment on lines +19 to +20
In order to solve this problem, the modules are no longer monkey patched by us for request isolation. Instead, we register diagnostics*channel hooks to handle request isolation now.
While this is generally not expected to break anything, there is one tiny change that \_may* affect you if you have been relying on very specific functionality:
Copy link
Member

Choose a reason for hiding this comment

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

l: Markdown formatting here seems odd, is that on purpose?

Copy link
Member Author

Choose a reason for hiding this comment

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

oops, no!

Copy link
Member

@s1gr1d s1gr1d left a comment

Choose a reason for hiding this comment

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

I tested this in a test application which showed double root spans and this fixed the problem!

However, as I was testing the routes in the browser it was requesting favicon.ico (which was not defined) and therefore showed a GET / transaction with the http.target: /favicon.ico. As we talked about in person, we should in general filter out 4xx spans (related issue #11720).

The default route name is always / in our express 5 instrumentation (below) and in the OTel instrumentation

[SEMATTRS_HTTP_ROUTE]: route.length > 0 ? route : '/',

@mydea mydea merged commit 2e41f5e into develop May 5, 2025
135 checks passed
@mydea mydea deleted the fn/http-instrumentation-diagnostics-channel branch May 5, 2025 12:24
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.

@sentry/node creates duplicate Transactions Duplicate auto-instrumented spans in traces
4 participants