next.js
b0d01507 - Preserve deferred middleware instrumentation spans when the adapter throws (#100552)

Commit
8 days ago
Preserve deferred middleware instrumentation spans when the adapter throws (#100552) ### What? Direct Node.js middleware/proxy entrypoints now emit the deferred `instrumentation.ts` lifecycle spans (`load instrumentation module`, `register instrumentation`) even when the adapter rejects the request before it runs the middleware. ### Why? Follow-up to #100350. When a request to the Node.js middleware entrypoint triggers `instrumentation.ts` registration, the lifecycle spans are deferred and emitted inside the `Middleware.execute` span. If adapter setup throws first (e.g. the configured cache handler throws while the adapter builds the request's incremental cache), the emitter is never called. Registration has already completed, so no later request receives it either, and those spans are lost for the lifetime of the instance. This only affects direct entrypoints (adapters, deployments). `next start` / `next dev` register instrumentation at startup. ### How? - The middleware entrypoint template, which owns the deferred emitter, now awaits the adapter in a `try/catch`. On error it calls the emitter, then rethrows the original error unchanged. The adapter and its options are unchanged. - The emitter already emits at most once, so the fallback does nothing when the spans were already emitted inside `Middleware.execute` (e.g. the user's middleware threw). Successful requests keep their existing span parentage. In the fallback case there is no middleware span, so the spans are emitted in the caller's active context (or as roots). - The fallback call is guarded so that a failure while tracing can't replace the adapter's error. **Test:** this reuses the existing direct-entrypoint OpenTelemetry harness (`test/e2e/opentelemetry/instrumentation`, `next start` only). The harness calls the built middleware entrypoint's `handler` with a real `Request`, like the Vercel adapter does. - The fixture's custom cache handler throws from its constructor when a test-only request header is set. The adapter constructs it per request, before running the middleware. - A new suite, in a fresh process, sends that request through the normal middleware flow and asserts that: - the original error propagates; - both lifecycle spans are collected exactly once, with no `Middleware.execute` span; - a later valid request succeeds without emitting them again. - The test fails without the fix. ### Verification - New test plus the existing "should trace instrumentation registration in the first middleware request" variants: `pnpm test-start-turbo` and `pnpm test-start-webpack` on `test/e2e/opentelemetry/instrumentation/opentelemetry.test.ts` - `pnpm jest packages/next/src/server/lib/router-utils/instrumentation-globals.test.ts` - `pnpm --filter=next types`, `pnpm typescript` <!-- NEXT_JS_LLM --> <!-- fleet bb16cbee-5cda-4702-b442-ea49bf648fe0 --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Author
Parents
Loading