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' } }, + ], }); ```