Description
Link to the code that reproduces this issue
https://github.com/lforst/nextjs-instrumentation-behaviour-repro
To Reproduce
npm i
npm run build
npm run start
curl http://localhost:3000/make-req
Afterward, take a look at the server logs.
Inside make-req/route.ts
, we are making an http.get()
call to check/route.ts
.
This http.get()
call should be instrumented, meaning tracing headers should be attached.
The check/route.ts
endpoint simply returns the headers it receives, but no tracing headers are attached, meaning the instrumentation didn't work.
You might be inclined to say "yo the Sentry SDK is probably broken", but I don't think this is the case.
For the sake of simplicity, I used the Sentry SDK which uses the OpenTelemetry instrumentation under the hood.
You could probably also reproduce this by using the OpenTelemetry HTTP instrumentation + any OTEL HTTP propagator, and the propagation would also fail.
Current vs. Expected behavior
The expected behaviour is for the OpenTelemetry instrumentation to be able to patch the http
module so that the http
call will have tracing headers attached.
I suspect that other native modules like fs
are also affected by this.
Provide environment information
Operating System:
Platform: linux
Arch: arm64
Version: #15-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 9 22:39:36 UTC 2024
Available memory (MB): 15962
Available CPU cores: 6
Binaries:
Node: 18.18.2
npm: 9.8.1
Yarn: 1.22.22
pnpm: 8.15.5
Relevant Packages:
next: 14.2.2 // Latest available version is detected (14.2.2).
eslint-config-next: N/A
react: 18.2.0
react-dom: 18.2.0
typescript: 5.1.3
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Instrumentation, SWC
Which stage(s) are affected? (Select all that apply)
next dev (local), next build (local), next start (local), Vercel (Deployed)
Additional context
We noticed this from version 14.2.0
onwards which included a change to not externalize @sentry/nextjs
anymore: #61194
However, we don't think this is the problem. In it's root I believe this is caused by the following, only surfaced by that change:
- Next.js bundling produces code that "caches"
require
calls to native modules likehttp
. - This usually looks like the following:
13685: (e) => { "use strict"; e.exports = require("http"); },
- When any code inside
instrumentation.ts
or any dependencies ofinstrumentation.ts
depends on native modules likehttp
, the require call to this module is cached before theregister()
hook is ran. - This means, when OpenTelemetry tries to patch
require
withrequire-in-the-middle
, it is pointless because any usages of the native module will use the "cached" version. - When
@sentry/nextjs
wasn't externalized, we didn't notice this problem, because the dependency onhttp
by@sentry/nextjs
wasn't bundled into the output ofinstrumentation.ts
, meaninghttp
wasn't cached too early.
I think this is fixable by either not "caching" native modules inside instrumentation.ts
, or simply externalizing everything imported inside instrumentation.ts
. The latter would probably have a lot of bad consequences.
Let me know if this needs clarification.