Skip to content

Commit 862c935

Browse files
msonnbclaude
andauthored
feat(node)!: Use db.query and db span ops for Redis (#22690)
Commands and batches use `db.query` instead of `db.redis`; connect spans use `db` instead of `db.redis.connect`. The datastore is already identified by `db.system.name=redis`, so the library-specific op leaf was redundant. Command spans in the DC subscriber now also set `db.operation.name` — the command was only in `db.query.text` before. Two command paths in `tracing-channel/redis.ts` were already emitting a bare `db`; those become `db.query` too, since they carry a statement. Redis used as a cache keeps its `cache.*` ops (that classification happens in the response hook and is unaffected). Part of #22446 --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 6acb3d4 commit 862c935

12 files changed

Lines changed: 72 additions & 63 deletions

File tree

dev-packages/e2e-tests/test-applications/deno-redis/src/app.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Deno.serve({ port, hostname: '0.0.0.0' }, async (req: Request) => {
5656
}
5757

5858
// node-redis: SET then GET — exercises two commands inside a single
59-
// transaction so we can assert the parent has two db.redis children.
59+
// transaction so we can assert the parent has two db.query children.
6060
if (url.pathname === '/redis-set-get') {
6161
const key = url.searchParams.get('key') ?? 'cache:key';
6262
const value = url.searchParams.get('value') ?? 'hello';
@@ -89,7 +89,7 @@ Deno.serve({ port, hostname: '0.0.0.0' }, async (req: Request) => {
8989

9090
// ioredis: MULTI — ioredis has no separate batch channel; per-command
9191
// payloads carry `batchMode`/`batchSize` instead, so we still expect one
92-
// db.redis span per command.
92+
// db.query span per command.
9393
if (url.pathname === '/ioredis-multi') {
9494
const result = await ioredis.multi().set('iomulti:a', '1').set('iomulti:b', '2').get('iomulti:a').exec();
9595
return Response.json({ result });
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { expect, test } from '@playwright/test';
22
import { waitForTransaction } from '@sentry-internal/test-utils';
33

4-
test('ioredis GET emits an http.server transaction containing a db.redis child span', async ({ baseURL }) => {
4+
test('ioredis GET emits an http.server transaction containing a db.query child span', async ({ baseURL }) => {
55
// Each incoming request gets a Sentry http.server transaction (via the
66
// default denoServeIntegration); the ioredis command runs inside it, so the
77
// child span attaches to that transaction.
88
const transactionPromise = waitForTransaction('deno-redis', event => {
99
return (
1010
event?.contexts?.trace?.op === 'http.server' &&
1111
(event.request?.url ?? '').includes('/ioredis-get') &&
12-
(event.spans?.some(span => span.op === 'db.redis') ?? false)
12+
(event.spans?.some(span => span.op === 'db.query') ?? false)
1313
);
1414
});
1515

@@ -18,20 +18,20 @@ test('ioredis GET emits an http.server transaction containing a db.redis child s
1818
await res.json();
1919

2020
const transaction = await transactionPromise;
21-
const redisSpan = transaction.spans!.find(span => span.op === 'db.redis');
21+
const redisSpan = transaction.spans!.find(span => span.op === 'db.query');
2222
expect(redisSpan).toBeDefined();
2323
// ioredis publishes lowercase command names; node-redis publishes uppercase.
2424
expect(redisSpan!.description).toBe('redis-get');
2525
expect(redisSpan!.data?.['db.system.name']).toBe('redis');
2626
expect(redisSpan!.data?.['db.query.text']).toBe('get iocache:user:42');
2727
});
2828

29-
test('ioredis SET then GET emit two db.redis child spans on the same transaction', async ({ baseURL }) => {
29+
test('ioredis SET then GET emit two db.query child spans on the same transaction', async ({ baseURL }) => {
3030
const transactionPromise = waitForTransaction('deno-redis', event => {
3131
return (
3232
event?.contexts?.trace?.op === 'http.server' &&
3333
(event.request?.url ?? '').includes('/ioredis-set-get') &&
34-
(event.spans?.filter(span => span.op === 'db.redis').length ?? 0) >= 2
34+
(event.spans?.filter(span => span.op === 'db.query').length ?? 0) >= 2
3535
);
3636
});
3737

@@ -40,14 +40,14 @@ test('ioredis SET then GET emit two db.redis child spans on the same transaction
4040
await res.json();
4141

4242
const transaction = await transactionPromise;
43-
const redisSpans = transaction.spans!.filter(span => span.op === 'db.redis');
43+
const redisSpans = transaction.spans!.filter(span => span.op === 'db.query');
4444
expect(redisSpans.length).toBeGreaterThanOrEqual(2);
4545
const ops = redisSpans.map(s => s.description);
4646
expect(ops).toContain('redis-set');
4747
expect(ops).toContain('redis-get');
4848
});
4949

50-
test('ioredis MULTI emits one db.redis span per command (no batch channel)', async ({ baseURL }) => {
50+
test('ioredis MULTI emits one db.query span per command (no batch channel)', async ({ baseURL }) => {
5151
// ioredis does not publish to a batch channel — each command in the
5252
// transaction publishes individually with batchMode/batchSize set on its
5353
// own payload. So the transaction should contain multiple `redis-<cmd>`
@@ -56,7 +56,7 @@ test('ioredis MULTI emits one db.redis span per command (no batch channel)', asy
5656
return (
5757
event?.contexts?.trace?.op === 'http.server' &&
5858
(event.request?.url ?? '').includes('/ioredis-multi') &&
59-
(event.spans?.filter(span => span.op === 'db.redis').length ?? 0) >= 3
59+
(event.spans?.filter(span => span.op === 'db.query').length ?? 0) >= 3
6060
);
6161
});
6262

@@ -65,7 +65,7 @@ test('ioredis MULTI emits one db.redis span per command (no batch channel)', asy
6565
await res.json();
6666

6767
const transaction = await transactionPromise;
68-
const redisSpans = transaction.spans!.filter(span => span.op === 'db.redis');
68+
const redisSpans = transaction.spans!.filter(span => span.op === 'db.query');
6969
expect(redisSpans.length).toBeGreaterThanOrEqual(3);
7070
const descriptions = redisSpans.map(s => s.description);
7171
expect(descriptions).toContain('redis-set');
@@ -75,12 +75,12 @@ test('ioredis MULTI emits one db.redis span per command (no batch channel)', asy
7575
expect(batchSpan).toBeUndefined();
7676
});
7777

78-
test('ioredis PIPELINE emits one db.redis span per command', async ({ baseURL }) => {
78+
test('ioredis PIPELINE emits one db.query span per command', async ({ baseURL }) => {
7979
const transactionPromise = waitForTransaction('deno-redis', event => {
8080
return (
8181
event?.contexts?.trace?.op === 'http.server' &&
8282
(event.request?.url ?? '').includes('/ioredis-pipeline') &&
83-
(event.spans?.filter(span => span.op === 'db.redis').length ?? 0) >= 3
83+
(event.spans?.filter(span => span.op === 'db.query').length ?? 0) >= 3
8484
);
8585
});
8686

@@ -89,6 +89,6 @@ test('ioredis PIPELINE emits one db.redis span per command', async ({ baseURL })
8989
await res.json();
9090

9191
const transaction = await transactionPromise;
92-
const redisSpans = transaction.spans!.filter(span => span.op === 'db.redis');
92+
const redisSpans = transaction.spans!.filter(span => span.op === 'db.query');
9393
expect(redisSpans.length).toBeGreaterThanOrEqual(3);
9494
});

dev-packages/e2e-tests/test-applications/deno-redis/tests/redis.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { expect, test } from '@playwright/test';
22
import { waitForTransaction } from '@sentry-internal/test-utils';
33

4-
test('GET command emits an http.server transaction containing a db.redis child span', async ({ baseURL }) => {
4+
test('GET command emits an http.server transaction containing a db.query child span', async ({ baseURL }) => {
55
// Each incoming request gets a Sentry http.server transaction (via the
66
// default denoServeIntegration); the redis command runs inside it, so the
77
// child span attaches to that transaction.
88
const transactionPromise = waitForTransaction('deno-redis', event => {
99
return (
1010
event?.contexts?.trace?.op === 'http.server' &&
1111
(event.request?.url ?? '').includes('/redis-get') &&
12-
(event.spans?.some(span => span.op === 'db.redis') ?? false)
12+
(event.spans?.some(span => span.op === 'db.query') ?? false)
1313
);
1414
});
1515

@@ -18,7 +18,7 @@ test('GET command emits an http.server transaction containing a db.redis child s
1818
await res.json();
1919

2020
const transaction = await transactionPromise;
21-
const redisSpan = transaction.spans!.find(span => span.op === 'db.redis');
21+
const redisSpan = transaction.spans!.find(span => span.op === 'db.query');
2222
expect(redisSpan).toBeDefined();
2323
expect(redisSpan!.description).toBe('redis-GET');
2424
expect(redisSpan!.data?.['db.system.name']).toBe('redis');
@@ -27,12 +27,12 @@ test('GET command emits an http.server transaction containing a db.redis child s
2727
expect(redisSpan!.data?.['server.port']).toBe(6379);
2828
});
2929

30-
test('SET then GET emit two db.redis child spans on the same transaction', async ({ baseURL }) => {
30+
test('SET then GET emit two db.query child spans on the same transaction', async ({ baseURL }) => {
3131
const transactionPromise = waitForTransaction('deno-redis', event => {
3232
return (
3333
event?.contexts?.trace?.op === 'http.server' &&
3434
(event.request?.url ?? '').includes('/redis-set-get') &&
35-
(event.spans?.filter(span => span.op === 'db.redis').length ?? 0) >= 2
35+
(event.spans?.filter(span => span.op === 'db.query').length ?? 0) >= 2
3636
);
3737
});
3838

@@ -41,7 +41,7 @@ test('SET then GET emit two db.redis child spans on the same transaction', async
4141
await res.json();
4242

4343
const transaction = await transactionPromise;
44-
const redisSpans = transaction.spans!.filter(span => span.op === 'db.redis');
44+
const redisSpans = transaction.spans!.filter(span => span.op === 'db.query');
4545
expect(redisSpans.length).toBeGreaterThanOrEqual(2);
4646
const ops = redisSpans.map(s => s.description);
4747
expect(ops).toContain('redis-SET');
@@ -64,7 +64,7 @@ test('MULTI batch emits a PIPELINE/MULTI batch span', async ({ baseURL }) => {
6464
const transaction = await transactionPromise;
6565
const batchSpan = transaction.spans!.find(span => span.description === 'MULTI' || span.description === 'PIPELINE');
6666
expect(batchSpan).toBeDefined();
67-
expect(batchSpan!.op).toBe('db.redis');
67+
expect(batchSpan!.op).toBe('db.query');
6868
expect(batchSpan!.data?.['db.system.name']).toBe('redis');
6969
});
7070

dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/tests/performance/redis.server.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import { waitForTransaction } from '@sentry-internal/test-utils';
33
import { APP_NAME } from '../constants';
44

55
test.describe('server - redis db spans (instrumentation API)', () => {
6-
test('OTel db.redis spans nest under the native instrumentation-API http.server transaction', async ({ page }) => {
6+
test('OTel db.query spans nest under the native instrumentation-API http.server transaction', async ({ page }) => {
77
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
88
return (
99
transactionEvent.transaction === 'GET /performance/redis' &&
10-
(transactionEvent.spans?.some(span => span.op === 'db.redis') ?? false)
10+
(transactionEvent.spans?.some(span => span.op === 'db.query') ?? false)
1111
);
1212
});
1313

@@ -24,7 +24,7 @@ test.describe('server - redis db spans (instrumentation API)', () => {
2424
const rootSpanId = transaction.contexts?.trace?.span_id;
2525
const spanIds = new Set([rootSpanId, ...(transaction.spans ?? []).map(span => span.span_id)]);
2626

27-
const redisSpans = transaction.spans!.filter(span => span.op === 'db.redis');
27+
const redisSpans = transaction.spans!.filter(span => span.op === 'db.query');
2828

2929
// loader runs SET then GET => at least two redis command spans
3030
expect(redisSpans.length).toBeGreaterThanOrEqual(2);

dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/redis.server.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import { waitForTransaction } from '@sentry-internal/test-utils';
33
import { APP_NAME } from '../constants';
44

55
test.describe('server - redis db spans', () => {
6-
test('server loader emits db.redis child spans on the http.server transaction', async ({ page }) => {
6+
test('server loader emits db.query child spans on the http.server transaction', async ({ page }) => {
77
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
88
return (
99
transactionEvent.transaction === 'GET /performance/redis' &&
10-
(transactionEvent.spans?.some(span => span.op === 'db.redis') ?? false)
10+
(transactionEvent.spans?.some(span => span.op === 'db.query') ?? false)
1111
);
1212
});
1313

@@ -21,7 +21,7 @@ test.describe('server - redis db spans', () => {
2121
const rootSpanId = transaction.contexts?.trace?.span_id;
2222
const spanIds = new Set([rootSpanId, ...(transaction.spans ?? []).map(span => span.span_id)]);
2323

24-
const redisSpans = transaction.spans!.filter(span => span.op === 'db.redis');
24+
const redisSpans = transaction.spans!.filter(span => span.op === 'db.query');
2525

2626
// loader runs SET then GET => at least two redis command spans
2727
expect(redisSpans.length).toBeGreaterThanOrEqual(2);

dev-packages/e2e-tests/test-applications/react-router-8-framework/tests/performance/redis.server.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import { waitForTransaction } from '@sentry-internal/test-utils';
33
import { APP_NAME } from '../constants';
44

55
test.describe('server - redis db spans', () => {
6-
test('server loader emits db.redis child spans on the http.server transaction', async ({ page }) => {
6+
test('server loader emits db.query child spans on the http.server transaction', async ({ page }) => {
77
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
88
return (
99
transactionEvent.transaction === 'GET /performance/redis' &&
10-
(transactionEvent.spans?.some(span => span.op === 'db.redis') ?? false)
10+
(transactionEvent.spans?.some(span => span.op === 'db.query') ?? false)
1111
);
1212
});
1313

@@ -21,7 +21,7 @@ test.describe('server - redis db spans', () => {
2121
const rootSpanId = transaction.contexts?.trace?.span_id;
2222
const spanIds = new Set([rootSpanId, ...(transaction.spans ?? []).map(span => span.span_id)]);
2323

24-
const redisSpans = transaction.spans!.filter(span => span.op === 'db.redis');
24+
const redisSpans = transaction.spans!.filter(span => span.op === 'db.query');
2525

2626
// loader runs SET then GET => at least two redis command spans
2727
expect(redisSpans.length).toBeGreaterThanOrEqual(2);

dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ describeWithDockerCompose(
1313
transaction: 'Test Span IORedis 5.11 DC',
1414
spans: expect.arrayContaining([
1515
expect.objectContaining({
16-
op: 'db.redis',
16+
op: 'db.query',
1717
origin: 'auto.db.redis.diagnostic_channel',
1818
data: expect.objectContaining({
19-
'sentry.op': 'db.redis',
19+
'sentry.op': 'db.query',
2020
'sentry.origin': 'auto.db.redis.diagnostic_channel',
2121
'db.system.name': 'redis',
2222
'db.query.text': 'set dc-test-key ?',
@@ -45,10 +45,10 @@ describeWithDockerCompose(
4545
}),
4646
}),
4747
expect.objectContaining({
48-
op: 'db.redis',
48+
op: 'db.query',
4949
origin: 'auto.db.redis.diagnostic_channel',
5050
data: expect.objectContaining({
51-
'sentry.op': 'db.redis',
51+
'sentry.op': 'db.query',
5252
'sentry.origin': 'auto.db.redis.diagnostic_channel',
5353
'db.system.name': 'redis',
5454
'db.query.text': 'get dc-test-key',
@@ -78,10 +78,10 @@ describeWithDockerCompose(
7878
}),
7979
}),
8080
expect.objectContaining({
81-
op: 'db.redis',
81+
op: 'db.query',
8282
origin: 'auto.db.redis.diagnostic_channel',
8383
data: expect.objectContaining({
84-
'sentry.op': 'db.redis',
84+
'sentry.op': 'db.query',
8585
'sentry.origin': 'auto.db.redis.diagnostic_channel',
8686
'db.system.name': 'redis',
8787
'db.query.text': 'mget ? ? ?',

dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory
168168
? [
169169
expect.objectContaining({
170170
description: 'MULTI',
171-
op: 'db.redis',
171+
op: 'db.query',
172172
origin: redisOrigin,
173173
data: expect.objectContaining({
174174
'sentry.origin': redisOrigin,
@@ -286,7 +286,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory
286286
// a failing command produces a span with an error status
287287
expect.objectContaining({
288288
description: 'INCR redis-test-key',
289-
op: 'db',
289+
op: isOrchestrionEnabled() ? 'db.query' : 'db',
290290
status: 'internal_error',
291291
origin: redisOrigin,
292292
data: expect.objectContaining({
@@ -321,7 +321,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory
321321
? [
322322
expect.objectContaining({
323323
description: 'MULTI',
324-
op: 'db.redis',
324+
op: 'db.query',
325325
origin: redisOrigin,
326326
data: expect.objectContaining({
327327
'sentry.origin': redisOrigin,
@@ -439,7 +439,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory
439439
// a failing command produces a span with an error status
440440
expect.objectContaining({
441441
description: 'INCR redis-5-test-key',
442-
op: 'db',
442+
op: isOrchestrionEnabled() ? 'db.query' : 'db',
443443
status: 'internal_error',
444444
origin: redisOrigin,
445445
data: expect.objectContaining({

dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ describeWithDockerCompose(
1313
transaction: 'Test Span Redis 5 DC',
1414
spans: expect.arrayContaining([
1515
expect.objectContaining({
16-
op: 'db.redis',
16+
op: 'db.query',
1717
origin: 'auto.db.redis.diagnostic_channel',
1818
data: expect.objectContaining({
19-
'sentry.op': 'db.redis',
19+
'sentry.op': 'db.query',
2020
'sentry.origin': 'auto.db.redis.diagnostic_channel',
2121
'db.system.name': 'redis',
2222
'db.query.text': 'SET dc-test-key ?',
@@ -47,10 +47,10 @@ describeWithDockerCompose(
4747
}),
4848
}),
4949
expect.objectContaining({
50-
op: 'db.redis',
50+
op: 'db.query',
5151
origin: 'auto.db.redis.diagnostic_channel',
5252
data: expect.objectContaining({
53-
'sentry.op': 'db.redis',
53+
'sentry.op': 'db.query',
5454
'sentry.origin': 'auto.db.redis.diagnostic_channel',
5555
'db.system.name': 'redis',
5656
'db.query.text': 'GET dc-test-key',
@@ -82,12 +82,12 @@ describeWithDockerCompose(
8282
}),
8383
}),
8484
// MGET: node-redis sanitizes args for diagnostics_channel (keys become '?'),
85-
// so cache detection cannot match prefixes — remains a plain db.redis span.
85+
// so cache detection cannot match prefixes — remains a plain db.query span.
8686
expect.objectContaining({
87-
op: 'db.redis',
87+
op: 'db.query',
8888
origin: 'auto.db.redis.diagnostic_channel',
8989
data: expect.objectContaining({
90-
'sentry.op': 'db.redis',
90+
'sentry.op': 'db.query',
9191
'sentry.origin': 'auto.db.redis.diagnostic_channel',
9292
'db.system.name': 'redis',
9393
'db.query.text': 'MGET ? ? ?',

0 commit comments

Comments
 (0)