From eb883431a77b5dbb213a83fc16ee5be0b811746f Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Oct 2026 08:52:10 +0200 Subject: [PATCH 1/5] test(node): Port Redis integration tests to span streaming Co-Authored-By: GPT-6 --- .../node-suites/excludes.ts | 1 + .../suites/tracing/ioredis-dc/instrument.mjs | 1 - .../suites/tracing/ioredis-dc/test.ts | 276 ++--- .../redis-cache-static/docker-compose.yml | 14 + .../redis-cache-static/instrument-ioredis.mjs | 11 + .../redis-cache-static/scenario-ioredis.mjs | 34 + .../suites/tracing/redis-cache-static/test.ts | 127 +++ .../redis-cache/instrument-ioredis.mjs | 1 - .../redis-cache/instrument-redis-4.mjs | 1 - .../redis-cache/instrument-redis-5.mjs | 1 - .../suites/tracing/redis-cache/test.ts | 977 +++--------------- .../suites/tracing/redis-dc/instrument.mjs | 1 - .../suites/tracing/redis-dc/test.ts | 242 ++--- .../suites/tracing/redis/instrument.mjs | 1 - .../suites/tracing/redis/test.ts | 168 ++- 15 files changed, 584 insertions(+), 1272 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/redis-cache-static/docker-compose.yml create mode 100644 dev-packages/node-integration-tests/suites/tracing/redis-cache-static/instrument-ioredis.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/redis-cache-static/scenario-ioredis.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts diff --git a/dev-packages/bun-integration-tests/node-suites/excludes.ts b/dev-packages/bun-integration-tests/node-suites/excludes.ts index d27790a79a0f..4d8312fd7292 100644 --- a/dev-packages/bun-integration-tests/node-suites/excludes.ts +++ b/dev-packages/bun-integration-tests/node-suites/excludes.ts @@ -133,6 +133,7 @@ export const NO_AUTO_INSTRUMENTATION = [ 'suites/tracing/prisma-orm-v7/test.ts', 'suites/tracing/prisma-orm-v8/test.ts', 'suites/tracing/redis-cache/test.ts', + 'suites/tracing/redis-cache-static/test.ts', 'suites/tracing/redis-dc/test.ts', 'suites/tracing/redis/test.ts', 'suites/tracing/tedious/test.ts', diff --git a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/instrument.mjs index c0a1998369a5..db5276b8c536 100644 --- a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/instrument.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts index e6866955ca5c..939774499db8 100644 --- a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts @@ -1,5 +1,26 @@ -import { SENTRY_TRACE_LIFECYCLE } from '@sentry/conventions/attributes'; -import type { SerializedStreamedSpanContainer } from '@sentry/core'; +import { + CACHE_HIT, + CACHE_ITEM_SIZE, + CACHE_KEY, + CACHE_OPERATION, + DB_OPERATION_NAME, + DB_QUERY_TEXT, + DB_SYSTEM_NAME, + NETWORK_PEER_ADDRESS, + NETWORK_PEER_PORT, + SENTRY_ENVIRONMENT, + SENTRY_IS_LOCALHOST, + SENTRY_OP, + SENTRY_ORIGIN, + SENTRY_RELEASE, + SENTRY_SDK_NAME, + SENTRY_SDK_VERSION, + SENTRY_SEGMENT_ID, + SENTRY_SEGMENT_NAME, + SENTRY_TRACE_LIFECYCLE, + SERVER_ADDRESS, + SERVER_PORT, +} from '@sentry/conventions/attributes'; import { afterAll, describe, expect } from 'vitest'; import { EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -12,104 +33,6 @@ describeWithDockerCompose( cleanupChildProcesses(); }); - const EXPECTED_TRANSACTION = { - transaction: 'Test Span IORedis 5.11 DC', - spans: expect.arrayContaining([ - expect.objectContaining({ - op: 'db.query', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.op': 'db.query', - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.system.name': 'redis', - 'db.query.text': 'set dc-test-key ?', - }), - }), - expect.objectContaining({ - description: 'dc-cache:test-key', - op: 'cache.put', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.query.text': 'set dc-cache:test-key ?', - 'cache.key': ['dc-cache:test-key'], - 'cache.item_size': 2, - }), - }), - expect.objectContaining({ - description: 'dc-cache:test-key-ex', - op: 'cache.put', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.query.text': 'set dc-cache:test-key-ex ? ? ?', - 'cache.key': ['dc-cache:test-key-ex'], - 'cache.item_size': 2, - }), - }), - expect.objectContaining({ - op: 'db.query', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.op': 'db.query', - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.system.name': 'redis', - 'db.query.text': 'get dc-test-key', - }), - }), - expect.objectContaining({ - description: 'dc-cache:test-key', - op: 'cache.get', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.query.text': 'get dc-cache:test-key', - 'cache.hit': true, - 'cache.key': ['dc-cache:test-key'], - 'cache.item_size': 10, - }), - }), - expect.objectContaining({ - description: 'dc-cache:unavailable-data', - op: 'cache.get', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.query.text': 'get dc-cache:unavailable-data', - 'cache.hit': false, - 'cache.key': ['dc-cache:unavailable-data'], - }), - }), - expect.objectContaining({ - op: 'db.query', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.op': 'db.query', - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.system.name': 'redis', - 'db.query.text': 'mget ? ? ?', - }), - }), - ]), - }; - - const EXPECTED_CONNECT = { - transaction: 'redis-connect', - }; - - createEsmAndCjsTests(__dirname, 'scenario-ioredis-5-11.mjs', 'instrument.mjs', (createTestRunner, test) => { - test('creates spans for ioredis v5.11 commands via diagnostics_channel', { timeout: 75_000 }, async () => { - await createTestRunner() - .expect({ transaction: EXPECTED_CONNECT }) - .expect({ transaction: EXPECTED_TRANSACTION }) - .start() - .completed(); - }); - }); - - // The same commands as above, asserted on the streamed span container. With span streaming the - // db spans are named `{db.operation.name} {server.address}:{server.port}` instead of - // `redis-{command}`. ioredis reports its commands lowercase, so the name follows suit. describe('streamed', () => { const ORIGIN = 'auto.db.redis.diagnostic_channel'; const SEGMENT_NAME = 'Test Span IORedis 5.11 DC'; @@ -131,21 +54,21 @@ describeWithDockerCompose( name, attributes: { ...streamAttributes({ - 'db.system.name': 'redis', - 'sentry.environment': 'production', - 'sentry.op': op, - 'sentry.origin': ORIGIN, - 'sentry.release': '1.0', - 'sentry.sdk.name': EXPECTED_SDK_NAME, - 'sentry.segment.name': SEGMENT_NAME, - 'server.address': HOST, - 'server.port': PORT, + [DB_SYSTEM_NAME]: 'redis', + [SENTRY_ENVIRONMENT]: 'production', + [SENTRY_OP]: op, + [SENTRY_ORIGIN]: ORIGIN, + [SENTRY_RELEASE]: '1.0', + [SENTRY_SDK_NAME]: EXPECTED_SDK_NAME, + [SENTRY_SEGMENT_NAME]: SEGMENT_NAME, + [SERVER_ADDRESS]: HOST, + [SERVER_PORT]: PORT, [SENTRY_TRACE_LIFECYCLE]: 'stream', ...attributes, }), - 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, - 'sentry.segment.id': { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, - 'sentry.is_localhost': { type: 'boolean', value: false }, + [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, + [SENTRY_SEGMENT_ID]: { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, + [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, }, end_timestamp: expect.any(Number), is_segment: false, @@ -157,83 +80,78 @@ describeWithDockerCompose( }; } - const PEER = { 'network.peer.address': HOST, 'network.peer.port': PORT }; + const PEER = { [NETWORK_PEER_ADDRESS]: HOST, [NETWORK_PEER_PORT]: PORT }; // A cache span is a db span the cache hook took over: it is renamed to its cache operation // and reports the connection it inherited as peer attributes too. const cacheSpan = ( op: 'cache.get' | 'cache.put' | 'cache.remove', attributes: Record, - ): unknown => streamedSpan(op, op, { ...PEER, 'cache.operation': op.slice('cache.'.length), ...attributes }); + ): unknown => streamedSpan(op, op, { ...PEER, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); createEsmAndCjsTests(__dirname, 'scenario-ioredis-5-11.mjs', 'instrument.mjs', (createTestRunner, test) => { - test( - 'creates streamed spans for ioredis v5.11 commands via diagnostics_channel', - { timeout: 75_000 }, - async () => { - await createTestRunner() - .withEnv({ STREAMED: 'true' }) - .expect({ - span: (container: SerializedStreamedSpanContainer) => { - // The connect span opens its own segment but shares the trace with the test span, - // so both segments arrive in the same container. - expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([ - 'redis-connect', - SEGMENT_NAME, - ]); + test('creates spans for ioredis v5.11 commands via diagnostics_channel', { timeout: 75_000 }, async () => { + await createTestRunner() + .expect({ + span: container => { + // The connect span opens its own segment but shares the trace with the test span, + // so both segments arrive in the same container. + expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([ + 'redis-connect', + SEGMENT_NAME, + ]); - // ioredis' own handshake commands (`client SETINFO`, `info`) are emitted on the - // channel too, but belong to the connect segment — the test span's children are the - // commands the scenario issues. - const spans = container.items.filter( - item => !item.is_segment && item.attributes['sentry.segment.name']?.value === SEGMENT_NAME, - ); + // ioredis' own handshake commands (`client SETINFO`, `info`) are emitted on the + // channel too, but belong to the connect segment — the test span's children are the + // commands the scenario issues. + const spans = container.items.filter( + item => !item.is_segment && item.attributes[SENTRY_SEGMENT_NAME]?.value === SEGMENT_NAME, + ); - expect(spans).toEqual([ - streamedSpan(`set ${HOST}:${PORT}`, 'db.query', { - 'db.operation.name': 'set', - 'db.query.text': 'set dc-test-key ?', - }), - cacheSpan('cache.put', { - 'db.operation.name': 'set', - 'db.query.text': 'set dc-cache:test-key ?', - 'cache.key': ['dc-cache:test-key'], - 'cache.item_size': 2, - }), - cacheSpan('cache.put', { - 'db.operation.name': 'set', - 'db.query.text': 'set dc-cache:test-key-ex ? ? ?', - 'cache.key': ['dc-cache:test-key-ex'], - 'cache.item_size': 2, - }), - streamedSpan(`get ${HOST}:${PORT}`, 'db.query', { - 'db.operation.name': 'get', - 'db.query.text': 'get dc-test-key', - }), - cacheSpan('cache.get', { - 'db.operation.name': 'get', - 'db.query.text': 'get dc-cache:test-key', - 'cache.key': ['dc-cache:test-key'], - 'cache.hit': true, - 'cache.item_size': 10, - }), - cacheSpan('cache.get', { - 'db.operation.name': 'get', - 'db.query.text': 'get dc-cache:unavailable-data', - 'cache.key': ['dc-cache:unavailable-data'], - 'cache.hit': false, - }), - streamedSpan(`mget ${HOST}:${PORT}`, 'db.query', { - 'db.operation.name': 'mget', - 'db.query.text': 'mget ? ? ?', - }), - ]); - }, - }) - .start() - .completed(); - }, - ); + expect(spans).toEqual([ + streamedSpan(`set ${HOST}:${PORT}`, 'db.query', { + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set dc-test-key ?', + }), + cacheSpan('cache.put', { + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set dc-cache:test-key ?', + [CACHE_KEY]: ['dc-cache:test-key'], + [CACHE_ITEM_SIZE]: 2, + }), + cacheSpan('cache.put', { + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set dc-cache:test-key-ex ? ? ?', + [CACHE_KEY]: ['dc-cache:test-key-ex'], + [CACHE_ITEM_SIZE]: 2, + }), + streamedSpan(`get ${HOST}:${PORT}`, 'db.query', { + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get dc-test-key', + }), + cacheSpan('cache.get', { + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get dc-cache:test-key', + [CACHE_KEY]: ['dc-cache:test-key'], + [CACHE_HIT]: true, + [CACHE_ITEM_SIZE]: 10, + }), + cacheSpan('cache.get', { + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get dc-cache:unavailable-data', + [CACHE_KEY]: ['dc-cache:unavailable-data'], + [CACHE_HIT]: false, + }), + streamedSpan(`mget ${HOST}:${PORT}`, 'db.query', { + [DB_OPERATION_NAME]: 'mget', + [DB_QUERY_TEXT]: 'mget ? ? ?', + }), + ]); + }, + }) + .start() + .completed(); + }); }); }); }, diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/docker-compose.yml new file mode 100644 index 000000000000..1196c2651032 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.9' + +services: + db: + image: redis:latest + restart: always + ports: + - '6384:6379' + healthcheck: + test: ['CMD-SHELL', 'redis-cli ping | grep -q PONG'] + interval: 2s + timeout: 3s + retries: 30 + start_period: 5s diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/instrument-ioredis.mjs b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/instrument-ioredis.mjs new file mode 100644 index 000000000000..9732fa703714 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/instrument-ioredis.mjs @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + integrations: [Sentry.redisIntegration({ cachePrefixes: ['ioredis-cache:'] })], +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/scenario-ioredis.mjs b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/scenario-ioredis.mjs new file mode 100644 index 000000000000..daabe69f38e3 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/scenario-ioredis.mjs @@ -0,0 +1,34 @@ +import * as Sentry from '@sentry/node'; +import Redis from 'ioredis'; + +const redis = new Redis({ port: 6384 }); + +async function run() { + await Sentry.startSpan( + { + name: 'Test Span', + op: 'test-span', + }, + async () => { + try { + await redis.set('test-key', 'test-value'); + await redis.set('ioredis-cache:test-key', 'test-value'); + + await redis.set('ioredis-cache:test-key-set-EX', 'test-value', 'EX', 10); + await redis.setex('ioredis-cache:test-key-setex', 10, 'test-value'); + + await redis.get('test-key'); + await redis.get('ioredis-cache:test-key'); + await redis.get('ioredis-cache:unavailable-data'); + + await redis.mget('test-key', 'ioredis-cache:test-key', 'ioredis-cache:unavailable-data'); + + await redis.del('ioredis-cache:test-key'); + } finally { + await redis.disconnect(); + } + }, + ); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts new file mode 100644 index 000000000000..139cb232e397 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts @@ -0,0 +1,127 @@ +import { afterAll, expect } from 'vitest'; +import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; + +describeWithDockerCompose('redis cache static instrumentation', { workingDirectory: [__dirname] }, () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + const redisOrigin = 'auto.db.redis'; + + const EXPECTED_TRANSACTION = { + transaction: 'Test Span', + spans: expect.arrayContaining([ + // SET + expect.objectContaining({ + description: 'ioredis-cache:test-key', + op: 'cache.put', + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'set ioredis-cache:test-key [1 other arguments]', + 'cache.operation': 'put', + 'cache.key': ['ioredis-cache:test-key'], + 'cache.item_size': 2, + 'network.peer.address': 'localhost', + 'network.peer.port': 6384, + }), + }), + // SET (with EX) + expect.objectContaining({ + description: 'ioredis-cache:test-key-set-EX', + op: 'cache.put', + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'set ioredis-cache:test-key-set-EX [3 other arguments]', + 'cache.operation': 'put', + 'cache.key': ['ioredis-cache:test-key-set-EX'], + 'cache.item_size': 2, + 'network.peer.address': 'localhost', + 'network.peer.port': 6384, + }), + }), + // SETEX + expect.objectContaining({ + description: 'ioredis-cache:test-key-setex', + op: 'cache.put', + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'setex ioredis-cache:test-key-setex [2 other arguments]', + 'cache.operation': 'put', + 'cache.key': ['ioredis-cache:test-key-setex'], + 'cache.item_size': 2, + 'network.peer.address': 'localhost', + 'network.peer.port': 6384, + }), + }), + // GET + expect.objectContaining({ + description: 'ioredis-cache:test-key', + op: 'cache.get', + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'get ioredis-cache:test-key', + 'cache.operation': 'get', + 'cache.hit': true, + 'cache.key': ['ioredis-cache:test-key'], + 'cache.item_size': 10, + 'network.peer.address': 'localhost', + 'network.peer.port': 6384, + }), + }), + // GET (unavailable - no cache hit) + expect.objectContaining({ + description: 'ioredis-cache:unavailable-data', + op: 'cache.get', + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'get ioredis-cache:unavailable-data', + 'cache.operation': 'get', + 'cache.hit': false, + 'cache.key': ['ioredis-cache:unavailable-data'], + 'network.peer.address': 'localhost', + 'network.peer.port': 6384, + }), + }), + // MGET + expect.objectContaining({ + description: 'test-key, ioredis-cache:test-key, ioredis-cache:unavailable-data', + op: 'cache.get', + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'mget [3 other arguments]', + 'cache.operation': 'get', + 'cache.hit': true, + 'cache.key': ['test-key', 'ioredis-cache:test-key', 'ioredis-cache:unavailable-data'], + 'network.peer.address': 'localhost', + 'network.peer.port': 6384, + }), + }), + // DEL + expect.objectContaining({ + description: 'ioredis-cache:test-key', + op: 'cache.remove', + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'del ioredis-cache:test-key', + 'cache.operation': 'remove', + 'cache.key': ['ioredis-cache:test-key'], + 'network.peer.address': 'localhost', + 'network.peer.port': 6384, + }), + }), + ]), + }; + + createEsmAndCjsTests(__dirname, 'scenario-ioredis.mjs', 'instrument-ioredis.mjs', (createTestRunner, test) => { + test('should create cache spans for prefixed keys (ioredis)', { timeout: 60_000 }, async () => { + await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + }); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-ioredis.mjs b/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-ioredis.mjs index 8b1385e92375..4a6ca8a42ec4 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-ioredis.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-ioredis.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-redis-4.mjs b/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-redis-4.mjs index 9b24191fd0a1..df9d0ca876df 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-redis-4.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-redis-4.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-redis-5.mjs b/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-redis-5.mjs index 58f4a0875acd..285078fd57e7 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-redis-5.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-redis-5.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts index 2e8de3443a55..774787c1b85b 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts @@ -1,4 +1,30 @@ -import { SENTRY_TRACE_LIFECYCLE } from '@sentry/conventions/attributes'; +import { + CACHE_HIT, + CACHE_ITEM_SIZE, + CACHE_KEY, + CACHE_OPERATION, + DB_OPERATION_BATCH_SIZE, + DB_OPERATION_NAME, + DB_QUERY_TEXT, + DB_SYSTEM_NAME, + ERROR_TYPE, + NETWORK_PEER_ADDRESS, + NETWORK_PEER_PORT, + SENTRY_ENVIRONMENT, + SENTRY_IS_LOCALHOST, + SENTRY_KIND, + SENTRY_OP, + SENTRY_ORIGIN, + SENTRY_RELEASE, + SENTRY_SDK_NAME, + SENTRY_SDK_VERSION, + SENTRY_SEGMENT_ID, + SENTRY_SEGMENT_NAME, + SENTRY_STATUS_MESSAGE, + SENTRY_TRACE_LIFECYCLE, + SERVER_ADDRESS, + SERVER_PORT, +} from '@sentry/conventions/attributes'; import type { SerializedStreamedSpanContainer } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; import { EXPECTED_SDK_NAME } from '../../../utils'; @@ -12,683 +38,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory const redisOrigin = 'auto.db.redis'; const redisSpanOp = 'db.query'; - describe('ioredis non-cache keys', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Span', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'set test-key [1 other arguments]', - op: redisSpanOp, - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.op': redisSpanOp, - 'db.system.name': 'redis', - 'server.address': 'localhost', - 'server.port': 6383, - 'db.query.text': 'set test-key [1 other arguments]', - }), - }), - expect.objectContaining({ - description: 'get test-key', - op: redisSpanOp, - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.op': redisSpanOp, - 'db.system.name': 'redis', - 'server.address': 'localhost', - 'server.port': 6383, - 'db.query.text': 'get test-key', - }), - }), - ]), - }; - - createEsmAndCjsTests(__dirname, 'scenario-ioredis.mjs', 'instrument-ioredis.mjs', (createTestRunner, test) => { - test('should not add cache spans when key is not prefixed', { timeout: 60_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); - }); - }); - }); - - describe('ioredis cache keys', () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Span', - spans: expect.arrayContaining([ - // SET - expect.objectContaining({ - description: 'ioredis-cache:test-key', - op: 'cache.put', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'set ioredis-cache:test-key [1 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['ioredis-cache:test-key'], - 'cache.item_size': 2, - 'network.peer.address': 'localhost', - 'network.peer.port': 6383, - }), - }), - // SET (with EX) - expect.objectContaining({ - description: 'ioredis-cache:test-key-set-EX', - op: 'cache.put', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'set ioredis-cache:test-key-set-EX [3 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['ioredis-cache:test-key-set-EX'], - 'cache.item_size': 2, - 'network.peer.address': 'localhost', - 'network.peer.port': 6383, - }), - }), - // SETEX - expect.objectContaining({ - description: 'ioredis-cache:test-key-setex', - op: 'cache.put', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'setex ioredis-cache:test-key-setex [2 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['ioredis-cache:test-key-setex'], - 'cache.item_size': 2, - 'network.peer.address': 'localhost', - 'network.peer.port': 6383, - }), - }), - // GET - expect.objectContaining({ - description: 'ioredis-cache:test-key', - op: 'cache.get', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'get ioredis-cache:test-key', - 'cache.operation': 'get', - 'cache.hit': true, - 'cache.key': ['ioredis-cache:test-key'], - 'cache.item_size': 10, - 'network.peer.address': 'localhost', - 'network.peer.port': 6383, - }), - }), - // GET (unavailable - no cache hit) - expect.objectContaining({ - description: 'ioredis-cache:unavailable-data', - op: 'cache.get', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'get ioredis-cache:unavailable-data', - 'cache.operation': 'get', - 'cache.hit': false, - 'cache.key': ['ioredis-cache:unavailable-data'], - 'network.peer.address': 'localhost', - 'network.peer.port': 6383, - }), - }), - // MGET - expect.objectContaining({ - description: 'test-key, ioredis-cache:test-key, ioredis-cache:unavailable-data', - op: 'cache.get', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'mget [3 other arguments]', - 'cache.operation': 'get', - 'cache.hit': true, - 'cache.key': ['test-key', 'ioredis-cache:test-key', 'ioredis-cache:unavailable-data'], - 'network.peer.address': 'localhost', - 'network.peer.port': 6383, - }), - }), - // DEL - expect.objectContaining({ - description: 'ioredis-cache:test-key', - op: 'cache.remove', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'del ioredis-cache:test-key', - 'cache.operation': 'remove', - 'cache.key': ['ioredis-cache:test-key'], - 'network.peer.address': 'localhost', - 'network.peer.port': 6383, - }), - }), - ]), - }; - - // Same commands as above, but streamed: the cache key is gone from the span name and only - // `cache.key` still holds it. - const EXPECTED_STREAMED_SPANS = expect.arrayContaining([ - // SET - expect.objectContaining({ - name: 'cache.put', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.origin': { type: 'string', value: redisOrigin }, - 'sentry.op': { type: 'string', value: 'cache.put' }, - 'db.query.text': { type: 'string', value: 'set ioredis-cache:test-key [1 other arguments]' }, - 'cache.operation': { type: 'string', value: 'put' }, - 'cache.key': { type: 'array', value: ['ioredis-cache:test-key'] }, - 'cache.item_size': { type: 'integer', value: 2 }, - }), - }), - // SETEX - expect.objectContaining({ - name: 'cache.put', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.op': { type: 'string', value: 'cache.put' }, - 'db.query.text': { type: 'string', value: 'setex ioredis-cache:test-key-setex [2 other arguments]' }, - 'cache.operation': { type: 'string', value: 'put' }, - 'cache.key': { type: 'array', value: ['ioredis-cache:test-key-setex'] }, - }), - }), - // GET - expect.objectContaining({ - name: 'cache.get', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.origin': { type: 'string', value: redisOrigin }, - 'sentry.op': { type: 'string', value: 'cache.get' }, - 'db.query.text': { type: 'string', value: 'get ioredis-cache:test-key' }, - 'cache.operation': { type: 'string', value: 'get' }, - 'cache.hit': { type: 'boolean', value: true }, - 'cache.key': { type: 'array', value: ['ioredis-cache:test-key'] }, - 'cache.item_size': { type: 'integer', value: 10 }, - }), - }), - // MGET - expect.objectContaining({ - name: 'cache.get', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.op': { type: 'string', value: 'cache.get' }, - 'db.query.text': { type: 'string', value: 'mget [3 other arguments]' }, - 'cache.operation': { type: 'string', value: 'get' }, - 'cache.key': { - type: 'array', - value: ['test-key', 'ioredis-cache:test-key', 'ioredis-cache:unavailable-data'], - }, - }), - }), - // DEL - expect.objectContaining({ - name: 'cache.remove', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.origin': { type: 'string', value: redisOrigin }, - 'sentry.op': { type: 'string', value: 'cache.remove' }, - 'db.query.text': { type: 'string', value: 'del ioredis-cache:test-key' }, - 'cache.operation': { type: 'string', value: 'remove' }, - 'cache.key': { type: 'array', value: ['ioredis-cache:test-key'] }, - }), - }), - ]); - - createEsmAndCjsTests(__dirname, 'scenario-ioredis.mjs', 'instrument-ioredis.mjs', (createTestRunner, test) => { - test('should create cache spans for prefixed keys (ioredis)', { timeout: 60_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); - }); - - test('should name cache spans after the cache operation when streamed (ioredis)', async () => { - await createTestRunner() - .withEnv({ STREAMED: 'true' }) - .expect({ span: { items: EXPECTED_STREAMED_SPANS } }) - .start() - .completed(); - }); - }); - }); - - describe('redis-4 cache keys', () => { - const EXPECTED_REDIS_CONNECT = { - transaction: 'redis-connect', - }; - - const batchSpans = [ - expect.objectContaining({ - description: 'MULTI', - op: 'db.query', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.system.name': 'redis', - 'db.operation.batch.size': 2, - }), - }), - ]; - - const EXPECTED_TRANSACTION = { - transaction: 'Test Span Redis 4', - spans: expect.arrayContaining([ - // SET - expect.objectContaining({ - description: 'redis-cache:test-key', - op: 'cache.put', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'SET redis-cache:test-key [1 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['redis-cache:test-key'], - 'cache.item_size': 2, - }), - }), - // SET (with EX) - expect.objectContaining({ - description: 'redis-cache:test-key-set-EX', - op: 'cache.put', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'SET redis-cache:test-key-set-EX [3 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['redis-cache:test-key-set-EX'], - 'cache.item_size': 2, - }), - }), - // SETEX - expect.objectContaining({ - description: 'redis-cache:test-key-setex', - op: 'cache.put', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'SETEX redis-cache:test-key-setex [2 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['redis-cache:test-key-setex'], - 'cache.item_size': 2, - }), - }), - // GET - expect.objectContaining({ - description: 'redis-cache:test-key', - op: 'cache.get', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'GET redis-cache:test-key', - 'cache.operation': 'get', - 'cache.hit': true, - 'cache.key': ['redis-cache:test-key'], - 'cache.item_size': 10, - }), - }), - // GET (unavailable - no cache hit) - expect.objectContaining({ - description: 'redis-cache:unavailable-data', - op: 'cache.get', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'GET redis-cache:unavailable-data', - 'cache.operation': 'get', - 'cache.hit': false, - 'cache.key': ['redis-cache:unavailable-data'], - }), - }), - // MGET - expect.objectContaining({ - description: 'redis-test-key, redis-cache:test-key, redis-cache:unavailable-data', - op: 'cache.get', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'MGET [3 other arguments]', - 'cache.operation': 'get', - 'cache.hit': true, - 'cache.key': ['redis-test-key', 'redis-cache:test-key', 'redis-cache:unavailable-data'], - }), - }), - // DEL - expect.objectContaining({ - description: 'redis-cache:test-key', - op: 'cache.remove', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'DEL redis-cache:test-key', - 'cache.operation': 'remove', - 'cache.key': ['redis-cache:test-key'], - }), - }), - ...batchSpans, - // a failing command produces a span with an error status - expect.objectContaining({ - description: 'INCR redis-test-key', - op: 'db.query', - status: 'internal_error', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.system.name': 'redis', - 'db.query.text': 'INCR redis-test-key', - }), - }), - ]), - }; - - const EXPECTED_STREAMED_SPANS = expect.arrayContaining([ - // SET - expect.objectContaining({ - name: 'cache.put', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.origin': { type: 'string', value: redisOrigin }, - 'sentry.op': { type: 'string', value: 'cache.put' }, - 'db.query.text': { type: 'string', value: 'SET redis-cache:test-key [1 other arguments]' }, - 'cache.operation': { type: 'string', value: 'put' }, - 'cache.key': { type: 'array', value: ['redis-cache:test-key'] }, - 'cache.item_size': { type: 'integer', value: 2 }, - }), - }), - // SETEX - expect.objectContaining({ - name: 'cache.put', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.op': { type: 'string', value: 'cache.put' }, - 'db.query.text': { type: 'string', value: 'SETEX redis-cache:test-key-setex [2 other arguments]' }, - 'cache.operation': { type: 'string', value: 'put' }, - 'cache.key': { type: 'array', value: ['redis-cache:test-key-setex'] }, - }), - }), - // GET - expect.objectContaining({ - name: 'cache.get', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.origin': { type: 'string', value: redisOrigin }, - 'sentry.op': { type: 'string', value: 'cache.get' }, - 'db.query.text': { type: 'string', value: 'GET redis-cache:test-key' }, - 'cache.operation': { type: 'string', value: 'get' }, - 'cache.hit': { type: 'boolean', value: true }, - 'cache.key': { type: 'array', value: ['redis-cache:test-key'] }, - 'cache.item_size': { type: 'integer', value: 10 }, - }), - }), - // MGET - expect.objectContaining({ - name: 'cache.get', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.op': { type: 'string', value: 'cache.get' }, - 'db.query.text': { type: 'string', value: 'MGET [3 other arguments]' }, - 'cache.operation': { type: 'string', value: 'get' }, - 'cache.key': { - type: 'array', - value: ['redis-test-key', 'redis-cache:test-key', 'redis-cache:unavailable-data'], - }, - }), - }), - // DEL - expect.objectContaining({ - name: 'cache.remove', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.origin': { type: 'string', value: redisOrigin }, - 'sentry.op': { type: 'string', value: 'cache.remove' }, - 'db.query.text': { type: 'string', value: 'DEL redis-cache:test-key' }, - 'cache.operation': { type: 'string', value: 'remove' }, - 'cache.key': { type: 'array', value: ['redis-cache:test-key'] }, - }), - }), - ]); - - createEsmAndCjsTests(__dirname, 'scenario-redis-4.mjs', 'instrument-redis-4.mjs', (createTestRunner, test) => { - test('should create cache spans for prefixed keys (redis-4)', async () => { - await createTestRunner() - .expect({ transaction: EXPECTED_REDIS_CONNECT }) - .expect({ transaction: EXPECTED_TRANSACTION }) - .start() - .completed(); - }); - - test('should name cache spans after the cache operation when streamed (redis-4)', async () => { - await createTestRunner() - .withEnv({ STREAMED: 'true' }) - // The connect span opens its own segment. `unordered` keeps this expectation from - // depending on which segment the buffer happens to emit first. - .unordered() - .expect({ span: { items: EXPECTED_STREAMED_SPANS } }) - .start() - .completed(); - }); - }); - }); - - // node-redis 5.0-5.11 still flows through the vendored monkey-patch - // instrumentation (diagnostics_channel was only added in 5.12.0), so this - // exercises the `>=5.0.0 <5.12.0` branch of the vendored RedisInstrumentation. - describe('redis-5 cache keys', () => { - const EXPECTED_REDIS_CONNECT = { - transaction: 'redis-connect', - }; - - const batchSpans = [ - expect.objectContaining({ - description: 'MULTI', - op: 'db.query', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.system.name': 'redis', - 'db.operation.batch.size': 2, - }), - }), - ]; - - const EXPECTED_TRANSACTION = { - transaction: 'Test Span Redis 5', - spans: expect.arrayContaining([ - // SET - expect.objectContaining({ - description: 'redis-5-cache:test-key', - op: 'cache.put', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'SET redis-5-cache:test-key [1 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['redis-5-cache:test-key'], - 'cache.item_size': 2, - }), - }), - // SET (with EX) - expect.objectContaining({ - description: 'redis-5-cache:test-key-set-EX', - op: 'cache.put', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'SET redis-5-cache:test-key-set-EX [3 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['redis-5-cache:test-key-set-EX'], - 'cache.item_size': 2, - }), - }), - // SETEX - expect.objectContaining({ - description: 'redis-5-cache:test-key-setex', - op: 'cache.put', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'SETEX redis-5-cache:test-key-setex [2 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['redis-5-cache:test-key-setex'], - 'cache.item_size': 2, - }), - }), - // GET - expect.objectContaining({ - description: 'redis-5-cache:test-key', - op: 'cache.get', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'GET redis-5-cache:test-key', - 'cache.operation': 'get', - 'cache.hit': true, - 'cache.key': ['redis-5-cache:test-key'], - 'cache.item_size': 10, - }), - }), - // GET (unavailable - no cache hit) - expect.objectContaining({ - description: 'redis-5-cache:unavailable-data', - op: 'cache.get', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'GET redis-5-cache:unavailable-data', - 'cache.operation': 'get', - 'cache.hit': false, - 'cache.key': ['redis-5-cache:unavailable-data'], - }), - }), - // MGET - expect.objectContaining({ - description: 'redis-5-test-key, redis-5-cache:test-key, redis-5-cache:unavailable-data', - op: 'cache.get', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'MGET [3 other arguments]', - 'cache.operation': 'get', - 'cache.hit': true, - 'cache.key': ['redis-5-test-key', 'redis-5-cache:test-key', 'redis-5-cache:unavailable-data'], - }), - }), - // DEL - expect.objectContaining({ - description: 'redis-5-cache:test-key', - op: 'cache.remove', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'DEL redis-5-cache:test-key', - 'cache.operation': 'remove', - 'cache.key': ['redis-5-cache:test-key'], - }), - }), - ...batchSpans, - // a failing command produces a span with an error status - expect.objectContaining({ - description: 'INCR redis-5-test-key', - op: 'db.query', - status: 'internal_error', - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.system.name': 'redis', - 'db.query.text': 'INCR redis-5-test-key', - }), - }), - ]), - }; - - const EXPECTED_STREAMED_SPANS = expect.arrayContaining([ - // SET - expect.objectContaining({ - name: 'cache.put', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.origin': { type: 'string', value: redisOrigin }, - 'sentry.op': { type: 'string', value: 'cache.put' }, - 'db.query.text': { type: 'string', value: 'SET redis-5-cache:test-key [1 other arguments]' }, - 'cache.operation': { type: 'string', value: 'put' }, - 'cache.key': { type: 'array', value: ['redis-5-cache:test-key'] }, - 'cache.item_size': { type: 'integer', value: 2 }, - }), - }), - // SETEX - expect.objectContaining({ - name: 'cache.put', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.op': { type: 'string', value: 'cache.put' }, - 'db.query.text': { type: 'string', value: 'SETEX redis-5-cache:test-key-setex [2 other arguments]' }, - 'cache.operation': { type: 'string', value: 'put' }, - 'cache.key': { type: 'array', value: ['redis-5-cache:test-key-setex'] }, - }), - }), - // GET - expect.objectContaining({ - name: 'cache.get', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.origin': { type: 'string', value: redisOrigin }, - 'sentry.op': { type: 'string', value: 'cache.get' }, - 'db.query.text': { type: 'string', value: 'GET redis-5-cache:test-key' }, - 'cache.operation': { type: 'string', value: 'get' }, - 'cache.hit': { type: 'boolean', value: true }, - 'cache.key': { type: 'array', value: ['redis-5-cache:test-key'] }, - 'cache.item_size': { type: 'integer', value: 10 }, - }), - }), - // MGET - expect.objectContaining({ - name: 'cache.get', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.op': { type: 'string', value: 'cache.get' }, - 'db.query.text': { type: 'string', value: 'MGET [3 other arguments]' }, - 'cache.operation': { type: 'string', value: 'get' }, - 'cache.key': { - type: 'array', - value: ['redis-5-test-key', 'redis-5-cache:test-key', 'redis-5-cache:unavailable-data'], - }, - }), - }), - // DEL - expect.objectContaining({ - name: 'cache.remove', - is_segment: false, - attributes: expect.objectContaining({ - 'sentry.origin': { type: 'string', value: redisOrigin }, - 'sentry.op': { type: 'string', value: 'cache.remove' }, - 'db.query.text': { type: 'string', value: 'DEL redis-5-cache:test-key' }, - 'cache.operation': { type: 'string', value: 'remove' }, - 'cache.key': { type: 'array', value: ['redis-5-cache:test-key'] }, - }), - }), - ]); - - createEsmAndCjsTests(__dirname, 'scenario-redis-5.mjs', 'instrument-redis-5.mjs', (createTestRunner, test) => { - test('should create cache spans for prefixed keys (redis-5)', async () => { - await createTestRunner() - .expect({ transaction: EXPECTED_REDIS_CONNECT }) - .expect({ transaction: EXPECTED_TRANSACTION }) - .start() - .completed(); - }); - - test('should name cache spans after the cache operation when streamed (redis-5)', async () => { - await createTestRunner() - .withEnv({ STREAMED: 'true' }) - // The connect span opens its own segment. `unordered` keeps this expectation from - // depending on which segment the buffer happens to emit first. - .unordered() - .expect({ span: { items: EXPECTED_STREAMED_SPANS } }) - .start() - .completed(); - }); - }); - }); - describe('streamed', () => { - // The blocks above assert the same commands as transactions. With span streaming, span names - // have to be low cardinality, so `db.query` spans drop the serialized statement from their - // name — it stays on `db.query.text` — and are named - // `{db.operation.name} {server.address}:{server.port}` instead. Cache spans are named after - // their cache operation, and batch spans keep their `MULTI`/`PIPELINE` name. const streamAttribute = (value: unknown): { type: string; value: unknown } => ({ type: Array.isArray(value) ? 'array' : Number.isInteger(value) ? 'integer' : typeof value, value, @@ -701,18 +51,18 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory const commonAttributes = (segmentName: string): Record => ({ ...streamAttributes({ - 'db.system.name': 'redis', - 'sentry.environment': 'production', - 'sentry.kind': 'client', - 'sentry.origin': redisOrigin, - 'sentry.release': '1.0', - 'sentry.sdk.name': EXPECTED_SDK_NAME, - 'sentry.segment.name': segmentName, + [DB_SYSTEM_NAME]: 'redis', + [SENTRY_ENVIRONMENT]: 'production', + [SENTRY_KIND]: 'client', + [SENTRY_ORIGIN]: redisOrigin, + [SENTRY_RELEASE]: '1.0', + [SENTRY_SDK_NAME]: EXPECTED_SDK_NAME, + [SENTRY_SEGMENT_NAME]: segmentName, [SENTRY_TRACE_LIFECYCLE]: 'stream', }), - 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, - 'sentry.is_localhost': { type: 'boolean', value: false }, - 'sentry.segment.id': { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, + [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, + [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, + [SENTRY_SEGMENT_ID]: { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, }); function streamedSpan({ @@ -732,7 +82,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory name, attributes: { ...commonAttributes(segmentName), - ...streamAttributes({ 'sentry.op': op, ...attributes }), + ...streamAttributes({ [SENTRY_OP]: op, ...attributes }), }, end_timestamp: expect.any(Number), is_segment: false, @@ -749,8 +99,8 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory describe('ioredis', () => { const segmentName = 'Test Span'; - const connection = { 'server.address': 'localhost', 'server.port': 6383 }; - const peer = { 'network.peer.address': 'localhost', 'network.peer.port': 6383 }; + const connection = { [SERVER_ADDRESS]: 'localhost', [SERVER_PORT]: 6383 }; + const peer = { [NETWORK_PEER_ADDRESS]: 'localhost', [NETWORK_PEER_PORT]: 6383 }; const span = (name: string, op: string, attributes: Record, status?: string): unknown => streamedSpan({ name, op, segmentName, status, attributes: { ...connection, ...attributes } }); @@ -760,67 +110,66 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory const cacheSpan = ( op: 'cache.get' | 'cache.put' | 'cache.remove', attributes: Record, - ): unknown => span(op, op, { ...peer, 'cache.operation': op.slice('cache.'.length), ...attributes }); + ): unknown => span(op, op, { ...peer, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); createEsmAndCjsTests(__dirname, 'scenario-ioredis.mjs', 'instrument-ioredis.mjs', (createTestRunner, test) => { - test('creates streamed db and cache spans (ioredis)', { timeout: 60_000 }, async () => { + test('should create cache spans for prefixed keys (ioredis)', { timeout: 60_000 }, async () => { await createTestRunner() - .withEnv({ STREAMED: 'true' }) .expect({ - span: (container: SerializedStreamedSpanContainer) => { + span: container => { expect(container.items.find(item => item.is_segment)?.name).toBe(segmentName); expect(childSpans(container)).toEqual([ span('set localhost:6383', redisSpanOp, { - 'db.operation.name': 'set', - 'db.query.text': 'set test-key [1 other arguments]', + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set test-key [1 other arguments]', }), cacheSpan('cache.put', { - 'db.operation.name': 'set', - 'db.query.text': 'set ioredis-cache:test-key [1 other arguments]', - 'cache.key': ['ioredis-cache:test-key'], - 'cache.item_size': 2, + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set ioredis-cache:test-key [1 other arguments]', + [CACHE_KEY]: ['ioredis-cache:test-key'], + [CACHE_ITEM_SIZE]: 2, }), cacheSpan('cache.put', { - 'db.operation.name': 'set', - 'db.query.text': 'set ioredis-cache:test-key-set-EX [3 other arguments]', - 'cache.key': ['ioredis-cache:test-key-set-EX'], - 'cache.item_size': 2, + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set ioredis-cache:test-key-set-EX [3 other arguments]', + [CACHE_KEY]: ['ioredis-cache:test-key-set-EX'], + [CACHE_ITEM_SIZE]: 2, }), cacheSpan('cache.put', { - 'db.operation.name': 'setex', - 'db.query.text': 'setex ioredis-cache:test-key-setex [2 other arguments]', - 'cache.key': ['ioredis-cache:test-key-setex'], - 'cache.item_size': 2, + [DB_OPERATION_NAME]: 'setex', + [DB_QUERY_TEXT]: 'setex ioredis-cache:test-key-setex [2 other arguments]', + [CACHE_KEY]: ['ioredis-cache:test-key-setex'], + [CACHE_ITEM_SIZE]: 2, }), span('get localhost:6383', redisSpanOp, { - 'db.operation.name': 'get', - 'db.query.text': 'get test-key', + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get test-key', }), cacheSpan('cache.get', { - 'db.operation.name': 'get', - 'db.query.text': 'get ioredis-cache:test-key', - 'cache.key': ['ioredis-cache:test-key'], - 'cache.hit': true, - 'cache.item_size': 10, + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get ioredis-cache:test-key', + [CACHE_KEY]: ['ioredis-cache:test-key'], + [CACHE_HIT]: true, + [CACHE_ITEM_SIZE]: 10, }), cacheSpan('cache.get', { - 'db.operation.name': 'get', - 'db.query.text': 'get ioredis-cache:unavailable-data', - 'cache.key': ['ioredis-cache:unavailable-data'], - 'cache.hit': false, + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get ioredis-cache:unavailable-data', + [CACHE_KEY]: ['ioredis-cache:unavailable-data'], + [CACHE_HIT]: false, }), cacheSpan('cache.get', { - 'db.operation.name': 'mget', - 'db.query.text': 'mget [3 other arguments]', - 'cache.key': ['test-key', 'ioredis-cache:test-key', 'ioredis-cache:unavailable-data'], - 'cache.hit': true, - 'cache.item_size': 20, + [DB_OPERATION_NAME]: 'mget', + [DB_QUERY_TEXT]: 'mget [3 other arguments]', + [CACHE_KEY]: ['test-key', 'ioredis-cache:test-key', 'ioredis-cache:unavailable-data'], + [CACHE_HIT]: true, + [CACHE_ITEM_SIZE]: 20, }), cacheSpan('cache.remove', { - 'db.operation.name': 'del', - 'db.query.text': 'del ioredis-cache:test-key', - 'cache.key': ['ioredis-cache:test-key'], + [DB_OPERATION_NAME]: 'del', + [DB_QUERY_TEXT]: 'del ioredis-cache:test-key', + [CACHE_KEY]: ['ioredis-cache:test-key'], }), ]); }, @@ -835,8 +184,8 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory // `{db.operation.name} {server.address}:{server.port}` name. describe('redis-4', () => { const segmentName = 'Test Span Redis 4'; - const connection = { 'server.address': 'localhost', 'server.port': 6383 }; - const peer = { 'network.peer.address': 'localhost', 'network.peer.port': 6383 }; + const connection = { [SERVER_ADDRESS]: 'localhost', [SERVER_PORT]: 6383 }; + const peer = { [NETWORK_PEER_ADDRESS]: 'localhost', [NETWORK_PEER_PORT]: 6383 }; const span = (name: string, op: string, attributes: Record, status?: string): unknown => streamedSpan({ name, op, segmentName, status, attributes: { ...connection, ...attributes } }); @@ -846,14 +195,13 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory const cacheSpan = ( op: 'cache.get' | 'cache.put' | 'cache.remove', attributes: Record, - ): unknown => span(op, op, { ...peer, 'cache.operation': op.slice('cache.'.length), ...attributes }); + ): unknown => span(op, op, { ...peer, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); createEsmAndCjsTests(__dirname, 'scenario-redis-4.mjs', 'instrument-redis-4.mjs', (createTestRunner, test) => { - test('creates streamed db and cache spans (redis-4)', { timeout: 60_000 }, async () => { + test('should create cache spans for prefixed keys (redis-4)', { timeout: 60_000 }, async () => { await createTestRunner() - .withEnv({ STREAMED: 'true' }) .expect({ - span: (container: SerializedStreamedSpanContainer) => { + span: container => { // The connect span opens its own segment, but shares the trace with the test span, // so both segments arrive in the same container. expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([ @@ -863,66 +211,66 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory expect(childSpans(container)).toEqual([ span('SET localhost:6383', redisSpanOp, { - 'db.operation.name': 'SET', - 'db.query.text': 'SET redis-test-key [1 other arguments]', + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET redis-test-key [1 other arguments]', }), cacheSpan('cache.put', { - 'db.operation.name': 'SET', - 'db.query.text': 'SET redis-cache:test-key [1 other arguments]', - 'cache.key': ['redis-cache:test-key'], - 'cache.item_size': 2, + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET redis-cache:test-key [1 other arguments]', + [CACHE_KEY]: ['redis-cache:test-key'], + [CACHE_ITEM_SIZE]: 2, }), cacheSpan('cache.put', { - 'db.operation.name': 'SET', - 'db.query.text': 'SET redis-cache:test-key-set-EX [3 other arguments]', - 'cache.key': ['redis-cache:test-key-set-EX'], - 'cache.item_size': 2, + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET redis-cache:test-key-set-EX [3 other arguments]', + [CACHE_KEY]: ['redis-cache:test-key-set-EX'], + [CACHE_ITEM_SIZE]: 2, }), cacheSpan('cache.put', { - 'db.operation.name': 'SETEX', - 'db.query.text': 'SETEX redis-cache:test-key-setex [2 other arguments]', - 'cache.key': ['redis-cache:test-key-setex'], - 'cache.item_size': 2, + [DB_OPERATION_NAME]: 'SETEX', + [DB_QUERY_TEXT]: 'SETEX redis-cache:test-key-setex [2 other arguments]', + [CACHE_KEY]: ['redis-cache:test-key-setex'], + [CACHE_ITEM_SIZE]: 2, }), span('GET localhost:6383', redisSpanOp, { - 'db.operation.name': 'GET', - 'db.query.text': 'GET redis-test-key', + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET redis-test-key', }), cacheSpan('cache.get', { - 'db.operation.name': 'GET', - 'db.query.text': 'GET redis-cache:test-key', - 'cache.key': ['redis-cache:test-key'], - 'cache.hit': true, - 'cache.item_size': 10, + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET redis-cache:test-key', + [CACHE_KEY]: ['redis-cache:test-key'], + [CACHE_HIT]: true, + [CACHE_ITEM_SIZE]: 10, }), cacheSpan('cache.get', { - 'db.operation.name': 'GET', - 'db.query.text': 'GET redis-cache:unavailable-data', - 'cache.key': ['redis-cache:unavailable-data'], - 'cache.hit': false, + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET redis-cache:unavailable-data', + [CACHE_KEY]: ['redis-cache:unavailable-data'], + [CACHE_HIT]: false, }), cacheSpan('cache.get', { - 'db.operation.name': 'MGET', - 'db.query.text': 'MGET [3 other arguments]', - 'cache.key': ['redis-test-key', 'redis-cache:test-key', 'redis-cache:unavailable-data'], - 'cache.hit': true, - 'cache.item_size': 20, + [DB_OPERATION_NAME]: 'MGET', + [DB_QUERY_TEXT]: 'MGET [3 other arguments]', + [CACHE_KEY]: ['redis-test-key', 'redis-cache:test-key', 'redis-cache:unavailable-data'], + [CACHE_HIT]: true, + [CACHE_ITEM_SIZE]: 20, }), cacheSpan('cache.remove', { - 'db.operation.name': 'DEL', - 'db.query.text': 'DEL redis-cache:test-key', - 'cache.key': ['redis-cache:test-key'], + [DB_OPERATION_NAME]: 'DEL', + [DB_QUERY_TEXT]: 'DEL redis-cache:test-key', + [CACHE_KEY]: ['redis-cache:test-key'], }), // Batch spans are named after the batch operation, which is already low cardinality. - span('MULTI', redisSpanOp, { 'db.operation.name': 'MULTI', 'db.operation.batch.size': 2 }), + span('MULTI', redisSpanOp, { [DB_OPERATION_NAME]: 'MULTI', [DB_OPERATION_BATCH_SIZE]: 2 }), span( 'INCR localhost:6383', redisSpanOp, { - 'db.operation.name': 'INCR', - 'db.query.text': 'INCR redis-test-key', - 'error.type': 'Error', - 'sentry.status.message': 'ERR value is not an integer or out of range', + [DB_OPERATION_NAME]: 'INCR', + [DB_QUERY_TEXT]: 'INCR redis-test-key', + [ERROR_TYPE]: 'Error', + [SENTRY_STATUS_MESSAGE]: 'ERR value is not an integer or out of range', }, 'error', ), @@ -940,8 +288,8 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory // connection and get the same span name. describe('redis-5', () => { const segmentName = 'Test Span Redis 5'; - const connection = { 'server.address': 'localhost', 'server.port': 6383 }; - const peer = { 'network.peer.address': 'localhost', 'network.peer.port': 6383 }; + const connection = { [SERVER_ADDRESS]: 'localhost', [SERVER_PORT]: 6383 }; + const peer = { [NETWORK_PEER_ADDRESS]: 'localhost', [NETWORK_PEER_PORT]: 6383 }; const span = (name: string, op: string, attributes: Record, status?: string): unknown => streamedSpan({ name, op, segmentName, status, attributes: { ...connection, ...attributes } }); @@ -951,14 +299,13 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory const cacheSpan = ( op: 'cache.get' | 'cache.put' | 'cache.remove', attributes: Record, - ): unknown => span(op, op, { ...peer, 'cache.operation': op.slice('cache.'.length), ...attributes }); + ): unknown => span(op, op, { ...peer, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); createEsmAndCjsTests(__dirname, 'scenario-redis-5.mjs', 'instrument-redis-5.mjs', (createTestRunner, test) => { - test('creates streamed db and cache spans (redis-5)', { timeout: 60_000 }, async () => { + test('should create cache spans for prefixed keys (redis-5)', { timeout: 60_000 }, async () => { await createTestRunner() - .withEnv({ STREAMED: 'true' }) .expect({ - span: (container: SerializedStreamedSpanContainer) => { + span: container => { expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([ 'redis-connect', segmentName, @@ -966,65 +313,65 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory expect(childSpans(container)).toEqual([ span('SET localhost:6383', redisSpanOp, { - 'db.operation.name': 'SET', - 'db.query.text': 'SET redis-5-test-key [1 other arguments]', + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET redis-5-test-key [1 other arguments]', }), cacheSpan('cache.put', { - 'db.operation.name': 'SET', - 'db.query.text': 'SET redis-5-cache:test-key [1 other arguments]', - 'cache.key': ['redis-5-cache:test-key'], - 'cache.item_size': 2, + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET redis-5-cache:test-key [1 other arguments]', + [CACHE_KEY]: ['redis-5-cache:test-key'], + [CACHE_ITEM_SIZE]: 2, }), cacheSpan('cache.put', { - 'db.operation.name': 'SET', - 'db.query.text': 'SET redis-5-cache:test-key-set-EX [3 other arguments]', - 'cache.key': ['redis-5-cache:test-key-set-EX'], - 'cache.item_size': 2, + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET redis-5-cache:test-key-set-EX [3 other arguments]', + [CACHE_KEY]: ['redis-5-cache:test-key-set-EX'], + [CACHE_ITEM_SIZE]: 2, }), cacheSpan('cache.put', { - 'db.operation.name': 'SETEX', - 'db.query.text': 'SETEX redis-5-cache:test-key-setex [2 other arguments]', - 'cache.key': ['redis-5-cache:test-key-setex'], - 'cache.item_size': 2, + [DB_OPERATION_NAME]: 'SETEX', + [DB_QUERY_TEXT]: 'SETEX redis-5-cache:test-key-setex [2 other arguments]', + [CACHE_KEY]: ['redis-5-cache:test-key-setex'], + [CACHE_ITEM_SIZE]: 2, }), span('GET localhost:6383', redisSpanOp, { - 'db.operation.name': 'GET', - 'db.query.text': 'GET redis-5-test-key', + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET redis-5-test-key', }), cacheSpan('cache.get', { - 'db.operation.name': 'GET', - 'db.query.text': 'GET redis-5-cache:test-key', - 'cache.key': ['redis-5-cache:test-key'], - 'cache.hit': true, - 'cache.item_size': 10, + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET redis-5-cache:test-key', + [CACHE_KEY]: ['redis-5-cache:test-key'], + [CACHE_HIT]: true, + [CACHE_ITEM_SIZE]: 10, }), cacheSpan('cache.get', { - 'db.operation.name': 'GET', - 'db.query.text': 'GET redis-5-cache:unavailable-data', - 'cache.key': ['redis-5-cache:unavailable-data'], - 'cache.hit': false, + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET redis-5-cache:unavailable-data', + [CACHE_KEY]: ['redis-5-cache:unavailable-data'], + [CACHE_HIT]: false, }), cacheSpan('cache.get', { - 'db.operation.name': 'MGET', - 'db.query.text': 'MGET [3 other arguments]', - 'cache.key': ['redis-5-test-key', 'redis-5-cache:test-key', 'redis-5-cache:unavailable-data'], - 'cache.hit': true, - 'cache.item_size': 20, + [DB_OPERATION_NAME]: 'MGET', + [DB_QUERY_TEXT]: 'MGET [3 other arguments]', + [CACHE_KEY]: ['redis-5-test-key', 'redis-5-cache:test-key', 'redis-5-cache:unavailable-data'], + [CACHE_HIT]: true, + [CACHE_ITEM_SIZE]: 20, }), cacheSpan('cache.remove', { - 'db.operation.name': 'DEL', - 'db.query.text': 'DEL redis-5-cache:test-key', - 'cache.key': ['redis-5-cache:test-key'], + [DB_OPERATION_NAME]: 'DEL', + [DB_QUERY_TEXT]: 'DEL redis-5-cache:test-key', + [CACHE_KEY]: ['redis-5-cache:test-key'], }), - span('MULTI', redisSpanOp, { 'db.operation.name': 'MULTI', 'db.operation.batch.size': 2 }), + span('MULTI', redisSpanOp, { [DB_OPERATION_NAME]: 'MULTI', [DB_OPERATION_BATCH_SIZE]: 2 }), span( 'INCR localhost:6383', redisSpanOp, { - 'db.operation.name': 'INCR', - 'db.query.text': 'INCR redis-5-test-key', - 'error.type': 'Error', - 'sentry.status.message': 'ERR value is not an integer or out of range', + [DB_OPERATION_NAME]: 'INCR', + [DB_QUERY_TEXT]: 'INCR redis-5-test-key', + [ERROR_TYPE]: 'Error', + [SENTRY_STATUS_MESSAGE]: 'ERR value is not an integer or out of range', }, 'error', ), diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-dc/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/redis-dc/instrument.mjs index d4f8cc7801fc..28e109af9952 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-dc/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/redis-dc/instrument.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts index 06d8cac5a465..92479ae33874 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts @@ -1,5 +1,28 @@ -import { SENTRY_TRACE_LIFECYCLE } from '@sentry/conventions/attributes'; -import type { SerializedStreamedSpanContainer } from '@sentry/core'; +import { + CACHE_HIT, + CACHE_ITEM_SIZE, + CACHE_KEY, + CACHE_OPERATION, + DB_OPERATION_NAME, + DB_QUERY_TEXT, + DB_SYSTEM_NAME, + ERROR_TYPE, + NETWORK_PEER_ADDRESS, + NETWORK_PEER_PORT, + SENTRY_ENVIRONMENT, + SENTRY_IS_LOCALHOST, + SENTRY_OP, + SENTRY_ORIGIN, + SENTRY_RELEASE, + SENTRY_SDK_NAME, + SENTRY_SDK_VERSION, + SENTRY_SEGMENT_ID, + SENTRY_SEGMENT_NAME, + SENTRY_STATUS_MESSAGE, + SENTRY_TRACE_LIFECYCLE, + SERVER_ADDRESS, + SERVER_PORT, +} from '@sentry/conventions/attributes'; import { afterAll, describe, expect } from 'vitest'; import { EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -12,131 +35,18 @@ describeWithDockerCompose( cleanupChildProcesses(); }); - const EXPECTED_TRANSACTION = { - transaction: 'Test Span Redis 5 DC', - spans: expect.arrayContaining([ - expect.objectContaining({ - op: 'db.query', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.op': 'db.query', - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.system.name': 'redis', - 'db.query.text': 'SET dc-test-key ?', - }), - }), - // cache SET: starts as a cache span, named by its key in the static lifecycle - expect.objectContaining({ - description: 'dc-cache:test-key', - op: 'cache.put', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.query.text': 'SET dc-cache:test-key ?', - 'cache.key': ['dc-cache:test-key'], - 'cache.item_size': 2, - }), - }), - // cache SET with EX option: redis v5 sends SET key value EX 10 as the command - expect.objectContaining({ - description: 'dc-cache:test-key-ex', - op: 'cache.put', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.query.text': 'SET dc-cache:test-key-ex ? ? ?', - 'cache.key': ['dc-cache:test-key-ex'], - 'cache.item_size': 2, - }), - }), - expect.objectContaining({ - op: 'db.query', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.op': 'db.query', - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.system.name': 'redis', - 'db.query.text': 'GET dc-test-key', - }), - }), - // cache GET (hit) - expect.objectContaining({ - description: 'dc-cache:test-key', - op: 'cache.get', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.query.text': 'GET dc-cache:test-key', - 'cache.hit': true, - 'cache.key': ['dc-cache:test-key'], - 'cache.item_size': 10, - }), - }), - // cache GET (miss) - expect.objectContaining({ - description: 'dc-cache:unavailable-data', - op: 'cache.get', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.query.text': 'GET dc-cache:unavailable-data', - 'cache.hit': false, - 'cache.key': ['dc-cache:unavailable-data'], - }), - }), - // MGET: node-redis sanitizes args for diagnostics_channel (keys become '?'), - // so cache detection cannot match prefixes — remains a plain db.query span. - expect.objectContaining({ - op: 'db.query', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'sentry.op': 'db.query', - 'sentry.origin': 'auto.db.redis.diagnostic_channel', - 'db.system.name': 'redis', - 'db.query.text': 'MGET ? ? ?', - }), - }), - // a failing command on a cache key reports as an errored cache span: - // the span starts as a cache span, so the classification survives the error - expect.objectContaining({ - description: 'dc-cache:list-key', - op: 'cache.get', - status: 'internal_error', - origin: 'auto.db.redis.diagnostic_channel', - data: expect.objectContaining({ - 'cache.operation': 'get', - 'cache.key': ['dc-cache:list-key'], - }), - }), - ]), - }; - - // node-redis emits a node-redis:connect DC event for the initial connection. - // That fires before startSpan so it arrives as the first envelope. - const EXPECTED_CONNECT = { - transaction: 'redis-connect', - }; - createEsmAndCjsTests(__dirname, 'scenario-redis-5-tracing.mjs', 'instrument.mjs', (createTestRunner, test) => { - test('should create spans for redis v5 commands via diagnostics_channel', { timeout: 60_000 }, async () => { - await createTestRunner() - .expect({ transaction: EXPECTED_CONNECT }) - .expect({ transaction: EXPECTED_TRANSACTION }) - .start() - .completed(); - }); - // `ignoreSpans` is evaluated at span start under streaming, so this only passes because the // span starts as a cache span — a db span renamed at response time would slip through. test('drops cache spans matching an ignoreSpans op filter at span start', { timeout: 60_000 }, async () => { await createTestRunner() - .withEnv({ STREAMED: 'true', IGNORE_CACHE_GET: 'true' }) + .withEnv({ IGNORE_CACHE_GET: 'true' }) .unignore('client_report') // The span container and the client report flush on independent timers, so they can // arrive in either order. .unordered() .expect({ - span: (container: SerializedStreamedSpanContainer) => { + span: container => { const names = container.items.map(item => item.name); expect(names).toContain('cache.put'); expect(names).not.toContain('cache.get'); @@ -155,9 +65,6 @@ describeWithDockerCompose( }); }); - // The same commands as above, asserted on the streamed span container. With span streaming the - // db spans are named `{db.operation.name} {server.address}:{server.port}` instead of - // `redis-{command}`. describe('streamed', () => { const ORIGIN = 'auto.db.redis.diagnostic_channel'; const SEGMENT_NAME = 'Test Span Redis 5 DC'; @@ -179,21 +86,21 @@ describeWithDockerCompose( name, attributes: { ...streamAttributes({ - 'db.system.name': 'redis', - 'sentry.environment': 'production', - 'sentry.op': op, - 'sentry.origin': ORIGIN, - 'sentry.release': '1.0', - 'sentry.sdk.name': EXPECTED_SDK_NAME, - 'sentry.segment.name': SEGMENT_NAME, - 'server.address': HOST, - 'server.port': PORT, + [DB_SYSTEM_NAME]: 'redis', + [SENTRY_ENVIRONMENT]: 'production', + [SENTRY_OP]: op, + [SENTRY_ORIGIN]: ORIGIN, + [SENTRY_RELEASE]: '1.0', + [SENTRY_SDK_NAME]: EXPECTED_SDK_NAME, + [SENTRY_SEGMENT_NAME]: SEGMENT_NAME, + [SERVER_ADDRESS]: HOST, + [SERVER_PORT]: PORT, [SENTRY_TRACE_LIFECYCLE]: 'stream', ...attributes, }), - 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, - 'sentry.segment.id': { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, - 'sentry.is_localhost': { type: 'boolean', value: false }, + [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, + [SENTRY_SEGMENT_ID]: { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, + [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, }, end_timestamp: expect.any(Number), is_segment: false, @@ -205,21 +112,20 @@ describeWithDockerCompose( }; } - const PEER = { 'network.peer.address': HOST, 'network.peer.port': PORT }; + const PEER = { [NETWORK_PEER_ADDRESS]: HOST, [NETWORK_PEER_PORT]: PORT }; // A cache span is a db span whose key matched a cache prefix: it starts named after its // cache operation and reports the connection as peer attributes too. const cacheSpan = ( op: 'cache.get' | 'cache.put' | 'cache.remove', attributes: Record, - ): unknown => streamedSpan(op, op, { ...PEER, 'cache.operation': op.slice('cache.'.length), ...attributes }); + ): unknown => streamedSpan(op, op, { ...PEER, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); createEsmAndCjsTests(__dirname, 'scenario-redis-5-tracing.mjs', 'instrument.mjs', (createTestRunner, test) => { - test('creates streamed spans for redis v5 commands via diagnostics_channel', { timeout: 60_000 }, async () => { + test('should create spans for redis v5 commands via diagnostics_channel', { timeout: 60_000 }, async () => { await createTestRunner() - .withEnv({ STREAMED: 'true' }) .expect({ - span: (container: SerializedStreamedSpanContainer) => { + span: container => { // The connect span opens its own segment but shares the trace with the test span, // so both segments arrive in the same container. expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([ @@ -228,66 +134,66 @@ describeWithDockerCompose( ]); const spans = container.items.filter( - item => !item.is_segment && item.attributes['sentry.segment.name']?.value === SEGMENT_NAME, + item => !item.is_segment && item.attributes[SENTRY_SEGMENT_NAME]?.value === SEGMENT_NAME, ); expect(spans).toEqual([ streamedSpan(`SET ${HOST}:${PORT}`, 'db.query', { - 'db.operation.name': 'SET', - 'db.query.text': 'SET dc-test-key ?', + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET dc-test-key ?', }), // cache SET: starts as a cache span cacheSpan('cache.put', { - 'db.operation.name': 'SET', - 'db.query.text': 'SET dc-cache:test-key ?', - 'cache.key': ['dc-cache:test-key'], - 'cache.item_size': 2, + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET dc-cache:test-key ?', + [CACHE_KEY]: ['dc-cache:test-key'], + [CACHE_ITEM_SIZE]: 2, }), // cache SET with EX option: redis v5 sends SET key value EX 10 as the command cacheSpan('cache.put', { - 'db.operation.name': 'SET', - 'db.query.text': 'SET dc-cache:test-key-ex ? ? ?', - 'cache.key': ['dc-cache:test-key-ex'], - 'cache.item_size': 2, + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET dc-cache:test-key-ex ? ? ?', + [CACHE_KEY]: ['dc-cache:test-key-ex'], + [CACHE_ITEM_SIZE]: 2, }), streamedSpan(`GET ${HOST}:${PORT}`, 'db.query', { - 'db.operation.name': 'GET', - 'db.query.text': 'GET dc-test-key', + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET dc-test-key', }), // cache GET (hit) cacheSpan('cache.get', { - 'db.operation.name': 'GET', - 'db.query.text': 'GET dc-cache:test-key', - 'cache.key': ['dc-cache:test-key'], - 'cache.hit': true, - 'cache.item_size': 10, + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET dc-cache:test-key', + [CACHE_KEY]: ['dc-cache:test-key'], + [CACHE_HIT]: true, + [CACHE_ITEM_SIZE]: 10, }), // cache GET (miss) cacheSpan('cache.get', { - 'db.operation.name': 'GET', - 'db.query.text': 'GET dc-cache:unavailable-data', - 'cache.key': ['dc-cache:unavailable-data'], - 'cache.hit': false, + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET dc-cache:unavailable-data', + [CACHE_KEY]: ['dc-cache:unavailable-data'], + [CACHE_HIT]: false, }), // MGET: node-redis sanitizes args for diagnostics_channel (keys become '?'), // so cache detection cannot match prefixes — remains a plain db.query span. streamedSpan(`MGET ${HOST}:${PORT}`, 'db.query', { - 'db.operation.name': 'MGET', - 'db.query.text': 'MGET ? ? ?', + [DB_OPERATION_NAME]: 'MGET', + [DB_QUERY_TEXT]: 'MGET ? ? ?', }), streamedSpan(`LPUSH ${HOST}:${PORT}`, 'db.query', { - 'db.operation.name': 'LPUSH', - 'db.query.text': 'LPUSH dc-cache:list-key ?', + [DB_OPERATION_NAME]: 'LPUSH', + [DB_QUERY_TEXT]: 'LPUSH dc-cache:list-key ?', }), // a failing command on a cache key reports as an errored cache span: // the span starts as a cache span, so the classification survives the error { ...(cacheSpan('cache.get', { - 'db.operation.name': 'GET', - 'db.query.text': 'GET dc-cache:list-key', - 'cache.key': ['dc-cache:list-key'], - 'error.type': 'Error', - 'sentry.status.message': 'WRONGTYPE Operation against a key holding the wrong kind of value', + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET dc-cache:list-key', + [CACHE_KEY]: ['dc-cache:list-key'], + [ERROR_TYPE]: 'Error', + [SENTRY_STATUS_MESSAGE]: 'WRONGTYPE Operation against a key holding the wrong kind of value', }) as Record), status: 'error', }, diff --git a/dev-packages/node-integration-tests/suites/tracing/redis/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/redis/instrument.mjs index 22bf57f14364..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/redis/instrument.mjs @@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ - traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/redis/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis/test.ts index d8b8b728a2af..a06b8a133459 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis/test.ts @@ -1,5 +1,23 @@ -import { SENTRY_TRACE_LIFECYCLE, SENTRY_OP } from '@sentry/conventions/attributes'; -import { type SerializedStreamedSpanContainer } from '@sentry/core'; +import { + DB_OPERATION_NAME, + DB_QUERY_TEXT, + DB_SYSTEM_NAME, + ERROR_TYPE, + SENTRY_ENVIRONMENT, + SENTRY_IS_LOCALHOST, + SENTRY_KIND, + SENTRY_OP, + SENTRY_ORIGIN, + SENTRY_RELEASE, + SENTRY_SDK_NAME, + SENTRY_SDK_VERSION, + SENTRY_SEGMENT_ID, + SENTRY_SEGMENT_NAME, + SENTRY_STATUS_MESSAGE, + SENTRY_TRACE_LIFECYCLE, + SERVER_ADDRESS, + SERVER_PORT, +} from '@sentry/conventions/attributes'; import { afterAll, describe, expect } from 'vitest'; import { EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -14,82 +32,21 @@ describeWithDockerCompose('redis auto instrumentation', { workingDirectory: [__d // other attributes are identical. const origin = 'auto.db.redis'; const redisSpanOp = 'db.query'; - const redisData = { - 'db.system.name': 'redis', - 'server.address': 'localhost', - 'server.port': 6380, - }; - - const EXPECTED_TRANSACTION = { - transaction: 'Test Span', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: 'set test-key [1 other arguments]', - op: redisSpanOp, - origin, - data: expect.objectContaining({ - 'sentry.op': redisSpanOp, - 'sentry.origin': origin, - ...redisData, - 'db.query.text': 'set test-key [1 other arguments]', - }), - }), - expect.objectContaining({ - description: 'get test-key', - op: redisSpanOp, - origin, - data: expect.objectContaining({ - 'sentry.op': redisSpanOp, - 'sentry.origin': origin, - ...redisData, - 'db.query.text': 'get test-key', - }), - }), - // a failing command produces a span with an error status - expect.objectContaining({ - description: 'incr test-key', - op: redisSpanOp, - status: 'internal_error', - origin, - data: expect.objectContaining({ - 'sentry.op': redisSpanOp, - 'sentry.origin': origin, - ...redisData, - 'db.query.text': 'incr test-key', - }), - }), - ]), - }; - - createEsmAndCjsTests(__dirname, 'scenario-ioredis.mjs', 'instrument.mjs', (createTestRunner, test) => { - test( - 'should auto-instrument `ioredis` package when using redis.set() and redis.get()', - { timeout: 75_000 }, - async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); - }, - ); - }); - describe('streamed', () => { - // The same three commands as above, asserted on the streamed span container. Only the span - // name differs: with span streaming names have to be low cardinality, so the serialized - // statement is reported through `db.query.text` alone and the name becomes - // `{db.operation.name} {server.address}:{server.port}`. const COMMON_ATTRIBUTES = { - 'sentry.is_localhost': { type: 'boolean', value: false }, - 'db.system.name': { type: 'string', value: 'redis' }, - 'server.address': { type: 'string', value: 'localhost' }, - 'server.port': { type: 'integer', value: 6380 }, - 'sentry.kind': { type: 'string', value: 'client' }, - 'sentry.environment': { type: 'string', value: 'production' }, - 'sentry.op': { type: 'string', value: redisSpanOp }, - 'sentry.origin': { type: 'string', value: origin }, - 'sentry.release': { type: 'string', value: '1.0' }, - 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, - 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, - 'sentry.segment.id': { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, - 'sentry.segment.name': { type: 'string', value: 'Test Span' }, + [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'redis' }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 6380 }, + [SENTRY_KIND]: { type: 'string', value: 'client' }, + [SENTRY_ENVIRONMENT]: { type: 'string', value: 'production' }, + [SENTRY_OP]: { type: 'string', value: redisSpanOp }, + [SENTRY_ORIGIN]: { type: 'string', value: origin }, + [SENTRY_RELEASE]: { type: 'string', value: '1.0' }, + [SENTRY_SDK_NAME]: { type: 'string', value: EXPECTED_SDK_NAME }, + [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, + [SENTRY_SEGMENT_ID]: { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, + [SENTRY_SEGMENT_NAME]: { type: 'string', value: 'Test Span' }, [SENTRY_TRACE_LIFECYCLE]: { type: 'string', value: 'stream' }, }; @@ -107,12 +64,12 @@ describeWithDockerCompose('redis auto instrumentation', { workingDirectory: [__d return { attributes: { ...COMMON_ATTRIBUTES, - 'db.operation.name': { type: 'string', value: operation }, - 'db.query.text': { type: 'string', value: statement }, + [DB_OPERATION_NAME]: { type: 'string', value: operation }, + [DB_QUERY_TEXT]: { type: 'string', value: statement }, ...(errorMessage ? { - 'error.type': { type: 'string', value: 'ReplyError' }, - 'sentry.status.message': { type: 'string', value: errorMessage }, + [ERROR_TYPE]: { type: 'string', value: 'ReplyError' }, + [SENTRY_STATUS_MESSAGE]: { type: 'string', value: errorMessage }, } : {}), }, @@ -128,32 +85,35 @@ describeWithDockerCompose('redis auto instrumentation', { workingDirectory: [__d } createEsmAndCjsTests(__dirname, 'scenario-ioredis.mjs', 'instrument.mjs', (createTestRunner, test) => { - test('should auto-instrument `ioredis` package with span streaming enabled', { timeout: 75_000 }, async () => { - await createTestRunner() - .withEnv({ STREAMED: 'true' }) - .expect({ - span: (container: SerializedStreamedSpanContainer) => { - const segmentSpan = container.items.find(item => item.is_segment); - expect(segmentSpan?.name).toBe('Test Span'); + test( + 'should auto-instrument `ioredis` package when using redis.set() and redis.get()', + { timeout: 75_000 }, + async () => { + await createTestRunner() + .expect({ + span: container => { + const segmentSpan = container.items.find(item => item.is_segment); + expect(segmentSpan?.name).toBe('Test Span'); - const dbSpans = container.items.filter(item => item.attributes[SENTRY_OP]?.value === redisSpanOp); + const dbSpans = container.items.filter(item => item.attributes[SENTRY_OP]?.value === redisSpanOp); - expect(dbSpans).toEqual([ - expectedDbSpan({ operation: 'set', statement: 'set test-key [1 other arguments]' }), - expectedDbSpan({ operation: 'get', statement: 'get test-key' }), - // a failing command produces a span with an error status - expectedDbSpan({ - operation: 'incr', - statement: 'incr test-key', - status: 'error', - errorMessage: 'ERR value is not an integer or out of range', - }), - ]); - }, - }) - .start() - .completed(); - }); + expect(dbSpans).toEqual([ + expectedDbSpan({ operation: 'set', statement: 'set test-key [1 other arguments]' }), + expectedDbSpan({ operation: 'get', statement: 'get test-key' }), + // a failing command produces a span with an error status + expectedDbSpan({ + operation: 'incr', + statement: 'incr test-key', + status: 'error', + errorMessage: 'ERR value is not an integer or out of range', + }), + ]); + }, + }) + .start() + .completed(); + }, + ); }); }); }); From 0d30edb8c685b6107083109b36c2b40afe8270b6 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Oct 2026 11:04:24 +0200 Subject: [PATCH 2/5] test(node): Use conventions constants for Redis span ops Co-Authored-By: GPT-6 --- .../suites/tracing/ioredis-dc/test.ts | 17 ++++--- .../suites/tracing/redis-cache-static/test.ts | 15 +++--- .../suites/tracing/redis-cache/test.ts | 51 ++++++++++--------- .../suites/tracing/redis-dc/test.ts | 25 ++++----- .../suites/tracing/redis/test.ts | 3 +- 5 files changed, 58 insertions(+), 53 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts index 939774499db8..be3543892cdd 100644 --- a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts @@ -21,6 +21,7 @@ import { SERVER_ADDRESS, SERVER_PORT, } from '@sentry/conventions/attributes'; +import { CACHE_GET, CACHE_PUT, type CACHE_REMOVE, DB_QUERY } from '@sentry/conventions/op'; import { afterAll, describe, expect } from 'vitest'; import { EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -85,7 +86,7 @@ describeWithDockerCompose( // A cache span is a db span the cache hook took over: it is renamed to its cache operation // and reports the connection it inherited as peer attributes too. const cacheSpan = ( - op: 'cache.get' | 'cache.put' | 'cache.remove', + op: typeof CACHE_GET | typeof CACHE_PUT | typeof CACHE_REMOVE, attributes: Record, ): unknown => streamedSpan(op, op, { ...PEER, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); @@ -109,40 +110,40 @@ describeWithDockerCompose( ); expect(spans).toEqual([ - streamedSpan(`set ${HOST}:${PORT}`, 'db.query', { + streamedSpan(`set ${HOST}:${PORT}`, DB_QUERY, { [DB_OPERATION_NAME]: 'set', [DB_QUERY_TEXT]: 'set dc-test-key ?', }), - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'set', [DB_QUERY_TEXT]: 'set dc-cache:test-key ?', [CACHE_KEY]: ['dc-cache:test-key'], [CACHE_ITEM_SIZE]: 2, }), - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'set', [DB_QUERY_TEXT]: 'set dc-cache:test-key-ex ? ? ?', [CACHE_KEY]: ['dc-cache:test-key-ex'], [CACHE_ITEM_SIZE]: 2, }), - streamedSpan(`get ${HOST}:${PORT}`, 'db.query', { + streamedSpan(`get ${HOST}:${PORT}`, DB_QUERY, { [DB_OPERATION_NAME]: 'get', [DB_QUERY_TEXT]: 'get dc-test-key', }), - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'get', [DB_QUERY_TEXT]: 'get dc-cache:test-key', [CACHE_KEY]: ['dc-cache:test-key'], [CACHE_HIT]: true, [CACHE_ITEM_SIZE]: 10, }), - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'get', [DB_QUERY_TEXT]: 'get dc-cache:unavailable-data', [CACHE_KEY]: ['dc-cache:unavailable-data'], [CACHE_HIT]: false, }), - streamedSpan(`mget ${HOST}:${PORT}`, 'db.query', { + streamedSpan(`mget ${HOST}:${PORT}`, DB_QUERY, { [DB_OPERATION_NAME]: 'mget', [DB_QUERY_TEXT]: 'mget ? ? ?', }), diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts index 139cb232e397..87279d401d3e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts @@ -1,3 +1,4 @@ +import { CACHE_GET, CACHE_PUT, CACHE_REMOVE } from '@sentry/conventions/op'; import { afterAll, expect } from 'vitest'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -14,7 +15,7 @@ describeWithDockerCompose('redis cache static instrumentation', { workingDirecto // SET expect.objectContaining({ description: 'ioredis-cache:test-key', - op: 'cache.put', + op: CACHE_PUT, origin: redisOrigin, data: expect.objectContaining({ 'sentry.origin': redisOrigin, @@ -29,7 +30,7 @@ describeWithDockerCompose('redis cache static instrumentation', { workingDirecto // SET (with EX) expect.objectContaining({ description: 'ioredis-cache:test-key-set-EX', - op: 'cache.put', + op: CACHE_PUT, origin: redisOrigin, data: expect.objectContaining({ 'sentry.origin': redisOrigin, @@ -44,7 +45,7 @@ describeWithDockerCompose('redis cache static instrumentation', { workingDirecto // SETEX expect.objectContaining({ description: 'ioredis-cache:test-key-setex', - op: 'cache.put', + op: CACHE_PUT, origin: redisOrigin, data: expect.objectContaining({ 'sentry.origin': redisOrigin, @@ -59,7 +60,7 @@ describeWithDockerCompose('redis cache static instrumentation', { workingDirecto // GET expect.objectContaining({ description: 'ioredis-cache:test-key', - op: 'cache.get', + op: CACHE_GET, origin: redisOrigin, data: expect.objectContaining({ 'sentry.origin': redisOrigin, @@ -75,7 +76,7 @@ describeWithDockerCompose('redis cache static instrumentation', { workingDirecto // GET (unavailable - no cache hit) expect.objectContaining({ description: 'ioredis-cache:unavailable-data', - op: 'cache.get', + op: CACHE_GET, origin: redisOrigin, data: expect.objectContaining({ 'sentry.origin': redisOrigin, @@ -90,7 +91,7 @@ describeWithDockerCompose('redis cache static instrumentation', { workingDirecto // MGET expect.objectContaining({ description: 'test-key, ioredis-cache:test-key, ioredis-cache:unavailable-data', - op: 'cache.get', + op: CACHE_GET, origin: redisOrigin, data: expect.objectContaining({ 'sentry.origin': redisOrigin, @@ -105,7 +106,7 @@ describeWithDockerCompose('redis cache static instrumentation', { workingDirecto // DEL expect.objectContaining({ description: 'ioredis-cache:test-key', - op: 'cache.remove', + op: CACHE_REMOVE, origin: redisOrigin, data: expect.objectContaining({ 'sentry.origin': redisOrigin, diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts index 774787c1b85b..f26e1ac8f150 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts @@ -25,6 +25,7 @@ import { SERVER_ADDRESS, SERVER_PORT, } from '@sentry/conventions/attributes'; +import { CACHE_GET, CACHE_PUT, CACHE_REMOVE, DB_QUERY } from '@sentry/conventions/op'; import type { SerializedStreamedSpanContainer } from '@sentry/core'; import { afterAll, describe, expect } from 'vitest'; import { EXPECTED_SDK_NAME } from '../../../utils'; @@ -36,7 +37,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory }); const redisOrigin = 'auto.db.redis'; - const redisSpanOp = 'db.query'; + const redisSpanOp = DB_QUERY; describe('streamed', () => { const streamAttribute = (value: unknown): { type: string; value: unknown } => ({ @@ -108,7 +109,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory // A cache span is a db span the cache hook took over: it is renamed to its cache operation // and reports the connection it inherited as peer attributes too. const cacheSpan = ( - op: 'cache.get' | 'cache.put' | 'cache.remove', + op: typeof CACHE_GET | typeof CACHE_PUT | typeof CACHE_REMOVE, attributes: Record, ): unknown => span(op, op, { ...peer, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); @@ -124,19 +125,19 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory [DB_OPERATION_NAME]: 'set', [DB_QUERY_TEXT]: 'set test-key [1 other arguments]', }), - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'set', [DB_QUERY_TEXT]: 'set ioredis-cache:test-key [1 other arguments]', [CACHE_KEY]: ['ioredis-cache:test-key'], [CACHE_ITEM_SIZE]: 2, }), - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'set', [DB_QUERY_TEXT]: 'set ioredis-cache:test-key-set-EX [3 other arguments]', [CACHE_KEY]: ['ioredis-cache:test-key-set-EX'], [CACHE_ITEM_SIZE]: 2, }), - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'setex', [DB_QUERY_TEXT]: 'setex ioredis-cache:test-key-setex [2 other arguments]', [CACHE_KEY]: ['ioredis-cache:test-key-setex'], @@ -146,27 +147,27 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory [DB_OPERATION_NAME]: 'get', [DB_QUERY_TEXT]: 'get test-key', }), - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'get', [DB_QUERY_TEXT]: 'get ioredis-cache:test-key', [CACHE_KEY]: ['ioredis-cache:test-key'], [CACHE_HIT]: true, [CACHE_ITEM_SIZE]: 10, }), - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'get', [DB_QUERY_TEXT]: 'get ioredis-cache:unavailable-data', [CACHE_KEY]: ['ioredis-cache:unavailable-data'], [CACHE_HIT]: false, }), - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'mget', [DB_QUERY_TEXT]: 'mget [3 other arguments]', [CACHE_KEY]: ['test-key', 'ioredis-cache:test-key', 'ioredis-cache:unavailable-data'], [CACHE_HIT]: true, [CACHE_ITEM_SIZE]: 20, }), - cacheSpan('cache.remove', { + cacheSpan(CACHE_REMOVE, { [DB_OPERATION_NAME]: 'del', [DB_QUERY_TEXT]: 'del ioredis-cache:test-key', [CACHE_KEY]: ['ioredis-cache:test-key'], @@ -193,7 +194,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory // A cache span is a db span the cache hook took over: it is renamed to its cache operation // and reports the connection it inherited as peer attributes too. const cacheSpan = ( - op: 'cache.get' | 'cache.put' | 'cache.remove', + op: typeof CACHE_GET | typeof CACHE_PUT | typeof CACHE_REMOVE, attributes: Record, ): unknown => span(op, op, { ...peer, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); @@ -214,19 +215,19 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory [DB_OPERATION_NAME]: 'SET', [DB_QUERY_TEXT]: 'SET redis-test-key [1 other arguments]', }), - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'SET', [DB_QUERY_TEXT]: 'SET redis-cache:test-key [1 other arguments]', [CACHE_KEY]: ['redis-cache:test-key'], [CACHE_ITEM_SIZE]: 2, }), - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'SET', [DB_QUERY_TEXT]: 'SET redis-cache:test-key-set-EX [3 other arguments]', [CACHE_KEY]: ['redis-cache:test-key-set-EX'], [CACHE_ITEM_SIZE]: 2, }), - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'SETEX', [DB_QUERY_TEXT]: 'SETEX redis-cache:test-key-setex [2 other arguments]', [CACHE_KEY]: ['redis-cache:test-key-setex'], @@ -236,27 +237,27 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory [DB_OPERATION_NAME]: 'GET', [DB_QUERY_TEXT]: 'GET redis-test-key', }), - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'GET', [DB_QUERY_TEXT]: 'GET redis-cache:test-key', [CACHE_KEY]: ['redis-cache:test-key'], [CACHE_HIT]: true, [CACHE_ITEM_SIZE]: 10, }), - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'GET', [DB_QUERY_TEXT]: 'GET redis-cache:unavailable-data', [CACHE_KEY]: ['redis-cache:unavailable-data'], [CACHE_HIT]: false, }), - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'MGET', [DB_QUERY_TEXT]: 'MGET [3 other arguments]', [CACHE_KEY]: ['redis-test-key', 'redis-cache:test-key', 'redis-cache:unavailable-data'], [CACHE_HIT]: true, [CACHE_ITEM_SIZE]: 20, }), - cacheSpan('cache.remove', { + cacheSpan(CACHE_REMOVE, { [DB_OPERATION_NAME]: 'DEL', [DB_QUERY_TEXT]: 'DEL redis-cache:test-key', [CACHE_KEY]: ['redis-cache:test-key'], @@ -297,7 +298,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory // A cache span is a db span the cache hook took over: it is renamed to its cache operation // and reports the connection it inherited as peer attributes too. const cacheSpan = ( - op: 'cache.get' | 'cache.put' | 'cache.remove', + op: typeof CACHE_GET | typeof CACHE_PUT | typeof CACHE_REMOVE, attributes: Record, ): unknown => span(op, op, { ...peer, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); @@ -316,19 +317,19 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory [DB_OPERATION_NAME]: 'SET', [DB_QUERY_TEXT]: 'SET redis-5-test-key [1 other arguments]', }), - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'SET', [DB_QUERY_TEXT]: 'SET redis-5-cache:test-key [1 other arguments]', [CACHE_KEY]: ['redis-5-cache:test-key'], [CACHE_ITEM_SIZE]: 2, }), - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'SET', [DB_QUERY_TEXT]: 'SET redis-5-cache:test-key-set-EX [3 other arguments]', [CACHE_KEY]: ['redis-5-cache:test-key-set-EX'], [CACHE_ITEM_SIZE]: 2, }), - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'SETEX', [DB_QUERY_TEXT]: 'SETEX redis-5-cache:test-key-setex [2 other arguments]', [CACHE_KEY]: ['redis-5-cache:test-key-setex'], @@ -338,27 +339,27 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory [DB_OPERATION_NAME]: 'GET', [DB_QUERY_TEXT]: 'GET redis-5-test-key', }), - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'GET', [DB_QUERY_TEXT]: 'GET redis-5-cache:test-key', [CACHE_KEY]: ['redis-5-cache:test-key'], [CACHE_HIT]: true, [CACHE_ITEM_SIZE]: 10, }), - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'GET', [DB_QUERY_TEXT]: 'GET redis-5-cache:unavailable-data', [CACHE_KEY]: ['redis-5-cache:unavailable-data'], [CACHE_HIT]: false, }), - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'MGET', [DB_QUERY_TEXT]: 'MGET [3 other arguments]', [CACHE_KEY]: ['redis-5-test-key', 'redis-5-cache:test-key', 'redis-5-cache:unavailable-data'], [CACHE_HIT]: true, [CACHE_ITEM_SIZE]: 20, }), - cacheSpan('cache.remove', { + cacheSpan(CACHE_REMOVE, { [DB_OPERATION_NAME]: 'DEL', [DB_QUERY_TEXT]: 'DEL redis-5-cache:test-key', [CACHE_KEY]: ['redis-5-cache:test-key'], diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts index 92479ae33874..20ab83c3ad5c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts @@ -23,6 +23,7 @@ import { SERVER_ADDRESS, SERVER_PORT, } from '@sentry/conventions/attributes'; +import { CACHE_GET, CACHE_PUT, type CACHE_REMOVE, DB_QUERY } from '@sentry/conventions/op'; import { afterAll, describe, expect } from 'vitest'; import { EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -48,8 +49,8 @@ describeWithDockerCompose( .expect({ span: container => { const names = container.items.map(item => item.name); - expect(names).toContain('cache.put'); - expect(names).not.toContain('cache.get'); + expect(names).toContain(CACHE_PUT); + expect(names).not.toContain(CACHE_GET); }, }) .expect({ @@ -117,7 +118,7 @@ describeWithDockerCompose( // A cache span is a db span whose key matched a cache prefix: it starts named after its // cache operation and reports the connection as peer attributes too. const cacheSpan = ( - op: 'cache.get' | 'cache.put' | 'cache.remove', + op: typeof CACHE_GET | typeof CACHE_PUT | typeof CACHE_REMOVE, attributes: Record, ): unknown => streamedSpan(op, op, { ...PEER, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); @@ -138,30 +139,30 @@ describeWithDockerCompose( ); expect(spans).toEqual([ - streamedSpan(`SET ${HOST}:${PORT}`, 'db.query', { + streamedSpan(`SET ${HOST}:${PORT}`, DB_QUERY, { [DB_OPERATION_NAME]: 'SET', [DB_QUERY_TEXT]: 'SET dc-test-key ?', }), // cache SET: starts as a cache span - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'SET', [DB_QUERY_TEXT]: 'SET dc-cache:test-key ?', [CACHE_KEY]: ['dc-cache:test-key'], [CACHE_ITEM_SIZE]: 2, }), // cache SET with EX option: redis v5 sends SET key value EX 10 as the command - cacheSpan('cache.put', { + cacheSpan(CACHE_PUT, { [DB_OPERATION_NAME]: 'SET', [DB_QUERY_TEXT]: 'SET dc-cache:test-key-ex ? ? ?', [CACHE_KEY]: ['dc-cache:test-key-ex'], [CACHE_ITEM_SIZE]: 2, }), - streamedSpan(`GET ${HOST}:${PORT}`, 'db.query', { + streamedSpan(`GET ${HOST}:${PORT}`, DB_QUERY, { [DB_OPERATION_NAME]: 'GET', [DB_QUERY_TEXT]: 'GET dc-test-key', }), // cache GET (hit) - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'GET', [DB_QUERY_TEXT]: 'GET dc-cache:test-key', [CACHE_KEY]: ['dc-cache:test-key'], @@ -169,7 +170,7 @@ describeWithDockerCompose( [CACHE_ITEM_SIZE]: 10, }), // cache GET (miss) - cacheSpan('cache.get', { + cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'GET', [DB_QUERY_TEXT]: 'GET dc-cache:unavailable-data', [CACHE_KEY]: ['dc-cache:unavailable-data'], @@ -177,18 +178,18 @@ describeWithDockerCompose( }), // MGET: node-redis sanitizes args for diagnostics_channel (keys become '?'), // so cache detection cannot match prefixes — remains a plain db.query span. - streamedSpan(`MGET ${HOST}:${PORT}`, 'db.query', { + streamedSpan(`MGET ${HOST}:${PORT}`, DB_QUERY, { [DB_OPERATION_NAME]: 'MGET', [DB_QUERY_TEXT]: 'MGET ? ? ?', }), - streamedSpan(`LPUSH ${HOST}:${PORT}`, 'db.query', { + streamedSpan(`LPUSH ${HOST}:${PORT}`, DB_QUERY, { [DB_OPERATION_NAME]: 'LPUSH', [DB_QUERY_TEXT]: 'LPUSH dc-cache:list-key ?', }), // a failing command on a cache key reports as an errored cache span: // the span starts as a cache span, so the classification survives the error { - ...(cacheSpan('cache.get', { + ...(cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'GET', [DB_QUERY_TEXT]: 'GET dc-cache:list-key', [CACHE_KEY]: ['dc-cache:list-key'], diff --git a/dev-packages/node-integration-tests/suites/tracing/redis/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis/test.ts index a06b8a133459..9767130338bf 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis/test.ts @@ -18,6 +18,7 @@ import { SERVER_ADDRESS, SERVER_PORT, } from '@sentry/conventions/attributes'; +import { DB_QUERY } from '@sentry/conventions/op'; import { afterAll, describe, expect } from 'vitest'; import { EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -31,7 +32,7 @@ describeWithDockerCompose('redis auto instrumentation', { workingDirectory: [__d // subscriber instead of the OTel monkey-patch, so the span origin differs. All // other attributes are identical. const origin = 'auto.db.redis'; - const redisSpanOp = 'db.query'; + const redisSpanOp = DB_QUERY; describe('streamed', () => { const COMMON_ATTRIBUTES = { [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, From d38f723d74aa25ecc8bc79c29d0610066d75ae1d Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Oct 2026 11:33:52 +0200 Subject: [PATCH 3/5] test(node): Keep the original Redis static case in place Co-Authored-By: GPT-6 --- .../node-suites/excludes.ts | 1 - .../redis-cache-static/docker-compose.yml | 14 -- .../redis-cache-static/instrument-ioredis.mjs | 11 -- .../redis-cache-static/scenario-ioredis.mjs | 34 ----- .../suites/tracing/redis-cache-static/test.ts | 128 ------------------ .../redis-cache/instrument-ioredis.mjs | 1 + .../suites/tracing/redis-cache/test.ts | 120 ++++++++++++++++ 7 files changed, 121 insertions(+), 188 deletions(-) delete mode 100644 dev-packages/node-integration-tests/suites/tracing/redis-cache-static/docker-compose.yml delete mode 100644 dev-packages/node-integration-tests/suites/tracing/redis-cache-static/instrument-ioredis.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/redis-cache-static/scenario-ioredis.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts diff --git a/dev-packages/bun-integration-tests/node-suites/excludes.ts b/dev-packages/bun-integration-tests/node-suites/excludes.ts index 4d8312fd7292..d27790a79a0f 100644 --- a/dev-packages/bun-integration-tests/node-suites/excludes.ts +++ b/dev-packages/bun-integration-tests/node-suites/excludes.ts @@ -133,7 +133,6 @@ export const NO_AUTO_INSTRUMENTATION = [ 'suites/tracing/prisma-orm-v7/test.ts', 'suites/tracing/prisma-orm-v8/test.ts', 'suites/tracing/redis-cache/test.ts', - 'suites/tracing/redis-cache-static/test.ts', 'suites/tracing/redis-dc/test.ts', 'suites/tracing/redis/test.ts', 'suites/tracing/tedious/test.ts', diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/docker-compose.yml deleted file mode 100644 index 1196c2651032..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/docker-compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: '3.9' - -services: - db: - image: redis:latest - restart: always - ports: - - '6384:6379' - healthcheck: - test: ['CMD-SHELL', 'redis-cli ping | grep -q PONG'] - interval: 2s - timeout: 3s - retries: 30 - start_period: 5s diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/instrument-ioredis.mjs b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/instrument-ioredis.mjs deleted file mode 100644 index 9732fa703714..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/instrument-ioredis.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - traceLifecycle: 'static', - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, - integrations: [Sentry.redisIntegration({ cachePrefixes: ['ioredis-cache:'] })], -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/scenario-ioredis.mjs b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/scenario-ioredis.mjs deleted file mode 100644 index daabe69f38e3..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/scenario-ioredis.mjs +++ /dev/null @@ -1,34 +0,0 @@ -import * as Sentry from '@sentry/node'; -import Redis from 'ioredis'; - -const redis = new Redis({ port: 6384 }); - -async function run() { - await Sentry.startSpan( - { - name: 'Test Span', - op: 'test-span', - }, - async () => { - try { - await redis.set('test-key', 'test-value'); - await redis.set('ioredis-cache:test-key', 'test-value'); - - await redis.set('ioredis-cache:test-key-set-EX', 'test-value', 'EX', 10); - await redis.setex('ioredis-cache:test-key-setex', 10, 'test-value'); - - await redis.get('test-key'); - await redis.get('ioredis-cache:test-key'); - await redis.get('ioredis-cache:unavailable-data'); - - await redis.mget('test-key', 'ioredis-cache:test-key', 'ioredis-cache:unavailable-data'); - - await redis.del('ioredis-cache:test-key'); - } finally { - await redis.disconnect(); - } - }, - ); -} - -run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts deleted file mode 100644 index 87279d401d3e..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache-static/test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { CACHE_GET, CACHE_PUT, CACHE_REMOVE } from '@sentry/conventions/op'; -import { afterAll, expect } from 'vitest'; -import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; - -describeWithDockerCompose('redis cache static instrumentation', { workingDirectory: [__dirname] }, () => { - afterAll(() => { - cleanupChildProcesses(); - }); - - const redisOrigin = 'auto.db.redis'; - - const EXPECTED_TRANSACTION = { - transaction: 'Test Span', - spans: expect.arrayContaining([ - // SET - expect.objectContaining({ - description: 'ioredis-cache:test-key', - op: CACHE_PUT, - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'set ioredis-cache:test-key [1 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['ioredis-cache:test-key'], - 'cache.item_size': 2, - 'network.peer.address': 'localhost', - 'network.peer.port': 6384, - }), - }), - // SET (with EX) - expect.objectContaining({ - description: 'ioredis-cache:test-key-set-EX', - op: CACHE_PUT, - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'set ioredis-cache:test-key-set-EX [3 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['ioredis-cache:test-key-set-EX'], - 'cache.item_size': 2, - 'network.peer.address': 'localhost', - 'network.peer.port': 6384, - }), - }), - // SETEX - expect.objectContaining({ - description: 'ioredis-cache:test-key-setex', - op: CACHE_PUT, - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'setex ioredis-cache:test-key-setex [2 other arguments]', - 'cache.operation': 'put', - 'cache.key': ['ioredis-cache:test-key-setex'], - 'cache.item_size': 2, - 'network.peer.address': 'localhost', - 'network.peer.port': 6384, - }), - }), - // GET - expect.objectContaining({ - description: 'ioredis-cache:test-key', - op: CACHE_GET, - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'get ioredis-cache:test-key', - 'cache.operation': 'get', - 'cache.hit': true, - 'cache.key': ['ioredis-cache:test-key'], - 'cache.item_size': 10, - 'network.peer.address': 'localhost', - 'network.peer.port': 6384, - }), - }), - // GET (unavailable - no cache hit) - expect.objectContaining({ - description: 'ioredis-cache:unavailable-data', - op: CACHE_GET, - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'get ioredis-cache:unavailable-data', - 'cache.operation': 'get', - 'cache.hit': false, - 'cache.key': ['ioredis-cache:unavailable-data'], - 'network.peer.address': 'localhost', - 'network.peer.port': 6384, - }), - }), - // MGET - expect.objectContaining({ - description: 'test-key, ioredis-cache:test-key, ioredis-cache:unavailable-data', - op: CACHE_GET, - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'mget [3 other arguments]', - 'cache.operation': 'get', - 'cache.hit': true, - 'cache.key': ['test-key', 'ioredis-cache:test-key', 'ioredis-cache:unavailable-data'], - 'network.peer.address': 'localhost', - 'network.peer.port': 6384, - }), - }), - // DEL - expect.objectContaining({ - description: 'ioredis-cache:test-key', - op: CACHE_REMOVE, - origin: redisOrigin, - data: expect.objectContaining({ - 'sentry.origin': redisOrigin, - 'db.query.text': 'del ioredis-cache:test-key', - 'cache.operation': 'remove', - 'cache.key': ['ioredis-cache:test-key'], - 'network.peer.address': 'localhost', - 'network.peer.port': 6384, - }), - }), - ]), - }; - - createEsmAndCjsTests(__dirname, 'scenario-ioredis.mjs', 'instrument-ioredis.mjs', (createTestRunner, test) => { - test('should create cache spans for prefixed keys (ioredis)', { timeout: 60_000 }, async () => { - await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); - }); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-ioredis.mjs b/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-ioredis.mjs index 4a6ca8a42ec4..8b1385e92375 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-ioredis.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache/instrument-ioredis.mjs @@ -2,6 +2,7 @@ import * as Sentry from '@sentry/node'; import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ + traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts index f26e1ac8f150..a6f3271d2356 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts @@ -39,6 +39,125 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory const redisOrigin = 'auto.db.redis'; const redisSpanOp = DB_QUERY; + describe('ioredis cache keys', () => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Span', + spans: expect.arrayContaining([ + // SET + expect.objectContaining({ + description: 'ioredis-cache:test-key', + op: CACHE_PUT, + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'set ioredis-cache:test-key [1 other arguments]', + 'cache.operation': 'put', + 'cache.key': ['ioredis-cache:test-key'], + 'cache.item_size': 2, + 'network.peer.address': 'localhost', + 'network.peer.port': 6383, + }), + }), + // SET (with EX) + expect.objectContaining({ + description: 'ioredis-cache:test-key-set-EX', + op: CACHE_PUT, + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'set ioredis-cache:test-key-set-EX [3 other arguments]', + 'cache.operation': 'put', + 'cache.key': ['ioredis-cache:test-key-set-EX'], + 'cache.item_size': 2, + 'network.peer.address': 'localhost', + 'network.peer.port': 6383, + }), + }), + // SETEX + expect.objectContaining({ + description: 'ioredis-cache:test-key-setex', + op: CACHE_PUT, + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'setex ioredis-cache:test-key-setex [2 other arguments]', + 'cache.operation': 'put', + 'cache.key': ['ioredis-cache:test-key-setex'], + 'cache.item_size': 2, + 'network.peer.address': 'localhost', + 'network.peer.port': 6383, + }), + }), + // GET + expect.objectContaining({ + description: 'ioredis-cache:test-key', + op: CACHE_GET, + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'get ioredis-cache:test-key', + 'cache.operation': 'get', + 'cache.hit': true, + 'cache.key': ['ioredis-cache:test-key'], + 'cache.item_size': 10, + 'network.peer.address': 'localhost', + 'network.peer.port': 6383, + }), + }), + // GET (unavailable - no cache hit) + expect.objectContaining({ + description: 'ioredis-cache:unavailable-data', + op: CACHE_GET, + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'get ioredis-cache:unavailable-data', + 'cache.operation': 'get', + 'cache.hit': false, + 'cache.key': ['ioredis-cache:unavailable-data'], + 'network.peer.address': 'localhost', + 'network.peer.port': 6383, + }), + }), + // MGET + expect.objectContaining({ + description: 'test-key, ioredis-cache:test-key, ioredis-cache:unavailable-data', + op: CACHE_GET, + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'mget [3 other arguments]', + 'cache.operation': 'get', + 'cache.hit': true, + 'cache.key': ['test-key', 'ioredis-cache:test-key', 'ioredis-cache:unavailable-data'], + 'network.peer.address': 'localhost', + 'network.peer.port': 6383, + }), + }), + // DEL + expect.objectContaining({ + description: 'ioredis-cache:test-key', + op: CACHE_REMOVE, + origin: redisOrigin, + data: expect.objectContaining({ + 'sentry.origin': redisOrigin, + 'db.query.text': 'del ioredis-cache:test-key', + 'cache.operation': 'remove', + 'cache.key': ['ioredis-cache:test-key'], + 'network.peer.address': 'localhost', + 'network.peer.port': 6383, + }), + }), + ]), + }; + + createEsmAndCjsTests(__dirname, 'scenario-ioredis.mjs', 'instrument-ioredis.mjs', (createTestRunner, test) => { + test('should create cache spans for prefixed keys (ioredis)', { timeout: 60_000 }, async () => { + await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + }); + }); + }); + describe('streamed', () => { const streamAttribute = (value: unknown): { type: string; value: unknown } => ({ type: Array.isArray(value) ? 'array' : Number.isInteger(value) ? 'integer' : typeof value, @@ -116,6 +235,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory createEsmAndCjsTests(__dirname, 'scenario-ioredis.mjs', 'instrument-ioredis.mjs', (createTestRunner, test) => { test('should create cache spans for prefixed keys (ioredis)', { timeout: 60_000 }, async () => { await createTestRunner() + .withEnv({ STREAMED: 'true' }) .expect({ span: container => { expect(container.items.find(item => item.is_segment)?.name).toBe(segmentName); From 6ac3e517e39865049aba8b4113fe2663eddd403f Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Oct 2026 15:41:08 +0200 Subject: [PATCH 4/5] test(node): remove redundant Redis streaming groups Co-Authored-By: GPT-6 --- .../suites/tracing/ioredis-dc/test.ts | 222 ++++++++------- .../suites/tracing/redis-dc/test.ts | 258 +++++++++--------- .../suites/tracing/redis/test.ts | 158 ++++++----- 3 files changed, 316 insertions(+), 322 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts index be3543892cdd..d854e0386637 100644 --- a/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/ioredis-dc/test.ts @@ -22,7 +22,7 @@ import { SERVER_PORT, } from '@sentry/conventions/attributes'; import { CACHE_GET, CACHE_PUT, type CACHE_REMOVE, DB_QUERY } from '@sentry/conventions/op'; -import { afterAll, describe, expect } from 'vitest'; +import { afterAll, expect } from 'vitest'; import { EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -34,125 +34,123 @@ describeWithDockerCompose( cleanupChildProcesses(); }); - describe('streamed', () => { - const ORIGIN = 'auto.db.redis.diagnostic_channel'; - const SEGMENT_NAME = 'Test Span IORedis 5.11 DC'; - const HOST = '127.0.0.1'; - const PORT = 6382; + const ORIGIN = 'auto.db.redis.diagnostic_channel'; + const SEGMENT_NAME = 'Test Span IORedis 5.11 DC'; + const HOST = '127.0.0.1'; + const PORT = 6382; - const streamAttribute = (value: unknown): { type: string; value: unknown } => ({ - type: Array.isArray(value) ? 'array' : Number.isInteger(value) ? 'integer' : typeof value, - value, - }); + const streamAttribute = (value: unknown): { type: string; value: unknown } => ({ + type: Array.isArray(value) ? 'array' : Number.isInteger(value) ? 'integer' : typeof value, + value, + }); - // Streamed spans carry `{ type, value }` attribute pairs; the expectations below are written - // as plain values and wrapped here. - const streamAttributes = (values: Record): Record => - Object.fromEntries(Object.entries(values).map(([key, value]) => [key, streamAttribute(value)])); + // Streamed spans carry `{ type, value }` attribute pairs; the expectations below are written + // as plain values and wrapped here. + const streamAttributes = (values: Record): Record => + Object.fromEntries(Object.entries(values).map(([key, value]) => [key, streamAttribute(value)])); - function streamedSpan(name: string, op: string, attributes: Record): unknown { - return { - name, - attributes: { - ...streamAttributes({ - [DB_SYSTEM_NAME]: 'redis', - [SENTRY_ENVIRONMENT]: 'production', - [SENTRY_OP]: op, - [SENTRY_ORIGIN]: ORIGIN, - [SENTRY_RELEASE]: '1.0', - [SENTRY_SDK_NAME]: EXPECTED_SDK_NAME, - [SENTRY_SEGMENT_NAME]: SEGMENT_NAME, - [SERVER_ADDRESS]: HOST, - [SERVER_PORT]: PORT, - [SENTRY_TRACE_LIFECYCLE]: 'stream', - ...attributes, - }), - [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, - [SENTRY_SEGMENT_ID]: { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, - [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, - }, - end_timestamp: expect.any(Number), - is_segment: false, - parent_span_id: expect.stringMatching(/^[\da-f]{16}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - start_timestamp: expect.any(Number), - status: 'ok', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - }; - } + function streamedSpan(name: string, op: string, attributes: Record): unknown { + return { + name, + attributes: { + ...streamAttributes({ + [DB_SYSTEM_NAME]: 'redis', + [SENTRY_ENVIRONMENT]: 'production', + [SENTRY_OP]: op, + [SENTRY_ORIGIN]: ORIGIN, + [SENTRY_RELEASE]: '1.0', + [SENTRY_SDK_NAME]: EXPECTED_SDK_NAME, + [SENTRY_SEGMENT_NAME]: SEGMENT_NAME, + [SERVER_ADDRESS]: HOST, + [SERVER_PORT]: PORT, + [SENTRY_TRACE_LIFECYCLE]: 'stream', + ...attributes, + }), + [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, + [SENTRY_SEGMENT_ID]: { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, + [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, + }, + end_timestamp: expect.any(Number), + is_segment: false, + parent_span_id: expect.stringMatching(/^[\da-f]{16}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + status: 'ok', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + }; + } - const PEER = { [NETWORK_PEER_ADDRESS]: HOST, [NETWORK_PEER_PORT]: PORT }; + const PEER = { [NETWORK_PEER_ADDRESS]: HOST, [NETWORK_PEER_PORT]: PORT }; - // A cache span is a db span the cache hook took over: it is renamed to its cache operation - // and reports the connection it inherited as peer attributes too. - const cacheSpan = ( - op: typeof CACHE_GET | typeof CACHE_PUT | typeof CACHE_REMOVE, - attributes: Record, - ): unknown => streamedSpan(op, op, { ...PEER, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); + // A cache span is a db span the cache hook took over: it is renamed to its cache operation + // and reports the connection it inherited as peer attributes too. + const cacheSpan = ( + op: typeof CACHE_GET | typeof CACHE_PUT | typeof CACHE_REMOVE, + attributes: Record, + ): unknown => streamedSpan(op, op, { ...PEER, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); - createEsmAndCjsTests(__dirname, 'scenario-ioredis-5-11.mjs', 'instrument.mjs', (createTestRunner, test) => { - test('creates spans for ioredis v5.11 commands via diagnostics_channel', { timeout: 75_000 }, async () => { - await createTestRunner() - .expect({ - span: container => { - // The connect span opens its own segment but shares the trace with the test span, - // so both segments arrive in the same container. - expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([ - 'redis-connect', - SEGMENT_NAME, - ]); + createEsmAndCjsTests(__dirname, 'scenario-ioredis-5-11.mjs', 'instrument.mjs', (createTestRunner, test) => { + test('creates spans for ioredis v5.11 commands via diagnostics_channel', { timeout: 75_000 }, async () => { + await createTestRunner() + .expect({ + span: container => { + // The connect span opens its own segment but shares the trace with the test span, + // so both segments arrive in the same container. + expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([ + 'redis-connect', + SEGMENT_NAME, + ]); - // ioredis' own handshake commands (`client SETINFO`, `info`) are emitted on the - // channel too, but belong to the connect segment — the test span's children are the - // commands the scenario issues. - const spans = container.items.filter( - item => !item.is_segment && item.attributes[SENTRY_SEGMENT_NAME]?.value === SEGMENT_NAME, - ); + // ioredis' own handshake commands (`client SETINFO`, `info`) are emitted on the + // channel too, but belong to the connect segment — the test span's children are the + // commands the scenario issues. + const spans = container.items.filter( + item => !item.is_segment && item.attributes[SENTRY_SEGMENT_NAME]?.value === SEGMENT_NAME, + ); - expect(spans).toEqual([ - streamedSpan(`set ${HOST}:${PORT}`, DB_QUERY, { - [DB_OPERATION_NAME]: 'set', - [DB_QUERY_TEXT]: 'set dc-test-key ?', - }), - cacheSpan(CACHE_PUT, { - [DB_OPERATION_NAME]: 'set', - [DB_QUERY_TEXT]: 'set dc-cache:test-key ?', - [CACHE_KEY]: ['dc-cache:test-key'], - [CACHE_ITEM_SIZE]: 2, - }), - cacheSpan(CACHE_PUT, { - [DB_OPERATION_NAME]: 'set', - [DB_QUERY_TEXT]: 'set dc-cache:test-key-ex ? ? ?', - [CACHE_KEY]: ['dc-cache:test-key-ex'], - [CACHE_ITEM_SIZE]: 2, - }), - streamedSpan(`get ${HOST}:${PORT}`, DB_QUERY, { - [DB_OPERATION_NAME]: 'get', - [DB_QUERY_TEXT]: 'get dc-test-key', - }), - cacheSpan(CACHE_GET, { - [DB_OPERATION_NAME]: 'get', - [DB_QUERY_TEXT]: 'get dc-cache:test-key', - [CACHE_KEY]: ['dc-cache:test-key'], - [CACHE_HIT]: true, - [CACHE_ITEM_SIZE]: 10, - }), - cacheSpan(CACHE_GET, { - [DB_OPERATION_NAME]: 'get', - [DB_QUERY_TEXT]: 'get dc-cache:unavailable-data', - [CACHE_KEY]: ['dc-cache:unavailable-data'], - [CACHE_HIT]: false, - }), - streamedSpan(`mget ${HOST}:${PORT}`, DB_QUERY, { - [DB_OPERATION_NAME]: 'mget', - [DB_QUERY_TEXT]: 'mget ? ? ?', - }), - ]); - }, - }) - .start() - .completed(); - }); + expect(spans).toEqual([ + streamedSpan(`set ${HOST}:${PORT}`, DB_QUERY, { + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set dc-test-key ?', + }), + cacheSpan(CACHE_PUT, { + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set dc-cache:test-key ?', + [CACHE_KEY]: ['dc-cache:test-key'], + [CACHE_ITEM_SIZE]: 2, + }), + cacheSpan(CACHE_PUT, { + [DB_OPERATION_NAME]: 'set', + [DB_QUERY_TEXT]: 'set dc-cache:test-key-ex ? ? ?', + [CACHE_KEY]: ['dc-cache:test-key-ex'], + [CACHE_ITEM_SIZE]: 2, + }), + streamedSpan(`get ${HOST}:${PORT}`, DB_QUERY, { + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get dc-test-key', + }), + cacheSpan(CACHE_GET, { + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get dc-cache:test-key', + [CACHE_KEY]: ['dc-cache:test-key'], + [CACHE_HIT]: true, + [CACHE_ITEM_SIZE]: 10, + }), + cacheSpan(CACHE_GET, { + [DB_OPERATION_NAME]: 'get', + [DB_QUERY_TEXT]: 'get dc-cache:unavailable-data', + [CACHE_KEY]: ['dc-cache:unavailable-data'], + [CACHE_HIT]: false, + }), + streamedSpan(`mget ${HOST}:${PORT}`, DB_QUERY, { + [DB_OPERATION_NAME]: 'mget', + [DB_QUERY_TEXT]: 'mget ? ? ?', + }), + ]); + }, + }) + .start() + .completed(); }); }); }, diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts index 20ab83c3ad5c..a5bf7450724d 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis-dc/test.ts @@ -24,7 +24,7 @@ import { SERVER_PORT, } from '@sentry/conventions/attributes'; import { CACHE_GET, CACHE_PUT, type CACHE_REMOVE, DB_QUERY } from '@sentry/conventions/op'; -import { afterAll, describe, expect } from 'vitest'; +import { afterAll, expect } from 'vitest'; import { EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -66,144 +66,142 @@ describeWithDockerCompose( }); }); - describe('streamed', () => { - const ORIGIN = 'auto.db.redis.diagnostic_channel'; - const SEGMENT_NAME = 'Test Span Redis 5 DC'; - const HOST = '127.0.0.1'; - const PORT = 6381; + const ORIGIN = 'auto.db.redis.diagnostic_channel'; + const SEGMENT_NAME = 'Test Span Redis 5 DC'; + const HOST = '127.0.0.1'; + const PORT = 6381; - const streamAttribute = (value: unknown): { type: string; value: unknown } => ({ - type: Array.isArray(value) ? 'array' : Number.isInteger(value) ? 'integer' : typeof value, - value, - }); + const streamAttribute = (value: unknown): { type: string; value: unknown } => ({ + type: Array.isArray(value) ? 'array' : Number.isInteger(value) ? 'integer' : typeof value, + value, + }); - // Streamed spans carry `{ type, value }` attribute pairs; the expectations below are written - // as plain values and wrapped here. - const streamAttributes = (values: Record): Record => - Object.fromEntries(Object.entries(values).map(([key, value]) => [key, streamAttribute(value)])); + // Streamed spans carry `{ type, value }` attribute pairs; the expectations below are written + // as plain values and wrapped here. + const streamAttributes = (values: Record): Record => + Object.fromEntries(Object.entries(values).map(([key, value]) => [key, streamAttribute(value)])); - function streamedSpan(name: string, op: string, attributes: Record): unknown { - return { - name, - attributes: { - ...streamAttributes({ - [DB_SYSTEM_NAME]: 'redis', - [SENTRY_ENVIRONMENT]: 'production', - [SENTRY_OP]: op, - [SENTRY_ORIGIN]: ORIGIN, - [SENTRY_RELEASE]: '1.0', - [SENTRY_SDK_NAME]: EXPECTED_SDK_NAME, - [SENTRY_SEGMENT_NAME]: SEGMENT_NAME, - [SERVER_ADDRESS]: HOST, - [SERVER_PORT]: PORT, - [SENTRY_TRACE_LIFECYCLE]: 'stream', - ...attributes, - }), - [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, - [SENTRY_SEGMENT_ID]: { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, - [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, - }, - end_timestamp: expect.any(Number), - is_segment: false, - parent_span_id: expect.stringMatching(/^[\da-f]{16}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - start_timestamp: expect.any(Number), - status: 'ok', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - }; - } + function streamedSpan(name: string, op: string, attributes: Record): unknown { + return { + name, + attributes: { + ...streamAttributes({ + [DB_SYSTEM_NAME]: 'redis', + [SENTRY_ENVIRONMENT]: 'production', + [SENTRY_OP]: op, + [SENTRY_ORIGIN]: ORIGIN, + [SENTRY_RELEASE]: '1.0', + [SENTRY_SDK_NAME]: EXPECTED_SDK_NAME, + [SENTRY_SEGMENT_NAME]: SEGMENT_NAME, + [SERVER_ADDRESS]: HOST, + [SERVER_PORT]: PORT, + [SENTRY_TRACE_LIFECYCLE]: 'stream', + ...attributes, + }), + [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, + [SENTRY_SEGMENT_ID]: { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, + [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, + }, + end_timestamp: expect.any(Number), + is_segment: false, + parent_span_id: expect.stringMatching(/^[\da-f]{16}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + status: 'ok', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + }; + } - const PEER = { [NETWORK_PEER_ADDRESS]: HOST, [NETWORK_PEER_PORT]: PORT }; + const PEER = { [NETWORK_PEER_ADDRESS]: HOST, [NETWORK_PEER_PORT]: PORT }; - // A cache span is a db span whose key matched a cache prefix: it starts named after its - // cache operation and reports the connection as peer attributes too. - const cacheSpan = ( - op: typeof CACHE_GET | typeof CACHE_PUT | typeof CACHE_REMOVE, - attributes: Record, - ): unknown => streamedSpan(op, op, { ...PEER, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); + // A cache span is a db span whose key matched a cache prefix: it starts named after its + // cache operation and reports the connection as peer attributes too. + const cacheSpan = ( + op: typeof CACHE_GET | typeof CACHE_PUT | typeof CACHE_REMOVE, + attributes: Record, + ): unknown => streamedSpan(op, op, { ...PEER, [CACHE_OPERATION]: op.slice('cache.'.length), ...attributes }); - createEsmAndCjsTests(__dirname, 'scenario-redis-5-tracing.mjs', 'instrument.mjs', (createTestRunner, test) => { - test('should create spans for redis v5 commands via diagnostics_channel', { timeout: 60_000 }, async () => { - await createTestRunner() - .expect({ - span: container => { - // The connect span opens its own segment but shares the trace with the test span, - // so both segments arrive in the same container. - expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([ - 'redis-connect', - SEGMENT_NAME, - ]); + createEsmAndCjsTests(__dirname, 'scenario-redis-5-tracing.mjs', 'instrument.mjs', (createTestRunner, test) => { + test('should create spans for redis v5 commands via diagnostics_channel', { timeout: 60_000 }, async () => { + await createTestRunner() + .expect({ + span: container => { + // The connect span opens its own segment but shares the trace with the test span, + // so both segments arrive in the same container. + expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([ + 'redis-connect', + SEGMENT_NAME, + ]); - const spans = container.items.filter( - item => !item.is_segment && item.attributes[SENTRY_SEGMENT_NAME]?.value === SEGMENT_NAME, - ); + const spans = container.items.filter( + item => !item.is_segment && item.attributes[SENTRY_SEGMENT_NAME]?.value === SEGMENT_NAME, + ); - expect(spans).toEqual([ - streamedSpan(`SET ${HOST}:${PORT}`, DB_QUERY, { - [DB_OPERATION_NAME]: 'SET', - [DB_QUERY_TEXT]: 'SET dc-test-key ?', - }), - // cache SET: starts as a cache span - cacheSpan(CACHE_PUT, { - [DB_OPERATION_NAME]: 'SET', - [DB_QUERY_TEXT]: 'SET dc-cache:test-key ?', - [CACHE_KEY]: ['dc-cache:test-key'], - [CACHE_ITEM_SIZE]: 2, - }), - // cache SET with EX option: redis v5 sends SET key value EX 10 as the command - cacheSpan(CACHE_PUT, { - [DB_OPERATION_NAME]: 'SET', - [DB_QUERY_TEXT]: 'SET dc-cache:test-key-ex ? ? ?', - [CACHE_KEY]: ['dc-cache:test-key-ex'], - [CACHE_ITEM_SIZE]: 2, - }), - streamedSpan(`GET ${HOST}:${PORT}`, DB_QUERY, { + expect(spans).toEqual([ + streamedSpan(`SET ${HOST}:${PORT}`, DB_QUERY, { + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET dc-test-key ?', + }), + // cache SET: starts as a cache span + cacheSpan(CACHE_PUT, { + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET dc-cache:test-key ?', + [CACHE_KEY]: ['dc-cache:test-key'], + [CACHE_ITEM_SIZE]: 2, + }), + // cache SET with EX option: redis v5 sends SET key value EX 10 as the command + cacheSpan(CACHE_PUT, { + [DB_OPERATION_NAME]: 'SET', + [DB_QUERY_TEXT]: 'SET dc-cache:test-key-ex ? ? ?', + [CACHE_KEY]: ['dc-cache:test-key-ex'], + [CACHE_ITEM_SIZE]: 2, + }), + streamedSpan(`GET ${HOST}:${PORT}`, DB_QUERY, { + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET dc-test-key', + }), + // cache GET (hit) + cacheSpan(CACHE_GET, { + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET dc-cache:test-key', + [CACHE_KEY]: ['dc-cache:test-key'], + [CACHE_HIT]: true, + [CACHE_ITEM_SIZE]: 10, + }), + // cache GET (miss) + cacheSpan(CACHE_GET, { + [DB_OPERATION_NAME]: 'GET', + [DB_QUERY_TEXT]: 'GET dc-cache:unavailable-data', + [CACHE_KEY]: ['dc-cache:unavailable-data'], + [CACHE_HIT]: false, + }), + // MGET: node-redis sanitizes args for diagnostics_channel (keys become '?'), + // so cache detection cannot match prefixes — remains a plain db.query span. + streamedSpan(`MGET ${HOST}:${PORT}`, DB_QUERY, { + [DB_OPERATION_NAME]: 'MGET', + [DB_QUERY_TEXT]: 'MGET ? ? ?', + }), + streamedSpan(`LPUSH ${HOST}:${PORT}`, DB_QUERY, { + [DB_OPERATION_NAME]: 'LPUSH', + [DB_QUERY_TEXT]: 'LPUSH dc-cache:list-key ?', + }), + // a failing command on a cache key reports as an errored cache span: + // the span starts as a cache span, so the classification survives the error + { + ...(cacheSpan(CACHE_GET, { [DB_OPERATION_NAME]: 'GET', - [DB_QUERY_TEXT]: 'GET dc-test-key', - }), - // cache GET (hit) - cacheSpan(CACHE_GET, { - [DB_OPERATION_NAME]: 'GET', - [DB_QUERY_TEXT]: 'GET dc-cache:test-key', - [CACHE_KEY]: ['dc-cache:test-key'], - [CACHE_HIT]: true, - [CACHE_ITEM_SIZE]: 10, - }), - // cache GET (miss) - cacheSpan(CACHE_GET, { - [DB_OPERATION_NAME]: 'GET', - [DB_QUERY_TEXT]: 'GET dc-cache:unavailable-data', - [CACHE_KEY]: ['dc-cache:unavailable-data'], - [CACHE_HIT]: false, - }), - // MGET: node-redis sanitizes args for diagnostics_channel (keys become '?'), - // so cache detection cannot match prefixes — remains a plain db.query span. - streamedSpan(`MGET ${HOST}:${PORT}`, DB_QUERY, { - [DB_OPERATION_NAME]: 'MGET', - [DB_QUERY_TEXT]: 'MGET ? ? ?', - }), - streamedSpan(`LPUSH ${HOST}:${PORT}`, DB_QUERY, { - [DB_OPERATION_NAME]: 'LPUSH', - [DB_QUERY_TEXT]: 'LPUSH dc-cache:list-key ?', - }), - // a failing command on a cache key reports as an errored cache span: - // the span starts as a cache span, so the classification survives the error - { - ...(cacheSpan(CACHE_GET, { - [DB_OPERATION_NAME]: 'GET', - [DB_QUERY_TEXT]: 'GET dc-cache:list-key', - [CACHE_KEY]: ['dc-cache:list-key'], - [ERROR_TYPE]: 'Error', - [SENTRY_STATUS_MESSAGE]: 'WRONGTYPE Operation against a key holding the wrong kind of value', - }) as Record), - status: 'error', - }, - ]); - }, - }) - .start() - .completed(); - }); + [DB_QUERY_TEXT]: 'GET dc-cache:list-key', + [CACHE_KEY]: ['dc-cache:list-key'], + [ERROR_TYPE]: 'Error', + [SENTRY_STATUS_MESSAGE]: 'WRONGTYPE Operation against a key holding the wrong kind of value', + }) as Record), + status: 'error', + }, + ]); + }, + }) + .start() + .completed(); }); }); }, diff --git a/dev-packages/node-integration-tests/suites/tracing/redis/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis/test.ts index 9767130338bf..36decf4b6001 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis/test.ts @@ -19,7 +19,7 @@ import { SERVER_PORT, } from '@sentry/conventions/attributes'; import { DB_QUERY } from '@sentry/conventions/op'; -import { afterAll, describe, expect } from 'vitest'; +import { afterAll, expect } from 'vitest'; import { EXPECTED_SDK_NAME } from '../../../utils'; import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; @@ -33,88 +33,86 @@ describeWithDockerCompose('redis auto instrumentation', { workingDirectory: [__d // other attributes are identical. const origin = 'auto.db.redis'; const redisSpanOp = DB_QUERY; - describe('streamed', () => { - const COMMON_ATTRIBUTES = { - [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, - [DB_SYSTEM_NAME]: { type: 'string', value: 'redis' }, - [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, - [SERVER_PORT]: { type: 'integer', value: 6380 }, - [SENTRY_KIND]: { type: 'string', value: 'client' }, - [SENTRY_ENVIRONMENT]: { type: 'string', value: 'production' }, - [SENTRY_OP]: { type: 'string', value: redisSpanOp }, - [SENTRY_ORIGIN]: { type: 'string', value: origin }, - [SENTRY_RELEASE]: { type: 'string', value: '1.0' }, - [SENTRY_SDK_NAME]: { type: 'string', value: EXPECTED_SDK_NAME }, - [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, - [SENTRY_SEGMENT_ID]: { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, - [SENTRY_SEGMENT_NAME]: { type: 'string', value: 'Test Span' }, - [SENTRY_TRACE_LIFECYCLE]: { type: 'string', value: 'stream' }, - }; + const COMMON_ATTRIBUTES = { + [SENTRY_IS_LOCALHOST]: { type: 'boolean', value: false }, + [DB_SYSTEM_NAME]: { type: 'string', value: 'redis' }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [SERVER_PORT]: { type: 'integer', value: 6380 }, + [SENTRY_KIND]: { type: 'string', value: 'client' }, + [SENTRY_ENVIRONMENT]: { type: 'string', value: 'production' }, + [SENTRY_OP]: { type: 'string', value: redisSpanOp }, + [SENTRY_ORIGIN]: { type: 'string', value: origin }, + [SENTRY_RELEASE]: { type: 'string', value: '1.0' }, + [SENTRY_SDK_NAME]: { type: 'string', value: EXPECTED_SDK_NAME }, + [SENTRY_SDK_VERSION]: { type: 'string', value: expect.any(String) }, + [SENTRY_SEGMENT_ID]: { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, + [SENTRY_SEGMENT_NAME]: { type: 'string', value: 'Test Span' }, + [SENTRY_TRACE_LIFECYCLE]: { type: 'string', value: 'stream' }, + }; - function expectedDbSpan({ - operation, - statement, - status = 'ok', - errorMessage, - }: { - operation: string; - statement: string; - status?: string; - errorMessage?: string; - }): unknown { - return { - attributes: { - ...COMMON_ATTRIBUTES, - [DB_OPERATION_NAME]: { type: 'string', value: operation }, - [DB_QUERY_TEXT]: { type: 'string', value: statement }, - ...(errorMessage - ? { - [ERROR_TYPE]: { type: 'string', value: 'ReplyError' }, - [SENTRY_STATUS_MESSAGE]: { type: 'string', value: errorMessage }, - } - : {}), - }, - name: `${operation} localhost:6380`, - end_timestamp: expect.any(Number), - is_segment: false, - parent_span_id: expect.stringMatching(/^[\da-f]{16}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - start_timestamp: expect.any(Number), - status, - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - }; - } + function expectedDbSpan({ + operation, + statement, + status = 'ok', + errorMessage, + }: { + operation: string; + statement: string; + status?: string; + errorMessage?: string; + }): unknown { + return { + attributes: { + ...COMMON_ATTRIBUTES, + [DB_OPERATION_NAME]: { type: 'string', value: operation }, + [DB_QUERY_TEXT]: { type: 'string', value: statement }, + ...(errorMessage + ? { + [ERROR_TYPE]: { type: 'string', value: 'ReplyError' }, + [SENTRY_STATUS_MESSAGE]: { type: 'string', value: errorMessage }, + } + : {}), + }, + name: `${operation} localhost:6380`, + end_timestamp: expect.any(Number), + is_segment: false, + parent_span_id: expect.stringMatching(/^[\da-f]{16}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + status, + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + }; + } - createEsmAndCjsTests(__dirname, 'scenario-ioredis.mjs', 'instrument.mjs', (createTestRunner, test) => { - test( - 'should auto-instrument `ioredis` package when using redis.set() and redis.get()', - { timeout: 75_000 }, - async () => { - await createTestRunner() - .expect({ - span: container => { - const segmentSpan = container.items.find(item => item.is_segment); - expect(segmentSpan?.name).toBe('Test Span'); + createEsmAndCjsTests(__dirname, 'scenario-ioredis.mjs', 'instrument.mjs', (createTestRunner, test) => { + test( + 'should auto-instrument `ioredis` package when using redis.set() and redis.get()', + { timeout: 75_000 }, + async () => { + await createTestRunner() + .expect({ + span: container => { + const segmentSpan = container.items.find(item => item.is_segment); + expect(segmentSpan?.name).toBe('Test Span'); - const dbSpans = container.items.filter(item => item.attributes[SENTRY_OP]?.value === redisSpanOp); + const dbSpans = container.items.filter(item => item.attributes[SENTRY_OP]?.value === redisSpanOp); - expect(dbSpans).toEqual([ - expectedDbSpan({ operation: 'set', statement: 'set test-key [1 other arguments]' }), - expectedDbSpan({ operation: 'get', statement: 'get test-key' }), - // a failing command produces a span with an error status - expectedDbSpan({ - operation: 'incr', - statement: 'incr test-key', - status: 'error', - errorMessage: 'ERR value is not an integer or out of range', - }), - ]); - }, - }) - .start() - .completed(); - }, - ); - }); + expect(dbSpans).toEqual([ + expectedDbSpan({ operation: 'set', statement: 'set test-key [1 other arguments]' }), + expectedDbSpan({ operation: 'get', statement: 'get test-key' }), + // a failing command produces a span with an error status + expectedDbSpan({ + operation: 'incr', + statement: 'incr test-key', + status: 'error', + errorMessage: 'ERR value is not an integer or out of range', + }), + ]); + }, + }) + .start() + .completed(); + }, + ); }); }); From 8061f443c14f34c99bffed8d016af2942c04d140 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Oct 2026 15:54:44 +0200 Subject: [PATCH 5/5] chore: bump Node bundle size limits Co-Authored-By: GPT-6 --- .size-limit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 220fa9c349e8..f6c7517ad493 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -419,7 +419,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '150 KB', + limit: '151 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -465,7 +465,7 @@ module.exports = [ path: 'packages/node/build/esm/index.js', import: createImport('init'), gzip: true, - limit: '128 KB', + limit: '129 KB', disablePlugins: ['@size-limit/esbuild'], ignore: [...builtinModules, ...nodePrefixedBuiltinModules], modifyWebpackConfig: function (config) {