Skip to content
Draft
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
40 changes: 30 additions & 10 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -617,17 +617,34 @@ These changes are not caught by TypeScript. If you filter, group, or alert on sp

Affected SDKs: All SDKs.

With [span streaming](#span-streaming-is-now-the-default) enabled(the default), span names are now **low cardinality**, following the [Sentry span name conventions](https://getsentry.github.io/sentry-conventions/names/).
With [span streaming](#span-streaming-is-now-the-default) enabled (the default), span names are now **low cardinality**, following the [Sentry span name conventions](https://getsentry.github.io/sentry-conventions/names/).

In v11, this affects `pageload` and `graphql` spans. Further ops will follow in future releases.
In v11, this affects `pageload`, `graphql`, `db` and `db.query` spans. Further ops will follow in future releases.
If you [opt out of span streaming](#opting-out-of-span-streaming), span names remain unchanged.

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 |
| `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`) |
| 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 |
| `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`) |
| `db` / `db.query` | The query the driver ran (`SELECT * FROM users WHERE id = ?`) | A summary of it (`SELECT users`), see below |

For `db` and `db.query` spans, each instrumentation uses the highest-priority [name template](https://getsentry.github.io/sentry-conventions/names/#db-queries) it has the attributes for. The full statement is still reported on the span's `db.query.text` attribute in every case — only the name changed.

| Driver | Template | Example |
| ------------------------------------------------------------------------------------ | ---------------------------------------------------- | ------------------------------------------------------ |
| `pg`, `mysql`, `mysql2`, `postgres.js`, `knex`, `db0` (Nitro), Cloudflare D1, Prisma | `{db.query.summary}` | `SELECT * FROM "User" WHERE id = $1` → `SELECT "User"` |
| `mongodb` | `{db.operation.name} {db.collection.name}` | `find users` |
| Supabase queries | `{db.operation.name} {db.collection.name}` | `update users` |
| `redis`, `ioredis` | `{db.operation.name} {server.address}:{server.port}` | `set 127.0.0.1:6379` |
| `tedious` | `{db.operation.name} {db.namespace}` | `execSql tests` |

The SQL query summary is the [OpenTelemetry summary](https://opentelemetry.io/docs/specs/semconv/database/database-spans/#generating-a-summary-of-the-query) of the sanitized statement, so a statement that touches no table (`SELECT NOW()`) summarizes to the bare operation, `SELECT`. Where an instrumentation can't fill its template — no statement to summarize, no collection — it drops to the next one it can (`{db.namespace}`, then `{db.system.name}`, e.g. `mysql`), and to `Database operation` only if even that is unknown.

Every SQL instrumentation now also reports that summary as a **new `db.query.summary` attribute**, so you can group or filter by it directly. It is set regardless of `traceLifecycle`, so it is available on static spans too — those just keep the full statement as their name.

Spans that fall outside the conventions' "queries" category keep their names: connect and pool spans (`pg.connect`, `mysql2.connect`, `redis-connect`, `generic-pool.acquire`) are explicitly excluded from it, and `mongoose.{model}.{operation}`, `durable_object_storage_{method}`, Firestore and Supabase `auth` spans are left as they are because they already carry a more descriptive low-cardinality name than the templates would produce.

Some consequences to be aware of:

Expand All @@ -637,17 +654,20 @@ Because a low-cardinality name cannot say which part of request processing a spa

For the same reason, `useOperationNameForRootSpan` no longer renames the enclosing root span (`GET /graphql` stays `GET /graphql`, instead of becoming `GET /graphql (query GetUser)`). The operations are still recorded on that span's `sentry.graphql.operation` attribute, as long as the option stays enabled (the default). Disabling it skips both, as before.

Child spans of a pageload span carry its name in their `sentry.segment.name` attribute, so that changes with it. If you group or filter spans by segment name in dashboards or alerts, update those references.
Child spans of a pageload span carry its name in their `sentry.segment.name` attribute, so that changes with it. If you group or filter spans by segment name in dashboards or alerts, update those references. The same applies to a `db` span that is the segment span of its trace (e.g. a script or worker that only talks to a database).

`ignoreSpans` is evaluated when a span **starts**, at which point a pageload span without a resolved route is already named `'Pageload'`, so filters matching a URL path no longer apply to it. Match on attributes instead:
`ignoreSpans` is evaluated when a span **starts**, at which point a pageload span without a resolved route is already named `'Pageload'` and a query span is already named after its summary, so filters matching a URL path or a full SQL statement no longer apply to them. Match on attributes instead:

```js
Sentry.init({
// Before
ignoreSpans: ['/health'],
ignoreSpans: ['/health', 'SELECT * FROM health_check'],

// After
ignoreSpans: [{ name: 'Pageload', attributes: { 'sentry.op': 'pageload', 'url.path': '/health' } }],
ignoreSpans: [
{ name: 'Pageload', attributes: { 'sentry.op': 'pageload', 'url.path': '/health' } },
{ name: 'SELECT health_check', attributes: { 'db.query.text': 'SELECT * FROM health_check' } },
],
});
```

Expand Down