From e71a1fab90de25f3860248863d3507b50e19bb21 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 25 Aug 2026 17:31:34 +0200 Subject: [PATCH] docs(migration): Document low-cardinality http.server span names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the `http.server` row to the v11 span name section and calls out that `tracesSampler` and `ignoreSpans` can no longer match on span name — they run at span start, before a route exists, so name-based rules stop matching silently. Also points the port-span-names skill at this file: the pageload port wrote to the root MIGRATION.md, which has been superseded for v11 content. Fixes #23527 Co-Authored-By: Claude Opus 5 (1M context) --- .agents/skills/port-span-names/SKILL.md | 52 +++++++++++++++++++++++-- MIGRATION.md | 26 +++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/.agents/skills/port-span-names/SKILL.md b/.agents/skills/port-span-names/SKILL.md index f966b8830dd1..0775a792012e 100644 --- a/.agents/skills/port-span-names/SKILL.md +++ b/.agents/skills/port-span-names/SKILL.md @@ -148,6 +148,15 @@ cd dev-packages/browser-integration-tests && npx playwright test --project=chrom Before assuming a failure is yours, baseline it: `git stash`, re-run, `git stash pop`. Commonly pre-existing in a dev checkout: `packages/core` `test/types/typedef.test.ts` (needs a full prod build), the `@sentry/ember` build, and `packages/nuxt` lint errors. +**`git stash` alone is not a baseline** for anything that resolves `@sentry/*` from `build/` — framework +packages, `dev-packages/node-integration-tests`, and the Playwright suites all do. The build output still +contains your change after stashing, so you must rebuild on both sides of the comparison. That is two +full builds; budget for it, run them in the background, and never leave the tree stashed while one runs. + +Cheaper than a rebuild baseline: check whether the failing suite is even reachable from your change. A +suite pinned to `traceLifecycle: 'static'` cannot be affected — every gate falls through to the original +expression — so a static-mode failure is causally excluded without rebuilding anything. + Expect to update, and read each one to confirm the new value is _correct_ rather than just green: - unit assertions on the span name and on `scope.transactionName` @@ -157,15 +166,52 @@ Expect to update, and read each one to confirm the new value is _correct_ rather Finish with `yarn format` and `yarn lint`. -## 8. Document it in MIGRATION.md +## 8. Document it in `MIGRATION.md` + +The v11 guide moved twice: it was split out to `docs/migration/v11-end-state.md` for the first alpha +(#23140), then merged back into the root `MIGRATION.md` (#23623), which deleted the split-out file. Check +which one exists before editing — if `docs/migration/v11-end-state.md` is back, prefer it. -Extend the existing **"Span name changes"** section under `## 2. Behaviour Changes` — add a row to its table rather than starting a new section: +Extend the **"Span name changes"** section under `## 2. Behaviour Changes` — add a row to its table rather +than starting a new section: | Span op | Before | After | | ------- | ------------------------------------------ | ---------------------------------------------- | | `` | what the name was, with a concrete example | the route, or `` if the SDK has none | -Also note, if they apply: that `ignoreSpans` is evaluated at span **start** (so filters matching a URL no longer match a fallback-named span, and users should match on attributes instead), any child-span attribute that follows the new name, and any span of a _different_ op that inherits the name (e.g. `ui.action.click` spans are named after the current route). +Check that any anchor links you reuse (`#span-streaming-is-now-the-default`, `#opting-out-of-span-streaming`) +resolve in _this_ file before relying on them. + +Also note, if they apply: any child-span attribute that follows the new name, and any span of a _different_ +op that inherits the name (e.g. `ui.action.click` spans are named after the current route). + +### Call out config that consumes the span name + +`tracesSampler` and `ignoreSpans` both receive the span **name at span start**, so a fallback name silently +breaks any name-based matching — no error, no warning. This is not hypothetical: porting `http.server` broke +`tracesSampler: ({ name }) => name === 'GET /health' ? 0 : 1` and `ignoreSpans: [/\/health/]`, and the only +symptom was unexpected quota usage. + +It bites hardest where the old name _looked_ like a route but was really the URL — a static route like +`/health` produces an identical string either way, so users matched a path while believing they matched a +route. + +Give the migration path explicitly, and check the attribute you recommend actually exists at span start at +every site (no route attribute does — neither `http.route` nor `url.template` is set yet): + +```js +// Before +tracesSampler: ({ name, inheritOrSampleWith }) => inheritOrSampleWith(name === 'GET /health' ? 0 : 1), +ignoreSpans: [/\/health/], + +// After +tracesSampler: ({ attributes, inheritOrSampleWith }) => + inheritOrSampleWith(attributes?.['url.path'] === '/health' ? 0 : 1), +ignoreSpans: [{ attributes: { 'url.path': '/health' } }], +``` + +Fix the repo's own suites the same way — `dev-packages/node-integration-tests/suites/tracing/` has both a +`tracesSampler` and an `ignoreSpans` suite that match on name. ## Rejected approaches diff --git a/MIGRATION.md b/MIGRATION.md index af23464201f2..4afef8f93281 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -793,6 +793,7 @@ The following span names were adjusted: | Span op | Before | After | | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `pageload` | The parameterized route, or the raw URL path if the SDK couldn't resolve one (`/users/123`) | The parameterized route, or `Pageload` if the SDK has none | +| `http.server` | The request method and route, or the raw URL path if the SDK couldn't resolve one (`GET /users/123`) | `GET /users/:id` when a route is known, otherwise just the request method (`GET`) | | `router` | Framework-specific, sometimes containing the raw URL (`/users/123`, `SvelteKit Route Change`) | The span's `http.route`, or `Router` if the SDK has none | | `graphql` | The graphql phase and, for operations, the operation name (`query GetUser`, `graphql.parse`, `graphql.resolve user.0.name`) | The operation type, or the processing type where there is none (`GraphQL query`, `GraphQL parse`, `GraphQL resolve`) | | `resource.*` | The resource URL, relative to the page origin for same-origin resources (`/assets/app.js`) | The resource domain (`cdn.example.com`), or `Resource` if the SDK has none | @@ -801,6 +802,8 @@ The following span names were adjusted: Resource spans now also carry a `url.domain` attribute holding that domain. The full URL remains available on `url.full`. +`http.server` requests that resolve to a route are **unchanged** — those names were already low cardinality. Only requests the SDK cannot parameterize are affected. + Some consequences to be aware of: The graphql operation name and the resolver field path are supplied by the client, so they are no longer part of a span name. They remain available on the `graphql.operation.name` and `graphql.field.path` attributes. @@ -831,6 +834,29 @@ Sentry.init({ }); ``` +The same applies to `tracesSampler`, which also runs at span start. A web framework matches the route +_after_ that point, so an `http.server` span is named `GET` when your rule is evaluated — never +`GET /health`. Name-based rules stop matching **silently**: no error, no warning, just unexpected quota +usage. No route attribute is set at that point either, so match on `url.path`: + +```js +Sentry.init({ + // Before + tracesSampler: ({ name, inheritOrSampleWith }) => inheritOrSampleWith(name === 'GET /health' ? 0 : 1), + + // After + tracesSampler: ({ attributes, inheritOrSampleWith }) => + inheritOrSampleWith(attributes?.['url.path'] === '/health' ? 0 : 1), +}); +``` + +On `@sentry/nextjs` the incoming-request span comes from Next.js' own OpenTelemetry instrumentation, so +match on `url.full` or `http.target` if `url.path` is absent. `normalizedRequest.url` is also available on +the sampling context. + +Error grouping is **not** affected by the `http.server` change: the scope's transaction name still holds +the full `${method} ${path}`. + ### AI integrations no longer trace non-inference operations Affected SDKs: All server-side SDKs.