Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions .agents/skills/port-span-names/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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 |
| ------- | ------------------------------------------ | ---------------------------------------------- |
| `<op>` | what the name was, with a concrete example | the route, or `<Fallback>` 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

Expand Down
26 changes: 26 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -787,13 +787,16 @@ 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 |
| `mcp.notification.client_to_server`, `mcp.notification.server_to_client` | The notification method name (`notifications/tools/list_changed`) | The notification method name, or `MCP notification` if the message carries none |

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.
Expand Down Expand Up @@ -822,6 +825,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.
Expand Down
Loading