From fa39c9b2f96e669b8a07f33639a5db6867943fb5 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 25 Aug 2026 17:42:56 +0200 Subject: [PATCH] docs: Document low cardinality db span names Extends the existing "Span name changes" section with the `db`/`db.query` row, the per-driver name template each instrumentation uses, and the new `db.query.summary` attribute. Kept out of the seven instrumentation PRs because each of those branches from develop, so incremental edits to this one section would conflict seven ways. Merge this last, once the behaviour it describes has landed. Refs #23523 Co-Authored-By: Claude Opus 5 (1M context) --- MIGRATION.md | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 9a7fafc9f9c4..432a86796bef 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -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: @@ -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' } }, + ], }); ```