Skip to content
Merged
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
5 changes: 5 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,7 @@ The following span names were adjusted:
| `queue.publish` | Integration-specific (`publish my-exchange`, `send my-topic`) | The messaging operation type and the destination (`send my-exchange`), or just the operation type when the destination has no name (`send`) |
| `queue.process` | Integration-specific, sometimes containing per-message data (`my-queue process`, `order.created.12345 process`) | The messaging operation type and the destination (`process my-exchange`), or just the operation type when the destination has no name (`process`) |
| `queue.receive` | The kafkajs operation name (`poll my-topic`) | The messaging operation type and the destination (`receive my-topic`) |
| `cache.get`, `cache.put`, `cache.remove` | The cache key(s) (`user:123`), or for dataloader the operation and loader name (`dataloader.load usersLoader`) | The cache operation (`cache.get`, `cache.put`, `cache.remove`) |
| `db` (mongoose) | `mongoose.<Model>.<operation>` (`mongoose.BlogPost.findOne`) | The operation and the collection (`findOne blogposts`), the database namespace when there is no collection, or `mongodb` when the SDK has neither |

`navigation.redirect` spans are started through the same code path as navigation spans, so they get the same names.
Expand Down Expand Up @@ -959,6 +960,10 @@ A mongoose span's name is built from `db.collection.name`, so it holds the colle

Messaging span names now read `<operation type> <destination>` in every integration. The amqplib, kafkajs and NestJS BullMQ integrations used their own word order or verb, so their names change: `my-queue process` became `process my-queue`, amqplib's `publish` became `send`, and the kafkajs batch span's `poll` became `receive`. Cloudflare Queues and the kafkajs producer already matched the conventions, so their names are the same in both trace lifecycles. The operation name an integration reports upstream stays on `messaging.operation.name`.

Cache keys are unbounded, so they are no longer part of a cache span name. They remain available on the `cache.key` attribute, and every cache span now also carries a `cache.operation` attribute (`get`, `put`, `remove`) — the value the name is built from. That attribute is set in both trace lifecycles. This affects the redis/ioredis cache spans (`cachePrefixes`), the Nuxt and Nitro storage spans, and the dataloader spans.

A dataloader span no longer carries the loader's `name` either (`dataloader.load usersLoader` becomes `cache.get`), because the cache conventions have no slot for it in the name. It is reported on the `db.collection.name` attribute instead — a loader batches one entity type, so it is the closest thing dataloader has to a collection — and that attribute is set in both trace lifecycles. Unnamed loaders do not set it.

AWS SQS `SendMessage`, `SendMessageBatch` and `ReceiveMessage`, and SNS `Publish`, are messaging spans (e.g. `queue.publish`) rather than `rpc` ones now. Every other command on those clients, such as `DeleteMessage`, stays `rpc`. Their names follow the messaging conventions too, so the operation comes first (`my-queue receive` becomes `receive my-queue`, `my-topic send` becomes `send my-topic`). A streamed SNS `Publish` to a platform endpoint is named `send`, because the endpoint ARN it used to carry ends in a per-device id (`endpoint/GCM/myapp/<uuid> send`). The full ARN remains on `messaging.destination.name`.

An amqplib span's destination is the exchange it uses, or the routing key when it uses the default exchange. RabbitMQ binds every queue to the default exchange under a key equal to the queue's own name, so `sendToQueue` spans are named after their queue (`send my-queue`) instead of dropping the destination. `messaging.destination.name` reports the same value, and the routing key remains on `messaging.rabbitmq.destination.routing_key` in full.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ test.describe('Cache Instrumentation', () => {
if (cacheMissSpan) {
expect(cacheMissSpan.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.get' },
'cache.operation': { type: 'string', value: 'get' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: { type: 'boolean', value: false },
'db.operation.name': { type: 'string', value: 'getItem' },
Expand All @@ -64,6 +65,7 @@ test.describe('Cache Instrumentation', () => {
if (cacheHitSpan) {
expect(cacheHitSpan.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.get' },
'cache.operation': { type: 'string', value: 'get' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: { type: 'boolean', value: true },
'db.operation.name': { type: 'string', value: 'getItem' },
Expand All @@ -81,6 +83,7 @@ test.describe('Cache Instrumentation', () => {
if (cacheSetSpan) {
expect(cacheSetSpan.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.put' },
'cache.operation': { type: 'string', value: 'put' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
'db.operation.name': { type: 'string', value: 'setItem' },
'db.collection.name': { type: 'string', value: expect.stringMatching(/^(cache)?$/) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,35 +38,38 @@ test.describe('Storage Instrumentation - Aliases', () => {
expect(setSpan).toBeDefined();
expect(setSpan?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.put' },
'cache.operation': { type: 'string', value: 'put' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: { type: 'string', value: prefixKey('alias:user') },
'db.operation.name': { type: 'string', value: 'setItem' },
'db.collection.name': { type: 'string', value: 'test-storage' },
'db.system.name': { type: 'string', value: 'memory' },
});
expect(setSpan?.name).toBe(prefixKey('alias:user'));
expect(setSpan?.name).toBe('cache.put');

// Test get (alias for getItem)
expect(findSpansByMethod('getItem').length).toBeGreaterThanOrEqual(1);
const getSpan = findByKey('getItem', prefixKey('alias:user'));
expect(getSpan).toBeDefined();
expect(getSpan?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.get' },
'cache.operation': { type: 'string', value: 'get' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: { type: 'string', value: prefixKey('alias:user') },
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: { type: 'boolean', value: true },
'db.operation.name': { type: 'string', value: 'getItem' },
'db.collection.name': { type: 'string', value: 'test-storage' },
'db.system.name': { type: 'string', value: 'memory' },
});
expect(getSpan?.name).toBe(prefixKey('alias:user'));
expect(getSpan?.name).toBe('cache.get');

// Test has (alias for hasItem)
expect(findSpansByMethod('hasItem').length).toBeGreaterThanOrEqual(1);
const hasSpan = findByKey('hasItem', prefixKey('alias:user'));
expect(hasSpan).toBeDefined();
expect(hasSpan?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.get' },
'cache.operation': { type: 'string', value: 'get' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: { type: 'string', value: prefixKey('alias:user') },
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: { type: 'boolean', value: true },
Expand All @@ -82,25 +85,27 @@ test.describe('Storage Instrumentation - Aliases', () => {
expect(delSpan).toBeDefined();
expect(delSpan?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.remove' },
'cache.operation': { type: 'string', value: 'remove' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: { type: 'string', value: prefixKey('alias:temp1') },
'db.operation.name': { type: 'string', value: 'removeItem' },
'db.collection.name': { type: 'string', value: 'test-storage' },
'db.system.name': { type: 'string', value: 'memory' },
});
expect(delSpan?.name).toBe(prefixKey('alias:temp1'));
expect(delSpan?.name).toBe('cache.remove');

const removeSpan = findByKey('removeItem', prefixKey('alias:temp2'));
expect(removeSpan).toBeDefined();
expect(removeSpan?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.remove' },
'cache.operation': { type: 'string', value: 'remove' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: { type: 'string', value: prefixKey('alias:temp2') },
'db.operation.name': { type: 'string', value: 'removeItem' },
'db.collection.name': { type: 'string', value: 'test-storage' },
'db.system.name': { type: 'string', value: 'memory' },
});
expect(removeSpan?.name).toBe(prefixKey('alias:temp2'));
expect(removeSpan?.name).toBe('cache.remove');

// Verify all spans have OK status
expect(allStorageSpans.length).toBeGreaterThan(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ test.describe('Storage Instrumentation', () => {
expect(setItemSpan).toBeDefined();
expect(setItemSpan?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.put' },
'cache.operation': { type: 'string', value: 'put' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: { type: 'string', value: prefixKey('user:123') },
'db.operation.name': { type: 'string', value: 'setItem' },
'db.collection.name': { type: 'string', value: 'test-storage' },
'db.system.name': { type: 'string', value: 'memory' },
});

expect(setItemSpan?.name).toBe(prefixKey('user:123'));
expect(setItemSpan?.name).toBe('cache.put');

// Test setItemRaw spans
expect(findSpansByMethod('setItemRaw').length).toBeGreaterThanOrEqual(1);
Expand All @@ -54,6 +55,7 @@ test.describe('Storage Instrumentation', () => {
expect(setItemRawSpan).toBeDefined();
expect(setItemRawSpan?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.put' },
'cache.operation': { type: 'string', value: 'put' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: { type: 'string', value: prefixKey('raw:data') },
'db.operation.name': { type: 'string', value: 'setItemRaw' },
Expand All @@ -67,6 +69,7 @@ test.describe('Storage Instrumentation', () => {
expect(hasItemSpan).toBeDefined();
expect(hasItemSpan?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.get' },
'cache.operation': { type: 'string', value: 'get' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: { type: 'string', value: prefixKey('user:123') },
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: { type: 'boolean', value: true },
Expand All @@ -81,21 +84,23 @@ test.describe('Storage Instrumentation', () => {
expect(getItemSpan).toBeDefined();
expect(getItemSpan?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.get' },
'cache.operation': { type: 'string', value: 'get' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: { type: 'string', value: prefixKey('user:123') },
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: { type: 'boolean', value: true },
'db.operation.name': { type: 'string', value: 'getItem' },
'db.collection.name': { type: 'string', value: 'test-storage' },
'db.system.name': { type: 'string', value: 'memory' },
});
expect(getItemSpan?.name).toBe(prefixKey('user:123'));
expect(getItemSpan?.name).toBe('cache.get');

// Test getItemRaw spans - should have cache hit attribute
expect(findSpansByMethod('getItemRaw').length).toBeGreaterThanOrEqual(1);
const getItemRawSpan = findSpanByCacheKey('getItemRaw', prefixKey('raw:data'));
expect(getItemRawSpan).toBeDefined();
expect(getItemRawSpan?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.get' },
'cache.operation': { type: 'string', value: 'get' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: { type: 'string', value: prefixKey('raw:data') },
[SEMANTIC_ATTRIBUTE_CACHE_HIT]: { type: 'boolean', value: true },
Expand All @@ -109,6 +114,7 @@ test.describe('Storage Instrumentation', () => {
expect(getKeysSpans.length).toBeGreaterThanOrEqual(1);
expect(getKeysSpans[0]?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.get' },
'cache.operation': { type: 'string', value: 'get' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
'db.operation.name': { type: 'string', value: 'getKeys' },
'db.collection.name': { type: 'string', value: 'test-storage' },
Expand All @@ -121,6 +127,7 @@ test.describe('Storage Instrumentation', () => {
expect(removeItemSpan).toBeDefined();
expect(removeItemSpan?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.remove' },
'cache.operation': { type: 'string', value: 'remove' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: { type: 'string', value: prefixKey('batch:1') },
'db.operation.name': { type: 'string', value: 'removeItem' },
Expand All @@ -133,6 +140,7 @@ test.describe('Storage Instrumentation', () => {
expect(clearSpans.length).toBeGreaterThanOrEqual(1);
expect(clearSpans[0]?.attributes).toMatchObject({
'sentry.op': { type: 'string', value: 'cache.remove' },
'cache.operation': { type: 'string', value: 'remove' },
'sentry.origin': { type: 'string', value: 'auto.cache.nuxt' },
'db.operation.name': { type: 'string', value: 'clear' },
'db.collection.name': { type: 'string', value: 'test-storage' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
traceLifecycle: 'static',
traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SerializedStreamedSpanContainer } from '@sentry/core';
import { afterAll, describe, expect } from 'vitest';
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';

Expand Down Expand Up @@ -103,6 +104,7 @@ describe('dataloader auto-instrumentation', () => {
expect(namedLoadSpan?.op).toBe(CACHE_GET_OP);
expect(namedLoadSpan?.origin).toBe(ORIGIN);
expect(namedLoadSpan?.status).toBe('ok');
expect(namedLoadSpan?.data?.['db.collection.name']).toBe('usersLoader');
},
})
.start();
Expand All @@ -113,5 +115,37 @@ describe('dataloader auto-instrumentation', () => {
await runner.makeRequest('get', '/named');
await runner.completed();
}, 30_000);

test('names spans after the cache operation when streamed', async () => {
const runner = createRunner()
.withEnv({ STREAMED: 'true' })
.expect({
span: (container: SerializedStreamedSpanContainer) => {
const namedLoadSpan = container.items.find(
span => span.attributes?.['db.operation.name']?.value === 'load',
);
expect(namedLoadSpan?.name).toBe('cache.get');
expect(namedLoadSpan?.attributes?.['sentry.op']?.value).toBe(CACHE_GET_OP);
expect(namedLoadSpan?.attributes?.['cache.operation']?.value).toBe('get');
// The loader name is no longer part of the span name, it moved to `db.collection.name`.
expect(namedLoadSpan?.attributes?.['db.collection.name']?.value).toBe('usersLoader');
},
})
.expect({
span: (container: SerializedStreamedSpanContainer) => {
for (const [operation, op] of Object.entries(CACHE_MUTATION_OPS)) {
const span = container.items.find(item => item.attributes?.['db.operation.name']?.value === operation);
expect(span, `expected a ${operation} span`).toBeDefined();
expect(span?.name).toBe(op);
expect(span?.attributes?.['sentry.op']?.value).toBe(op);
}
},
})
.start();

await runner.makeRequest('get', '/named');
await runner.makeRequest('get', '/cache-ops');
await runner.completed();
}, 30_000);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
traceLifecycle: 'static',
traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
traceLifecycle: 'static',
traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
traceLifecycle: 'static',
traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
Expand Down
Loading
Loading