diff --git a/dev-packages/node-integration-tests/suites/tracing/dataloader/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/dataloader/instrument.mjs index 22bf57f14364..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/dataloader/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/dataloader/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/dataloader/test.ts b/dev-packages/node-integration-tests/suites/tracing/dataloader/test.ts index b49004cf02a9..620a4311d083 100644 --- a/dev-packages/node-integration-tests/suites/tracing/dataloader/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/dataloader/test.ts @@ -1,4 +1,13 @@ -import type { SerializedStreamedSpanContainer } from '@sentry/core'; +import { + CACHE_KEY, + CACHE_OPERATION, + DB_COLLECTION_NAME, + DB_OPERATION_NAME, + SENTRY_KIND, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; +import { CACHE_GET, CACHE_PUT, CACHE_REMOVE } from '@sentry/conventions/op'; import { afterAll, describe, expect } from 'vitest'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; @@ -6,8 +15,7 @@ import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runn // enabled (via INJECT_ORCHESTRION) the OTel `Dataloader` integration is swapped for the // diagnostics-channel one, which stamps a different origin. const ORIGIN = 'auto.db.dataloader'; -const CACHE_GET_OP = 'cache.get'; -const CACHE_MUTATION_OPS = { prime: 'cache.put', clear: 'cache.remove', clearAll: 'cache.remove' } as const; +const CACHE_MUTATION_OPS = { prime: CACHE_PUT, clear: CACHE_REMOVE, clearAll: CACHE_REMOVE } as const; describe('dataloader auto-instrumentation', () => { afterAll(() => { @@ -18,30 +26,28 @@ describe('dataloader auto-instrumentation', () => { test('instruments load, loadMany, batch, prime, clear and clearAll', async () => { const runner = createRunner() .expect({ - transaction: event => { - expect(event.transaction).toBe('GET /load'); + span: container => { + expect(container.items.find(span => span.is_segment)?.name).toBe('GET /load'); - const spans = event.spans || []; + const spans = container.items; - const loadSpan = spans.find(span => span.description === 'dataloader.load'); + const loadSpan = spans.find(span => span.attributes[DB_OPERATION_NAME]?.value === 'load'); expect(loadSpan).toBeDefined(); - expect(loadSpan?.op).toBe(CACHE_GET_OP); - expect(loadSpan?.origin).toBe(ORIGIN); + expect(loadSpan?.attributes[SENTRY_OP]?.value).toBe(CACHE_GET); + expect(loadSpan?.attributes[SENTRY_ORIGIN]?.value).toBe(ORIGIN); expect(loadSpan?.status).toBe('ok'); - expect(loadSpan?.data?.['sentry.origin']).toBe(ORIGIN); - expect(loadSpan?.data?.['sentry.op']).toBe(CACHE_GET_OP); - expect(loadSpan?.data?.['cache.key']).toEqual(['user-1']); - expect(loadSpan?.data?.['db.operation.name']).toBe('load'); + expect(loadSpan?.attributes[CACHE_KEY]?.value).toEqual(['user-1']); + expect(loadSpan?.attributes[DB_OPERATION_NAME]?.value).toBe('load'); // A direct operation is a client call; the deferred `batch` below gets no kind - expect(loadSpan?.data?.['sentry.kind']).toBe('client'); + expect(loadSpan?.attributes[SENTRY_KIND]?.value).toBe('client'); - const batchSpan = spans.find(span => span.description === 'dataloader.batch'); + const batchSpan = spans.find(span => span.attributes[DB_OPERATION_NAME]?.value === 'batch'); expect(batchSpan).toBeDefined(); - expect(batchSpan?.op).toBe(CACHE_GET_OP); - expect(batchSpan?.origin).toBe(ORIGIN); + expect(batchSpan?.attributes[SENTRY_OP]?.value).toBe(CACHE_GET); + expect(batchSpan?.attributes[SENTRY_ORIGIN]?.value).toBe(ORIGIN); expect(batchSpan?.status).toBe('ok'); - expect(batchSpan?.data?.['cache.key']).toEqual(['user-1']); - expect(batchSpan?.data?.['sentry.kind']).toBeUndefined(); + expect(batchSpan?.attributes[CACHE_KEY]?.value).toEqual(['user-1']); + expect(batchSpan?.attributes[SENTRY_KIND]?.value).toBeUndefined(); // The batch span links back to the load span that triggered it expect(batchSpan?.links).toEqual([ @@ -54,57 +60,62 @@ describe('dataloader auto-instrumentation', () => { // Locks down the async behavior: `load` encloses the deferred `batch` span expect(batchSpan?.parent_span_id).toBe(loadSpan?.span_id); expect(loadSpan?.start_timestamp).toBeLessThanOrEqual(batchSpan?.start_timestamp ?? 0); - expect(loadSpan?.timestamp).toBeGreaterThanOrEqual(batchSpan?.timestamp ?? 0); + expect(loadSpan?.end_timestamp).toBeGreaterThanOrEqual(batchSpan?.end_timestamp ?? 0); }, }) .expect({ - transaction: event => { - expect(event.transaction).toBe('GET /load-many'); + span: container => { + expect(container.items.find(span => span.is_segment)?.name).toBe('GET /load-many'); - const loadManySpan = (event.spans || []).find(span => span.description === 'dataloader.loadMany'); + const loadManySpan = container.items.find(span => span.attributes[DB_OPERATION_NAME]?.value === 'loadMany'); expect(loadManySpan).toBeDefined(); - expect(loadManySpan?.op).toBe(CACHE_GET_OP); - expect(loadManySpan?.origin).toBe(ORIGIN); + expect(loadManySpan?.attributes[SENTRY_OP]?.value).toBe(CACHE_GET); + expect(loadManySpan?.attributes[SENTRY_ORIGIN]?.value).toBe(ORIGIN); expect(loadManySpan?.status).toBe('ok'); - expect(loadManySpan?.data?.['sentry.origin']).toBe(ORIGIN); - expect(loadManySpan?.data?.['sentry.op']).toBe(CACHE_GET_OP); - expect(loadManySpan?.data?.['cache.key']).toEqual(['user-1', 'user-2']); + expect(loadManySpan?.attributes[CACHE_KEY]?.value).toEqual(['user-1', 'user-2']); }, }) .expect({ - transaction: event => { - expect(event.transaction).toBe('GET /cache-ops'); + span: container => { + expect(container.items.find(span => span.is_segment)?.name).toBe('GET /cache-ops'); - const spans = event.spans || []; + const spans = container.items; // prime writes to the cache, clear/clearAll remove from it for (const [operation, op] of Object.entries(CACHE_MUTATION_OPS)) { - const span = spans.find(s => s.description === `dataloader.${operation}`); + const span = spans.find(s => s.attributes[DB_OPERATION_NAME]?.value === operation); expect(span, `expected a dataloader.${operation} span`).toBeDefined(); - expect(span?.origin).toBe(ORIGIN); + expect(span?.attributes[SENTRY_ORIGIN]?.value).toBe(ORIGIN); expect(span?.status).toBe('ok'); - expect(span?.op).toBe(op); - expect(span?.data?.['sentry.origin']).toBe(ORIGIN); - expect(span?.data?.['db.operation.name']).toBe(operation); + expect(span?.name).toBe(op); + expect(span?.attributes[SENTRY_OP]?.value).toBe(op); + expect(span?.attributes[DB_OPERATION_NAME]?.value).toBe(operation); } // `clearAll` takes no key, the other two act on a single key - expect(spans.find(s => s.description === 'dataloader.prime')?.data?.['cache.key']).toEqual(['user-1']); - expect(spans.find(s => s.description === 'dataloader.clear')?.data?.['cache.key']).toEqual(['user-1']); - expect(spans.find(s => s.description === 'dataloader.clearAll')?.data?.['cache.key']).toBeUndefined(); + expect( + spans.find(s => s.attributes[DB_OPERATION_NAME]?.value === 'prime')?.attributes[CACHE_KEY]?.value, + ).toEqual(['user-1']); + expect( + spans.find(s => s.attributes[DB_OPERATION_NAME]?.value === 'clear')?.attributes[CACHE_KEY]?.value, + ).toEqual(['user-1']); + expect( + spans.find(s => s.attributes[DB_OPERATION_NAME]?.value === 'clearAll')?.attributes[CACHE_KEY]?.value, + ).toBeUndefined(); }, }) .expect({ - transaction: event => { - expect(event.transaction).toBe('GET /named'); + span: container => { + expect(container.items.find(span => span.is_segment)?.name).toBe('GET /named'); - // A named dataloader includes its name in the span description - const namedLoadSpan = (event.spans || []).find(span => span.description === 'dataloader.load usersLoader'); + const namedLoadSpan = container.items.find(span => span.attributes[DB_OPERATION_NAME]?.value === 'load'); expect(namedLoadSpan).toBeDefined(); - expect(namedLoadSpan?.op).toBe(CACHE_GET_OP); - expect(namedLoadSpan?.origin).toBe(ORIGIN); + expect(namedLoadSpan?.attributes[SENTRY_OP]?.value).toBe(CACHE_GET); + expect(namedLoadSpan?.attributes[SENTRY_ORIGIN]?.value).toBe(ORIGIN); expect(namedLoadSpan?.status).toBe('ok'); - expect(namedLoadSpan?.data?.['db.collection.name']).toBe('usersLoader'); + expect(namedLoadSpan?.name).toBe(CACHE_GET); + expect(namedLoadSpan?.attributes[CACHE_OPERATION]?.value).toBe('get'); + expect(namedLoadSpan?.attributes[DB_COLLECTION_NAME]?.value).toBe('usersLoader'); }, }) .start(); @@ -115,37 +126,5 @@ describe('dataloader auto-instrumentation', () => { await runner.makeRequest('get', '/named'); await runner.completed(); }, 30_000); - - test('names spans after the cache operation when streamed', async () => { - const runner = createRunner() - .withEnv({ STREAMED: 'true' }) - .expect({ - span: (container: SerializedStreamedSpanContainer) => { - const namedLoadSpan = container.items.find( - span => span.attributes?.['db.operation.name']?.value === 'load', - ); - expect(namedLoadSpan?.name).toBe('cache.get'); - expect(namedLoadSpan?.attributes?.['sentry.op']?.value).toBe(CACHE_GET_OP); - expect(namedLoadSpan?.attributes?.['cache.operation']?.value).toBe('get'); - // The loader name is no longer part of the span name, it moved to `db.collection.name`. - expect(namedLoadSpan?.attributes?.['db.collection.name']?.value).toBe('usersLoader'); - }, - }) - .expect({ - span: (container: SerializedStreamedSpanContainer) => { - for (const [operation, op] of Object.entries(CACHE_MUTATION_OPS)) { - const span = container.items.find(item => item.attributes?.['db.operation.name']?.value === operation); - expect(span, `expected a ${operation} span`).toBeDefined(); - expect(span?.name).toBe(op); - expect(span?.attributes?.['sentry.op']?.value).toBe(op); - } - }, - }) - .start(); - - await runner.makeRequest('get', '/named'); - await runner.makeRequest('get', '/cache-ops'); - await runner.completed(); - }, 30_000); }); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/genericPool-v2/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/genericPool-v2/instrument.mjs index 170ad6f6a702..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/genericPool-v2/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/genericPool-v2/instrument.mjs @@ -2,7 +2,6 @@ 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, diff --git a/dev-packages/node-integration-tests/suites/tracing/genericPool-v2/test.ts b/dev-packages/node-integration-tests/suites/tracing/genericPool-v2/test.ts index 5f744edbbf3f..08f30f1545ef 100644 --- a/dev-packages/node-integration-tests/suites/tracing/genericPool-v2/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/genericPool-v2/test.ts @@ -1,3 +1,5 @@ +import { ERROR_TYPE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { DB } from '@sentry/conventions/op'; import { afterAll, describe, expect } from 'vitest'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; @@ -12,32 +14,30 @@ describe('genericPool v2 auto instrumentation', () => { 'instrument.mjs', (createRunner, test) => { test('should auto-instrument `generic-pool` v2 when calling pool.acquire()', async () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - description: 'generic-pool.acquire', - op: 'db', - origin: 'auto.db.generic_pool', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.generic_pool', + name: 'generic-pool.acquire', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: DB }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.db.generic_pool' }, }), status: 'ok', }), expect.objectContaining({ - description: 'generic-pool.acquire', - op: 'db', - origin: 'auto.db.generic_pool', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.generic_pool', + name: 'generic-pool.acquire', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: DB }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.db.generic_pool' }, }), status: 'ok', }), ]), }; - await createRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createRunner().expect({ span: EXPECTED_SPANS }).start().completed(); }); }, { additionalDependencies: { 'generic-pool': '^2.5.0' } }, @@ -49,20 +49,22 @@ describe('genericPool v2 auto instrumentation', () => { 'instrument.mjs', (createRunner, test) => { test('marks the `generic-pool.acquire` span as errored when acquiring fails', async () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - description: 'generic-pool.acquire', - op: 'db', - origin: 'auto.db.generic_pool', - data: expect.objectContaining({ 'sentry.origin': 'auto.db.generic_pool', 'error.type': 'Error' }), - status: 'internal_error', + name: 'generic-pool.acquire', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: DB }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.db.generic_pool' }, + [ERROR_TYPE]: { type: 'string', value: 'Error' }, + }), + status: 'error', }), ]), }; - await createRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createRunner().expect({ span: EXPECTED_SPANS }).start().completed(); }); }, { additionalDependencies: { 'generic-pool': '^2.5.0' } }, diff --git a/dev-packages/node-integration-tests/suites/tracing/genericPool/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/genericPool/instrument.mjs index 170ad6f6a702..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/genericPool/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/genericPool/instrument.mjs @@ -2,7 +2,6 @@ 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, diff --git a/dev-packages/node-integration-tests/suites/tracing/genericPool/test.ts b/dev-packages/node-integration-tests/suites/tracing/genericPool/test.ts index 2c607666691a..bce47d5f779f 100644 --- a/dev-packages/node-integration-tests/suites/tracing/genericPool/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/genericPool/test.ts @@ -1,3 +1,5 @@ +import { ERROR_TYPE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { DB } from '@sentry/conventions/op'; import { afterAll, describe, expect } from 'vitest'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; @@ -8,54 +10,51 @@ describe('genericPool auto instrumentation', () => { createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { test('should auto-instrument `genericPool` package when calling pool.require()', async () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - description: 'generic-pool.acquire', - op: 'db', - origin: 'auto.db.generic_pool', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.generic_pool', + name: 'generic-pool.acquire', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: DB }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.db.generic_pool' }, }), status: 'ok', }), expect.objectContaining({ - description: 'generic-pool.acquire', - op: 'db', - origin: 'auto.db.generic_pool', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.generic_pool', + name: 'generic-pool.acquire', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: DB }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.db.generic_pool' }, }), status: 'ok', }), ]), }; - await createRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createRunner().expect({ span: EXPECTED_SPANS }).start().completed(); }); }); createEsmAndCjsTests(__dirname, 'scenario-error.mjs', 'instrument.mjs', (createRunner, test) => { test('marks the `generic-pool.acquire` span as errored when acquiring fails', async () => { - const EXPECTED_TRANSACTION = { - transaction: 'Test Transaction', - spans: expect.arrayContaining([ + const EXPECTED_SPANS = { + items: expect.arrayContaining([ + expect.objectContaining({ name: 'Test Transaction', is_segment: true }), expect.objectContaining({ - description: 'generic-pool.acquire', - op: 'db', - origin: 'auto.db.generic_pool', - data: expect.objectContaining({ - 'sentry.origin': 'auto.db.generic_pool', - 'error.type': 'TimeoutError', + name: 'generic-pool.acquire', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: DB }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.db.generic_pool' }, + [ERROR_TYPE]: { type: 'string', value: 'TimeoutError' }, }), - status: 'internal_error', + status: 'error', }), ]), }; - await createRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); + await createRunner().expect({ span: EXPECTED_SPANS }).start().completed(); }); }); });