diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/scenario.ts new file mode 100644 index 000000000000..af9609ab863b --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/scenario.ts @@ -0,0 +1,12 @@ +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, +}); + +Sentry.startSpan({ name: 'test_span' }, () => undefined); diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/test.ts new file mode 100644 index 000000000000..d0b64a9393bb --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/test.ts @@ -0,0 +1,26 @@ +import { SENTRY_SEGMENT_NAME_SOURCE } from '@sentry/conventions/attributes'; +import { afterAll, expect, test } from 'vitest'; +import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; + +afterAll(() => { + cleanupChildProcesses(); +}); + +test('sends a manually started root span with source custom', async () => { + await createRunner(__dirname, 'scenario.ts') + .expect({ + transaction: { + transaction: 'test_span', + transaction_info: { source: 'custom' }, + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { [SENTRY_SEGMENT_NAME_SOURCE]: 'custom' }, + }, + }, + }, + }) + .start() + .completed(); +}); diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/with-children/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/with-children/scenario.ts new file mode 100644 index 000000000000..3e43091b62c8 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/with-children/scenario.ts @@ -0,0 +1,29 @@ +import * as Sentry from '@sentry/node'; +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, + transport: loggingTransport, + release: '1.0.0', +}); + +Sentry.startSpan({ name: 'test-span', op: 'test' }, segmentSpan => { + Sentry.startSpan({ name: 'test-child-span', op: 'test-child' }, () => { + // noop + }); + + const inactiveSpan = Sentry.startInactiveSpan({ + name: 'test-inactive-span', + links: [{ context: segmentSpan.spanContext(), attributes: { [SENTRY_LINK_TYPE]: 'some_relation' } }], + }); + inactiveSpan.end(); + + Sentry.startSpanManual({ name: 'test-manual-span' }, span => { + span.end(); + }); +}); + +void Sentry.flush(); diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/with-children/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/with-children/test.ts new file mode 100644 index 000000000000..9ddbf4f7e629 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-static/with-children/test.ts @@ -0,0 +1,108 @@ +import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; +import { SENTRY_LINK_TYPE, SENTRY_OP, SENTRY_ORIGIN, SENTRY_SEGMENT_NAME_SOURCE } from '@sentry/conventions/attributes'; +import { expect, test } from 'vitest'; +import { EXPECTED_SDK_NAME } from '../../../../../utils'; +import { assertSentryTransaction } from '../../../../../utils/assertions'; +import { createRunner } from '../../../../../utils/runner'; + +test('sends a transaction envelope with correct envelope header', async () => { + await createRunner(__dirname, 'scenario.ts') + .expectHeader({ + transaction: { + sent_at: expect.any(String), + sdk: { + name: EXPECTED_SDK_NAME, + version: SDK_VERSION, + }, + trace: expect.objectContaining({ + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + transaction: 'test-span', + }), + }, + }) + .start() + .completed(); +}); + +test('sends a transaction with correct spans for a manually started span with children', async () => { + await createRunner(__dirname, 'scenario.ts') + .expect({ + transaction: transaction => { + const rootSpanId = transaction.contexts?.trace?.span_id; + const traceId = transaction.contexts?.trace?.trace_id; + + assertSentryTransaction(transaction, { + transaction: 'test-span', + transaction_info: { source: 'custom' }, + release: '1.0.0', + environment: 'production', + sdk: { name: EXPECTED_SDK_NAME, version: SDK_VERSION }, + contexts: { + trace: { + span_id: expect.stringMatching(/^[\da-f]{16}$/), + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + op: 'test', + origin: 'manual', + status: 'ok', + data: { + [SENTRY_OP]: 'test', + [SENTRY_ORIGIN]: 'manual', + [SENTRY_SEGMENT_NAME_SOURCE]: 'custom', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + }, + }, + }, + }); + + expect(transaction.spans).toEqual([ + { + description: 'test-child-span', + op: 'test-child', + origin: 'manual', + data: { [SENTRY_OP]: 'test-child', [SENTRY_ORIGIN]: 'manual' }, + parent_span_id: rootSpanId, + trace_id: traceId, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + }, + { + description: 'test-inactive-span', + origin: 'manual', + data: { [SENTRY_ORIGIN]: 'manual' }, + parent_span_id: rootSpanId, + trace_id: traceId, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + links: [ + { + attributes: { [SENTRY_LINK_TYPE]: 'some_relation' }, + sampled: true, + span_id: rootSpanId, + trace_id: traceId, + }, + ], + }, + { + description: 'test-manual-span', + origin: 'manual', + data: { [SENTRY_ORIGIN]: 'manual' }, + parent_span_id: rootSpanId, + trace_id: traceId, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + }, + ]); + }, + }) + .start() + .completed(); +}); diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/scenario.ts index af9609ab863b..c2186df94b7f 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/scenario.ts @@ -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/public-api/startSpan/basic-usage/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/test.ts index d616c04c457d..ad5143480748 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/test.ts @@ -9,35 +9,14 @@ afterAll(() => { test('sends a manually started root span with source custom', async () => { await createRunner(__dirname, 'scenario.ts') .expect({ - transaction: { - transaction: 'test_span', - transaction_info: { source: 'custom' }, - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - data: { [SENTRY_SEGMENT_NAME_SOURCE]: 'custom' }, - }, - }, - }, - }) - .start() - .completed(); -}); - -test("doesn't change the name for manually started spans even if attributes triggering inference are set", async () => { - await createRunner(__dirname, 'scenario.ts') - .expect({ - transaction: { - transaction: 'test_span', - transaction_info: { source: 'custom' }, - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - data: { [SENTRY_SEGMENT_NAME_SOURCE]: 'custom' }, - }, - }, + span: container => { + const segment = container.items.find(span => span.is_segment); + expect(segment?.name).toBe('test_span'); + expect(segment?.span_id).toEqual(expect.any(String)); + expect(segment?.trace_id).toEqual(expect.any(String)); + expect(segment?.start_timestamp).toEqual(expect.any(Number)); + expect(segment?.end_timestamp).toEqual(expect.any(Number)); + expect(segment?.attributes[SENTRY_SEGMENT_NAME_SOURCE]).toEqual({ type: 'string', value: 'custom' }); }, }) .start() diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-streamed/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/with-children/scenario.ts similarity index 96% rename from dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-streamed/scenario.ts rename to dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/with-children/scenario.ts index ce3f914d51b8..f84a5df779dd 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-streamed/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/with-children/scenario.ts @@ -4,7 +4,6 @@ import { loggingTransport } from '@sentry-internal/node-integration-tests'; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1.0, - traceLifecycle: 'stream', integrations: [Sentry.spanStreamingIntegration()], transport: loggingTransport, release: '1.0.0', diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-streamed/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/with-children/test.ts similarity index 98% rename from dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-streamed/test.ts rename to dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/with-children/test.ts index 87a40fa69b3f..f873cb97a41b 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/with-children/test.ts @@ -13,8 +13,8 @@ import { SENTRY_ORIGIN, } from '@sentry/conventions/attributes'; import { expect, test } from 'vitest'; -import { createRunner } from '../../../../utils/runner'; -import { EXPECTED_SDK_NAME } from '../../../../utils'; +import { createRunner } from '../../../../../utils/runner'; +import { EXPECTED_SDK_NAME } from '../../../../../utils'; test('sends a streamed span envelope with correct envelope header', async () => { await createRunner(__dirname, 'scenario.ts') diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans-static/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans-static/scenario.ts new file mode 100644 index 000000000000..c372394e37e5 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans-static/scenario.ts @@ -0,0 +1,34 @@ +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, +}); + +Sentry.startSpan({ name: 'root_span' }, () => { + Sentry.startSpan( + { + name: 'span_1', + attributes: { + foo: 'bar', + baz: [1, 2, 3], + }, + }, + () => undefined, + ); + + // span_2 doesn't finish + Sentry.startInactiveSpan({ name: 'span_2' }); + + Sentry.startSpan({ name: 'span_3' }, () => { + // span_4 is the child of span_3 but doesn't finish. + Sentry.startInactiveSpan({ name: 'span_4', attributes: { qux: 'quux' } }); + + // span_5 is another child of span_3 but finishes. + Sentry.startSpan({ name: 'span_5' }, () => undefined); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans-static/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans-static/test.ts new file mode 100644 index 000000000000..c01b837db5f7 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans-static/test.ts @@ -0,0 +1,47 @@ +import type { SpanJSON } from '@sentry/core'; +import { afterAll, expect, test } from 'vitest'; +import { assertSentryTransaction } from '../../../../utils/assertions'; +import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; + +afterAll(() => { + cleanupChildProcesses(); +}); + +test('should report finished spans as children of the root transaction.', async () => { + await createRunner(__dirname, 'scenario.ts') + .expect({ + transaction: transaction => { + const rootSpanId = transaction.contexts?.trace?.span_id; + const span3Id = transaction.spans?.[1]?.span_id; + + expect(rootSpanId).toEqual(expect.any(String)); + expect(span3Id).toEqual(expect.any(String)); + + assertSentryTransaction(transaction, { + transaction: 'root_span', + spans: [ + { + description: 'span_1', + data: { + foo: 'bar', + baz: [1, 2, 3], + }, + parent_span_id: rootSpanId, + }, + { + description: 'span_3', + parent_span_id: rootSpanId, + data: {}, + }, + { + description: 'span_5', + parent_span_id: span3Id, + data: {}, + }, + ] as SpanJSON[], + }); + }, + }) + .start() + .completed(); +}); diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/scenario.ts index c372394e37e5..ce103213161b 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/scenario.ts @@ -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/public-api/startSpan/with-nested-spans/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts index c01b837db5f7..2fe870bfba64 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts @@ -1,45 +1,46 @@ -import type { SpanJSON } from '@sentry/core'; import { afterAll, expect, test } from 'vitest'; -import { assertSentryTransaction } from '../../../../utils/assertions'; import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; afterAll(() => { cleanupChildProcesses(); }); -test('should report finished spans as children of the root transaction.', async () => { +test('should report finished spans as children of the root span.', async () => { await createRunner(__dirname, 'scenario.ts') .expect({ - transaction: transaction => { - const rootSpanId = transaction.contexts?.trace?.span_id; - const span3Id = transaction.spans?.[1]?.span_id; + span: container => { + const segment = container.items.find(span => span.is_segment); + // Streamed spans arrive in completion order; compare the original creation order. + const spans = container.items + .filter(span => !span.is_segment) + .sort((a, b) => a.start_timestamp - b.start_timestamp); + const rootSpanId = segment?.span_id; + const span3Id = spans.find(span => span.name === 'span_3')?.span_id; expect(rootSpanId).toEqual(expect.any(String)); expect(span3Id).toEqual(expect.any(String)); - assertSentryTransaction(transaction, { - transaction: 'root_span', - spans: [ - { - description: 'span_1', - data: { - foo: 'bar', - baz: [1, 2, 3], - }, - parent_span_id: rootSpanId, + expect(segment?.name).toBe('root_span'); + expect(segment?.start_timestamp).toEqual(expect.any(Number)); + expect(segment?.end_timestamp).toEqual(expect.any(Number)); + expect(spans).toMatchObject([ + { + name: 'span_1', + attributes: { + foo: { type: 'string', value: 'bar' }, + baz: { type: 'array', value: [1, 2, 3] }, }, - { - description: 'span_3', - parent_span_id: rootSpanId, - data: {}, - }, - { - description: 'span_5', - parent_span_id: span3Id, - data: {}, - }, - ] as SpanJSON[], - }); + parent_span_id: rootSpanId, + }, + { + name: 'span_3', + parent_span_id: rootSpanId, + }, + { + name: 'span_5', + parent_span_id: span3Id, + }, + ]); }, }) .start()