Skip to content

Emit instrumentation lifecycle spans in the request that registered it - #100350

Merged
mischnic merged 7 commits into
fleet/otel-docs-instrumentation-spansfrom
fleet/otel-instrumentation-spans-under-request
Oct 21, 2026
Merged

mischnic merged 7 commits into
fleet/otel-docs-instrumentation-spansfrom
fleet/otel-instrumentation-spans-under-request

Conversation

@sokra

@sokra sokra commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Summary

Stacked on #100324. Includes the commits from #100392, which were folded into this PR.

Next.js emits two lifecycle spans for instrumentation.ts in the Node.js runtime: load instrumentation module and register instrumentation. They're backdated, because they're emitted after register() returns (register() usually installs the tracer provider). Until now they were always separate root traces.

Direct entrypoints (Vercel, adapters, standalone handlers) register instrumentation lazily, in the first request. That split the first request's work across extra traces. Now the spans join the trace of the request that triggered registration:

route / page entrypoint:
 [P →] GET /page (BaseServer.handleRequest, backdated to cover registration)
        ├─ load instrumentation module
        ├─ register instrumentation
        └─ prepare route module → render …

Node.js middleware entrypoint, no parent:
 GET (Next.request, backdated)
  ├─ middleware GET (backdated to cover registration)
  │   ├─ load instrumentation module
  │   └─ register instrumentation
  └─ GET /page

Node.js middleware entrypoint with host span / incoming traceparent P:
 P
  ├─ middleware GET (backdated to cover registration)
  │   ├─ load instrumentation module
  │   └─ register instrumentation
  └─ GET /page

How

  • ensureInstrumentationRegisteredWithDeferredSpans: when this call starts registration and registration succeeds, the lifecycle spans are recorded instead of emitted. The function returns an idempotent emitter to that caller only. Concurrent callers, later callers and startup callers get nothing.
  • RouteModule.ensureInstrumentation(req, { deferSpans: true }) is called by the templates before traceRequest. It records when the request started and returns that time, together with the emitter, as instrumentation. traceRequest takes it as an option, backdates the request span to that time, and emits the lifecycle spans as the span's first children.
  • The Node.js middleware template passes the emitter and the start time to the adapter. The adapter calls the emitter first thing inside the Middleware.execute span and backdates that span to the request start.
    • The shared parent (incoming traceparent, host span, or the backdated inserted Next.request root) is still captured before entering the middleware span. The middleware and the render stay siblings, and traceparent handling and forwarding are unchanged.
  • Unchanged:
    • next start / next dev startup registration (separate roots).
    • Registration errors: emitted immediately with the same statuses, including non-Error throws.
    • Edge (no lifecycle spans).
    • No instrumentation.ts, or no register export.
  • User-created spans in instrumentation.ts are out of scope (see docs: note that instrumentation-time spans don't join request traces #100324).

Verification

  • Collector suite, cold direct entrypoints:
    • Route/page requests, with and without an incoming traceparent, and with a host span.
    • Node.js middleware requests: no parent, incoming traceparent, host span.
    • Each test asserts exactly one load span and one register span in the request's trace, parented as shown above and covered by the backdated parent. For middleware, the parent is the middleware span, and it starts no later than the lifecycle spans and no earlier than its own parent.
    • Direct-entrypoint single-trace assertions no longer exclude lifecycle spans as startup work. Under next start, startup spans stay roots. Fail-first verified.
  • Deploy-capable test/e2e/app-dir/otel-middleware-trace: span tree snapshots of the lifecycle spans with their ancestors, plus the first request's tree including them.
    • Deployed: children of the request span that triggered registration, or of middleware GET for Node.js middleware.
    • next dev / next start: startup roots.
    • No deploy gate.
  • Unit tests for the deferral helper: once-only, concurrency, startup ownership, no register, register() errors (including throw undefined), and module-load failure.

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Tests Passed

Commit: c8bff0e

@sokra
sokra added this pull request to stack #100351 October 17, 2026 11:05
@sokra
sokra force-pushed the fleet/otel-instrumentation-spans-under-request branch from f566475 to 10d5128 Compare October 19, 2026 11:26
@mischnic
mischnic force-pushed the fleet/otel-instrumentation-spans-under-request branch from 10d5128 to 6754551 Compare October 19, 2026 15:21
@sokra
sokra force-pushed the fleet/otel-instrumentation-spans-under-request branch from 6754551 to 211f4eb Compare October 20, 2026 10:51
@sokra
sokra force-pushed the fleet/otel-instrumentation-spans-under-request branch from 211f4eb to c03855f Compare October 20, 2026 11:20
@sokra
sokra force-pushed the fleet/otel-instrumentation-spans-under-request branch from c03855f to 1cca382 Compare October 20, 2026 13:35
@sokra
sokra force-pushed the fleet/otel-instrumentation-spans-under-request branch from 1cca382 to bb48172 Compare October 20, 2026 14:04
@sokra
sokra force-pushed the fleet/otel-instrumentation-spans-under-request branch from bb48172 to 8121501 Compare October 20, 2026 16:12
@sokra
sokra force-pushed the fleet/otel-instrumentation-spans-under-request branch from 8121501 to b7a797e Compare October 20, 2026 16:36
@sokra
sokra force-pushed the fleet/otel-instrumentation-spans-under-request branch from b7a797e to cf1a8f9 Compare October 20, 2026 22:41
@sokra
sokra force-pushed the fleet/otel-instrumentation-spans-under-request branch from 4abe217 to 9df28d3 Compare October 21, 2026 13:33

sokra commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Reviewed 9df28d3

@sokra
sokra marked this pull request as ready for review October 21, 2026 14:40
@sokra
sokra requested a review from mischnic October 21, 2026 14:58
vercel-fleet-prod Bot and others added 4 commits October 21, 2026 15:09
Direct entrypoints register instrumentation.ts lazily in the first request.
Its backdated lifecycle spans (load instrumentation module, register
instrumentation) were separate root traces. Defer them to the request that
triggered registration and emit them as children of its request span, or, for
Node.js middleware, in the shared parent / inserted Middleware.request root.
Startup registration, errors and Edge are unchanged.

<!-- NEXT_JS_LLM -->

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Assert the recorded lifecycle spans and their ancestors as a span tree, and include them in the first request's tree.

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
@sokra
sokra force-pushed the fleet/otel-instrumentation-spans-under-request branch from 9df28d3 to 44566d8 Compare October 21, 2026 15:10
vercel-fleet-prod Bot and others added 2 commits October 21, 2026 15:10
When a Node.js middleware entrypoint registers instrumentation.ts, emit
the lifecycle spans inside the middleware span and backdate it to cover
registration. The middleware and render stay siblings.

Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Comment thread packages/next/src/server/web/adapter.ts Resolved
…tionRegisteredWithDeferredSpans

Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
@mischnic
mischnic merged commit 65675c3 into canary Oct 21, 2026
120 checks passed
@mischnic
mischnic deleted the fleet/otel-instrumentation-spans-under-request branch October 21, 2026 18:30
mischnic added a commit that referenced this pull request Oct 21, 2026
## Summary

Bottom of a stack: this PR → #100294 → #100324 → #100350 → #100392.

In direct entrypoints (Vercel, adapters, standalone handlers, i.e.
without next-server), `prepare route module` ran before the request span
started. Since 16.4 every request therefore produced an extra parentless
`prepare route module` trace next to the request trace. Now it is part
of the request span:

```
[P →] GET /page (BaseServer.handleRequest)
       └─ prepare route module → render …
```

### How

- New `RouteModule.traceRequest` (`@internal`, kept out of the published
declaration types) starts the request span **before** `prepare`. It uses
the caller's tracer. When the handler is wrapped by next-server, which
already started the request span, it runs inside that active span
instead.
- Callers first await the new `RouteModule.ensureInstrumentation`
(`@internal`), split out of `prepareImpl`: it registers
`instrumentation.ts`, which usually installs the tracer provider, so a
span started earlier would not be recorded. `prepareImpl` still calls
it, and patches `res.setHeader` as before.
- The app-page, app-route, pages and pages-api templates/handlers call
`ensureInstrumentation` and then `traceRequest`, and handle the response
through one code path: `traceRequest` passes the request span and the
request's root span (the span that was active when the handler was
invoked — next-server's request span or a platform/host span — or the
request span itself), and the route name is propagated to the root span
when it isn't the request span.
- Parenting of the request span itself (incoming `traceparent`, host
span, root) is unchanged.

## Verification

- Collector suite `test/e2e/opentelemetry/instrumentation`: direct
entrypoint cold and warm requests for all four handler types, with and
without an incoming `traceparent` and with a host span, assert
`RouteModule.prepare` is inside the request's trace, and route names on
the request and parent spans.
- New deploy-capable e2e suite `test/e2e/app-dir/otel-middleware-trace`
(moved down from #100294): `proxy.ts` and Edge `middleware.ts` variants,
plus a custom server with a host span. The app records its spans with a
SpanProcessor; the middleware and the render report the spans they can
see, and the test asserts the stitched span tree (with `http.target`,
`http.route`, `next.route` and `http.status_code` where set at report
time) in inline snapshots for pages, route handlers, rewrites,
redirects, direct responses, matcher misses, incoming `traceparent`,
header overrides and a middleware-set `traceparent`, in dev, start,
Cache Components and deploy. Here it records the current behavior
(middleware and render in separate traces); #100294 shows the change.
- Unit tests for the tracer changes (`tracer.test.ts`).

<!-- NEXT_JS_LLM -->


<!-- fleet ce0ecb23-b14c-4679-92a9-e676130f618e -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
mischnic pushed a commit that referenced this pull request Oct 21, 2026
## Summary

Bottom of a stack: this PR → #100294 → #100324 → #100350 → #100392.

In direct entrypoints (Vercel, adapters, standalone handlers, i.e.
without next-server), `prepare route module` ran before the request span
started. Since 16.4 every request therefore produced an extra parentless
`prepare route module` trace next to the request trace. Now it is part
of the request span:

```
[P →] GET /page (BaseServer.handleRequest)
       └─ prepare route module → render …
```

### How

- New `RouteModule.traceRequest` (`@internal`, kept out of the published
declaration types) starts the request span **before** `prepare`. It uses
the caller's tracer. When the handler is wrapped by next-server, which
already started the request span, it runs inside that active span
instead.
- Callers first await the new `RouteModule.ensureInstrumentation`
(`@internal`), split out of `prepareImpl`: it registers
`instrumentation.ts`, which usually installs the tracer provider, so a
span started earlier would not be recorded. `prepareImpl` still calls
it, and patches `res.setHeader` as before.
- The app-page, app-route, pages and pages-api templates/handlers call
`ensureInstrumentation` and then `traceRequest`, and handle the response
through one code path: `traceRequest` passes the request span and the
request's root span (the span that was active when the handler was
invoked — next-server's request span or a platform/host span — or the
request span itself), and the route name is propagated to the root span
when it isn't the request span.
- Parenting of the request span itself (incoming `traceparent`, host
span, root) is unchanged.

## Verification

- Collector suite `test/e2e/opentelemetry/instrumentation`: direct
entrypoint cold and warm requests for all four handler types, with and
without an incoming `traceparent` and with a host span, assert
`RouteModule.prepare` is inside the request's trace, and route names on
the request and parent spans.
- New deploy-capable e2e suite `test/e2e/app-dir/otel-middleware-trace`
(moved down from #100294): `proxy.ts` and Edge `middleware.ts` variants,
plus a custom server with a host span. The app records its spans with a
SpanProcessor; the middleware and the render report the spans they can
see, and the test asserts the stitched span tree (with `http.target`,
`http.route`, `next.route` and `http.status_code` where set at report
time) in inline snapshots for pages, route handlers, rewrites,
redirects, direct responses, matcher misses, incoming `traceparent`,
header overrides and a middleware-set `traceparent`, in dev, start,
Cache Components and deploy. Here it records the current behavior
(middleware and render in separate traces); #100294 shows the change.
- Unit tests for the tracer changes (`tracer.test.ts`).

<!-- NEXT_JS_LLM -->

<!-- fleet ce0ecb23-b14c-4679-92a9-e676130f618e -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
(cherry picked from commit e3c2f63)

Co-authored-by: Tobias Koppers <tobias.koppers@googlemail.com>
mischnic pushed a commit that referenced this pull request Oct 21, 2026
#100350)

## Summary

Stacked on #100324. Includes the commits from #100392, which were folded
into this PR.

Next.js emits two lifecycle spans for `instrumentation.ts` in the
Node.js runtime: `load instrumentation module` and `register
instrumentation`. They're backdated, because they're emitted after
`register()` returns (`register()` usually installs the tracer
provider). Until now they were always separate root traces.

Direct entrypoints (Vercel, adapters, standalone handlers) register
instrumentation lazily, in the first request. That split the first
request's work across extra traces. Now the spans join the trace of the
request that triggered registration:

```
route / page entrypoint:
 [P →] GET /page (BaseServer.handleRequest, backdated to cover registration)
        ├─ load instrumentation module
        ├─ register instrumentation
        └─ prepare route module → render …

Node.js middleware entrypoint, no parent:
 GET (Next.request, backdated)
  ├─ middleware GET (backdated to cover registration)
  │   ├─ load instrumentation module
  │   └─ register instrumentation
  └─ GET /page

Node.js middleware entrypoint with host span / incoming traceparent P:
 P
  ├─ middleware GET (backdated to cover registration)
  │   ├─ load instrumentation module
  │   └─ register instrumentation
  └─ GET /page
```

### How

- `ensureInstrumentationRegisteredWithDeferredSpans`: when this call
starts registration and registration succeeds, the lifecycle spans are
recorded instead of emitted. The function returns an idempotent emitter
to that caller only. Concurrent callers, later callers and startup
callers get nothing.
- `RouteModule.ensureInstrumentation(req, { deferSpans: true })` is
called by the templates before `traceRequest`. It records when the
request started and returns that time, together with the emitter, as
`instrumentation`. `traceRequest` takes it as an option, backdates the
request span to that time, and emits the lifecycle spans as the span's
first children.
- The Node.js middleware template passes the emitter and the start time
to the adapter. The adapter calls the emitter first thing inside the
`Middleware.execute` span and backdates that span to the request start.
- The shared parent (incoming `traceparent`, host span, or the backdated
inserted `Next.request` root) is still captured before entering the
middleware span. The middleware and the render stay siblings, and
`traceparent` handling and forwarding are unchanged.
- Unchanged:
  - `next start` / `next dev` startup registration (separate roots).
- Registration errors: emitted immediately with the same statuses,
including non-`Error` throws.
  - Edge (no lifecycle spans).
  - No `instrumentation.ts`, or no `register` export.
- User-created spans in `instrumentation.ts` are out of scope (see
#100324).

## Verification

- Collector suite, cold direct entrypoints:
- Route/page requests, with and without an incoming `traceparent`, and
with a host span.
- Node.js middleware requests: no parent, incoming `traceparent`, host
span.
- Each test asserts exactly one load span and one register span in the
request's trace, parented as shown above and covered by the backdated
parent. For middleware, the parent is the middleware span, and it starts
no later than the lifecycle spans and no earlier than its own parent.
- Direct-entrypoint single-trace assertions no longer exclude lifecycle
spans as startup work. Under `next start`, startup spans stay roots.
Fail-first verified.
- Deploy-capable `test/e2e/app-dir/otel-middleware-trace`: span tree
snapshots of the lifecycle spans with their ancestors, plus the first
request's tree including them.
- Deployed: children of the request span that triggered registration, or
of `middleware GET` for Node.js middleware.
  - `next dev` / `next start`: startup roots.
  - No deploy gate.
- Unit tests for the deferral helper: once-only, concurrency, startup
ownership, no `register`, `register()` errors (including `throw
undefined`), and module-load failure.

<!-- NEXT_JS_LLM -->

<!-- fleet ce0ecb23-b14c-4679-92a9-e676130f618e -->

---------

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: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
(cherry picked from commit 65675c3)

Co-authored-by: Tobias Koppers <tobias.koppers@googlemail.com>
mischnic added a commit that referenced this pull request Oct 22, 2026
…hrows (#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>
mischnic pushed a commit that referenced this pull request Oct 22, 2026
…hrows (#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>
(cherry picked from commit b0d0150)

Co-authored-by: Tobias Koppers <tobias.koppers@googlemail.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
mischnic pushed a commit that referenced this pull request Oct 22, 2026
…0294, #100324, #100350, #100552, #100583) (#100542)

Backports the OpenTelemetry request-trace stack to
`releases/lts/active`:

- #100349 Start the request span before route module preparation
- #100294 Keep middleware and render in one OpenTelemetry trace as
siblings (re-lands #100136, whose LTS backport #100226 was reverted in
#100269)
- #100324 docs: note that instrumentation-time spans don't join request
traces
- #100350 Emit instrumentation lifecycle spans in the request that
registered it
- #100552 Preserve deferred middleware instrumentation spans when the
adapter throws
- #100583 test: add non-adapter deploy snapshots to
otel-middleware-trace. Without it, `otel-middleware-trace` fails the
release deploy tests that deploy with `NEXT_ENABLE_ADAPTER=0`, as on
v16.5.0-canary.23.

The six canary merge commits are cherry-picked in order with `-x`, and
all applied cleanly. The patch content is identical to canary's; only
blob IDs and line offsets differ.

## Verification

- `test/e2e/opentelemetry/instrumentation/`: `test-start-turbo` (122
passed) and `test-dev-turbo` (67 passed). The #100552 regression test
fails without its `middleware.ts` change.
- `test/e2e/app-dir/otel-middleware-trace`: `test-start-turbo`,
`test-dev-turbo` and `test-start-webpack` (30 passed each, inline
snapshots unchanged). The #100583 commit leaves the test file
byte-identical to canary's.
- Unit tests for `instrumentation-globals`, `tracer` and
`forward-trace-context` (40 passed), plus `pnpm --filter=next types`
- Built with `CI=true pnpm build-all` against the published `@next/swc`
16.4.2 binary. The backport touches no Rust.
- PR CI deploys only through the adapter, so the non-adapter snapshots
from #100583 are confirmed by the next release deploy run.

<!-- NEXT_JS_LLM -->


<!-- fleet ce0ecb23-b14c-4679-92a9-e676130f618e -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
@github-actions github-actions Bot added the locked label Nov 5, 2026
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Nov 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

created-by: Turbopack team PRs by the Turbopack team. Documentation Related to Next.js' official documentation. locked tests type: next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants