bugfix: Bad `next.route` on `pages` dir OTel spans for `404`s / `500`s (#77150)
## Issue
- Resolves https://github.com/vercel/next.js/issues/82102
## What it Does
Prevents the root span attribute `next.route` from being overwritten
once set for a request. This resolves two issues:
1. Fixes `404`s and `500`s in the `pagesDir` overwriting `next.route` on
OpenTelemetry root span
2. Removes redundant `/route` suffixes on `appDir` API Route Handler
OTel span names
## Background
The root OTel span (`BaseServer.handleRequest`) for requests in Next.js
derives its `name` from its `next.route` attr, set by child spans
through the `NextTracerImpl.setRootSpanAttribute` function. The problem
is, when a page `404`s or `500`s, the rendering of the `404` or `500`
page *also* sets `next.route`. This scrubs the root span of the original
page's `next.route`, as if the user had requested the `/_not-found` or
`/_error` page directly.
For example, these two traces in `otel-desktop-viewer` are requests to
the same URL, with the bottom having received an error thrown in
`getServerSideProps`:
<img width="328" alt="image"
src="https://github.com/user-attachments/assets/855744b6-8720-4c5e-a5e5-b15cb95e43ca"
/>
This is not helpful at all! Further investigation shows that root spans
all have the following behavior::
- Successful responses are correctly named after their path, e.g. `GET
/foo` with `status_code: 200`
- Not found responses on ALL paths are named `GET /_not-found` with
`status_code: 404`
- Erroring responses on ALL paths are named `GET /_error` with
`status_code: 500`
As another example, consider which `name` and `status_code` combo is
more helpful:
- `name: GET /_not-found`, `status_code: 404`, `http.target:
/foo?page=10`, `next.route: /_not-found` (current)
- **`name: GET /foo`**, `status_code: 404`, `http.target: /foo?page=10`,
`next.route: /foo`, (desired)
Clearly, replacing span names with `GET /_not-found` only removes
information, as the "404"-ness is already indicated by the `status_code`
span attribute.
A search for `setRootSpanAttribute` confirms that this function is only
used 4 times, all to set `next.route`:
https://github.com/vercel/next.js/blob/6d7266e329a684b4e254c6a265f5b3ddb6e7b332/packages/next/src/server/api-utils/index.ts?plain=1#L28
https://github.com/vercel/next.js/blob/6d7266e329a684b4e254c6a265f5b3ddb6e7b332/packages/next/src/server/route-modules/app-route/module.ts?plain=1#L743
https://github.com/vercel/next.js/blob/6d7266e329a684b4e254c6a265f5b3ddb6e7b332/packages/next/src/server/base-server.ts?plain=1#L3732
https://github.com/vercel/next.js/blob/6d7266e329a684b4e254c6a265f5b3ddb6e7b332/packages/next/src/server/app-render/app-render.tsx?plain=1#L1333
So, we need to make sure `setRootSpanAttribute` does not overwrite
`next.route` if it has already been set. This PR does that, and adds
tests for `/_not-found` and `/_error` page cases (which were missing
from the `e2e` test suite).
## Repro
Repro: https://github.com/evankirkiles/nextjs-otel-span-name-errors
Before (using `next.js@canary`):
<img width="1773" height="1036" alt="SCR-20250727-suas"
src="https://github.com/user-attachments/assets/94895bc7-8f09-4c55-ba3e-63191ac49ff7"
/>
After (using changes from this PR):
<img width="1728" height="997" alt="image"
src="https://github.com/user-attachments/assets/3f3a1fdd-0acd-4b00-a34d-3e8e3d49e09a"
/>
## Fixing a bug
- [x] Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs