From c532c85a152dd03a228cb84a0141b0054aa6210b Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Oct 2026 16:58:14 +0200 Subject: [PATCH 1/2] test(node): Port outgoing HTTP client spans to streaming Co-Authored-By: GPT-6 --- .../node-suites/excludes.ts | 2 +- .../node-suites/excludes.ts | 2 +- .../instrument.mjs | 1 - .../scenario-fetch.mjs | 0 .../test.ts | 2 +- .../fetch-basic-streamed/instrument.mjs | 1 - .../fetch-error/instrument.mjs | 1 - .../http-client-spans/fetch-error/test.ts | 27 +++++--- .../fetch-forward-request-hook/instrument.mjs | 1 - .../fetch-forward-request-hook/test.ts | 46 +++++++------ .../instrument.mjs | 1 - .../fetch-headers-to-span-attributes/test.ts | 46 ++++++++----- .../fetch-strip-query/instrument.mjs | 1 - .../fetch-strip-query/test.ts | 67 ++++++++++++------- .../http-strip-query/instrument.mjs | 1 - .../http-strip-query/test.ts | 62 ++++++++++------- 16 files changed, 154 insertions(+), 107 deletions(-) rename dev-packages/node-integration-tests/suites/tracing/{http-client-span-streamed => http-client-span}/instrument.mjs (91%) rename dev-packages/node-integration-tests/suites/tracing/{http-client-span-streamed => http-client-span}/scenario-fetch.mjs (100%) rename dev-packages/node-integration-tests/suites/tracing/{http-client-span-streamed => http-client-span}/test.ts (94%) diff --git a/dev-packages/bun-integration-tests/node-suites/excludes.ts b/dev-packages/bun-integration-tests/node-suites/excludes.ts index 5b99ffc866ab..95f8d1f72391 100644 --- a/dev-packages/bun-integration-tests/node-suites/excludes.ts +++ b/dev-packages/bun-integration-tests/node-suites/excludes.ts @@ -39,7 +39,7 @@ const NO_HTTP_SERVER_SPANS = [ // project runs these suites, except the ones in `SENTRY_BUN_EXCLUDE`. const NO_FETCH_INSTRUMENTATION = [ 'suites/tracing/double-baggage/**', - 'suites/tracing/http-client-span-streamed/test.ts', + 'suites/tracing/http-client-span/test.ts', 'suites/tracing/http-client-spans/fetch-basic-streamed/test.ts', 'suites/tracing/http-client-spans/fetch-basic/test.ts', 'suites/tracing/http-client-spans/fetch-error/test.ts', diff --git a/dev-packages/deno-integration-tests/node-suites/excludes.ts b/dev-packages/deno-integration-tests/node-suites/excludes.ts index 36098da333df..e5ec281a7a52 100644 --- a/dev-packages/deno-integration-tests/node-suites/excludes.ts +++ b/dev-packages/deno-integration-tests/node-suites/excludes.ts @@ -9,7 +9,7 @@ const NODE_ONLY = ['suites/anr/test.ts', 'suites/breadcrumbs/**', 'suites/thread // spans, breadcrumbs or headers of outgoing `fetch` requests. const NO_FETCH_INSTRUMENTATION = [ 'suites/tracing/double-baggage/**', - 'suites/tracing/http-client-span-streamed/test.ts', + 'suites/tracing/http-client-span/test.ts', 'suites/tracing/http-client-spans/fetch-basic-streamed/test.ts', 'suites/tracing/http-client-spans/fetch-basic/test.ts', 'suites/tracing/http-client-spans/fetch-error/test.ts', diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/http-client-span/instrument.mjs similarity index 91% rename from dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/instrument.mjs rename to dev-packages/node-integration-tests/suites/tracing/http-client-span/instrument.mjs index b56505ef5e2d..3a69e61ceb90 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-span/instrument.mjs @@ -6,6 +6,5 @@ Sentry.init({ release: '1.0', tracesSampleRate: 1.0, transport: loggingTransport, - traceLifecycle: 'stream', clientReportFlushInterval: 1_000, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/scenario-fetch.mjs b/dev-packages/node-integration-tests/suites/tracing/http-client-span/scenario-fetch.mjs similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/scenario-fetch.mjs rename to dev-packages/node-integration-tests/suites/tracing/http-client-span/scenario-fetch.mjs diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-span/test.ts similarity index 94% rename from dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/test.ts rename to dev-packages/node-integration-tests/suites/tracing/http-client-span/test.ts index d5187a062b08..8a40bbb27a2f 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-span/test.ts @@ -1,7 +1,7 @@ import { afterAll, describe, expect } from 'vitest'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; -describe('http.client span with streaming enabled', () => { +describe('http.client span without a local parent', () => { afterAll(() => { cleanupChildProcesses(); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/instrument.mjs index b52ee5c5fdf1..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/instrument.mjs @@ -5,6 +5,5 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, - traceLifecycle: 'stream', transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-error/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-error/instrument.mjs index 170ad6f6a702..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-error/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-error/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/http-client-spans/fetch-error/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-error/test.ts index dcd890b1d766..27a9a0ba941e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-error/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-error/test.ts @@ -1,3 +1,5 @@ +import { HTTP_CLIENT } from '@sentry/conventions/op'; +import { SENTRY_OP, SENTRY_ORIGIN, URL_FULL } from '@sentry/conventions/attributes'; import { afterAll, describe, expect } from 'vitest'; import { cleanupChildProcesses, createCjsTests } from '../../../../utils/runner'; @@ -10,16 +12,21 @@ describe('outgoing fetch spans - error', () => { test('captures an errored span for a failed outgoing fetch request', async () => { await createRunner() .expect({ - transaction: { - transaction: 'test_transaction', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: expect.stringMatching(/GET http:\/\/localhost:\d+\//), - op: 'http.client', - origin: 'auto.http.node_fetch', - status: 'internal_error', - }), - ]), + span: container => { + expect(container.items.find(span => span.is_segment)?.name).toBe('test_transaction'); + expect(container.items.filter(span => !span.is_segment)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'GET localhost', + status: 'error', + attributes: expect.objectContaining({ + [URL_FULL]: { type: 'string', value: expect.stringMatching(/http:\/\/localhost:\d+\//) }, + [SENTRY_OP]: { type: 'string', value: HTTP_CLIENT }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.http.node_fetch' }, + }), + }), + ]), + ); }, }) .start() diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-forward-request-hook/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-forward-request-hook/instrument.mjs index 04a31f91c150..8cb369af50dc 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-forward-request-hook/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-forward-request-hook/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/http-client-spans/fetch-forward-request-hook/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-forward-request-hook/test.ts index 84c79873d8dd..9e533959e3e7 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-forward-request-hook/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-forward-request-hook/test.ts @@ -1,3 +1,5 @@ +import { HTTP_CLIENT } from '@sentry/conventions/op'; +import { HTTP_RESPONSE_STATUS_CODE, SENTRY_OP, SENTRY_ORIGIN, URL_FULL } from '@sentry/conventions/attributes'; import { createTestServer } from '@sentry-internal/test-utils'; import { afterAll, describe, expect } from 'vitest'; import { cleanupChildProcesses, createCjsTests } from '../../../../utils/runner'; @@ -9,7 +11,7 @@ describe('outgoing fetch spans - request/response hooks', () => { createCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { test('adds requestHook and responseHook attributes to spans of outgoing fetch requests', async () => { - expect.assertions(3); + expect.assertions(4); const [SERVER_URL, closeTestServer] = await createTestServer() .get('/api/v0', () => { @@ -29,33 +31,35 @@ describe('outgoing fetch spans - request/response hooks', () => { await createRunner() .withEnv({ SERVER_URL }) .expect({ - transaction: { - transaction: 'test_transaction', - spans: [ + span: container => { + expect(container.items.find(span => span.is_segment)?.name).toBe('test_transaction'); + expect(container.items.filter(span => !span.is_segment)).toEqual([ expect.objectContaining({ - description: expect.stringMatching(/GET .*\/api\/v0/), - op: 'http.client', - origin: 'auto.http.node_fetch', + name: 'GET localhost', status: 'ok', - data: expect.objectContaining({ - 'sentry.request.hook': '/api/v0', - 'sentry.response.hook.path': '/api/v0', - 'sentry.response.hook.status_code': 200, + attributes: expect.objectContaining({ + [URL_FULL]: { type: 'string', value: expect.stringMatching(/.*\/api\/v0/) }, + [SENTRY_OP]: { type: 'string', value: HTTP_CLIENT }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.http.node_fetch' }, + 'sentry.request.hook': { type: 'string', value: '/api/v0' }, + 'sentry.response.hook.path': { type: 'string', value: '/api/v0' }, + 'sentry.response.hook.status_code': { type: 'integer', value: 200 }, }), }), expect.objectContaining({ - description: expect.stringMatching(/GET .*\/api\/v1/), - op: 'http.client', - origin: 'auto.http.node_fetch', - status: 'not_found', - data: expect.objectContaining({ - 'sentry.request.hook': '/api/v1', - 'sentry.response.hook.path': '/api/v1', - 'sentry.response.hook.status_code': 404, - 'http.response.status_code': 404, + name: 'GET localhost', + status: 'error', + attributes: expect.objectContaining({ + [URL_FULL]: { type: 'string', value: expect.stringMatching(/.*\/api\/v1/) }, + [SENTRY_OP]: { type: 'string', value: HTTP_CLIENT }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.http.node_fetch' }, + 'sentry.request.hook': { type: 'string', value: '/api/v1' }, + 'sentry.response.hook.path': { type: 'string', value: '/api/v1' }, + 'sentry.response.hook.status_code': { type: 'integer', value: 404 }, + [HTTP_RESPONSE_STATUS_CODE]: { type: 'integer', value: 404 }, }), }), - ], + ]); }, }) .start() diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-headers-to-span-attributes/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-headers-to-span-attributes/instrument.mjs index 11ff9c8eb451..cf8fec6e7e1d 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-headers-to-span-attributes/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-headers-to-span-attributes/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/http-client-spans/fetch-headers-to-span-attributes/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-headers-to-span-attributes/test.ts index 6bfb18909525..d0bd81d8d5ea 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-headers-to-span-attributes/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-headers-to-span-attributes/test.ts @@ -1,3 +1,10 @@ +import { HTTP_CLIENT } from '@sentry/conventions/op'; +import { + HTTP_RESPONSE_HEADER_CONTENT_LENGTH, + SENTRY_OP, + SENTRY_ORIGIN, + URL_FULL, +} from '@sentry/conventions/attributes'; import { createTestServer } from '@sentry-internal/test-utils'; import { afterAll, describe, expect } from 'vitest'; import { cleanupChildProcesses, createCjsTests } from '../../../../utils/runner'; @@ -9,7 +16,7 @@ describe('outgoing fetch spans - headers to span attributes', () => { createCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { test('maps configured request & response headers to span attributes', async () => { - expect.assertions(2); + expect.assertions(3); const [SERVER_URL, closeTestServer] = await createTestServer() .get('/api/v0', headers => { @@ -20,24 +27,27 @@ describe('outgoing fetch spans - headers to span attributes', () => { await createRunner() .withEnv({ SERVER_URL }) .expect({ - transaction: { - transaction: 'test_transaction', - spans: expect.arrayContaining([ - expect.objectContaining({ - description: expect.stringMatching(/GET .*\/api\/v0/), - op: 'http.client', - origin: 'auto.http.node_fetch', - data: expect.objectContaining({ - 'http.request.header.x-test-header': ['test-value'], - // Listed in `headersToSpanAttributes`, but the built-in denylist still wins. - 'http.request.header.authorization': ['[Filtered]'], - // Listed in `headersToSpanAttributes`, but denied via `dataCollection.httpHeaders`. - 'http.request.header.x-tenant-id': ['[Filtered]'], - 'http.response.header.x-powered-by': ['Express'], - 'http.response.header.content-length': ['[Filtered]'], + span: container => { + expect(container.items.find(span => span.is_segment)?.name).toBe('test_transaction'); + expect(container.items.filter(span => !span.is_segment)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'GET localhost', + attributes: expect.objectContaining({ + [URL_FULL]: { type: 'string', value: expect.stringMatching(/.*\/api\/v0/) }, + [SENTRY_OP]: { type: 'string', value: HTTP_CLIENT }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.http.node_fetch' }, + 'http.request.header.x-test-header': { type: 'array', value: ['test-value'] }, + // Listed in `headersToSpanAttributes`, but the built-in denylist still wins. + 'http.request.header.authorization': { type: 'array', value: ['[Filtered]'] }, + // Listed in `headersToSpanAttributes`, but denied via `dataCollection.httpHeaders`. + 'http.request.header.x-tenant-id': { type: 'array', value: ['[Filtered]'] }, + 'http.response.header.x-powered-by': { type: 'array', value: ['Express'] }, + [HTTP_RESPONSE_HEADER_CONTENT_LENGTH]: { type: 'array', value: ['[Filtered]'] }, + }), }), - }), - ]), + ]), + ); }, }) .start() diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/instrument.mjs index 170ad6f6a702..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/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/http-client-spans/fetch-strip-query/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/test.ts index 8d2a20e79d9e..99b485b76141 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/test.ts @@ -1,3 +1,20 @@ +import { HTTP_CLIENT } from '@sentry/conventions/op'; +import { + HTTP_REQUEST_METHOD, + HTTP_RESPONSE_STATUS_CODE, + NETWORK_PEER_ADDRESS, + NETWORK_PEER_PORT, + SENTRY_KIND, + SENTRY_OP, + SENTRY_ORIGIN, + SERVER_ADDRESS, + SERVER_PORT, + URL_FULL, + URL_PATH, + URL_QUERY, + URL_SCHEME, + USER_AGENT_ORIGINAL, +} from '@sentry/conventions/attributes'; import { createTestServer } from '@sentry-internal/test-utils'; import { afterAll, describe, expect } from 'vitest'; import { cleanupChildProcesses, createCjsTests } from '../../../../utils/runner'; @@ -21,34 +38,34 @@ describe('outgoing fetch spans - strip query', () => { await createRunner() .withEnv({ SERVER_URL }) .expect({ - transaction: txn => { - expect(txn.transaction).toEqual('test_transaction'); - expect(txn.spans).toHaveLength(1); - expect(txn.spans?.[0]).toMatchObject({ - data: { - 'url.full': `${SERVER_URL}/api/v0/users?id=1`, - 'url.path': '/api/v0/users', - 'url.query': 'id=1', - 'url.scheme': 'http', - 'http.request.method': 'GET', - 'http.request.method_original': 'GET', - 'http.response.status_code': 200, - 'network.peer.address': '::1', - 'network.peer.port': expect.any(Number), - 'sentry.kind': 'client', - 'server.port': expect.any(Number), - 'user_agent.original': 'node', - 'sentry.op': 'http.client', - 'sentry.origin': 'auto.http.node_fetch', - 'server.address': 'localhost', + span: container => { + const segment = container.items.find(span => span.is_segment); + const spans = container.items.filter(span => !span.is_segment); + expect(segment?.name).toEqual('test_transaction'); + expect(spans).toHaveLength(1); + expect(spans[0]).toMatchObject({ + attributes: { + [URL_FULL]: { type: 'string', value: `${SERVER_URL}/api/v0/users?id=1` }, + [URL_PATH]: { type: 'string', value: '/api/v0/users' }, + [URL_QUERY]: { type: 'string', value: 'id=1' }, + [URL_SCHEME]: { type: 'string', value: 'http' }, + [HTTP_REQUEST_METHOD]: { type: 'string', value: 'GET' }, + 'http.request.method_original': { type: 'string', value: 'GET' }, + [HTTP_RESPONSE_STATUS_CODE]: { type: 'integer', value: 200 }, + [NETWORK_PEER_ADDRESS]: { type: 'string', value: '::1' }, + [NETWORK_PEER_PORT]: { type: 'integer', value: expect.any(Number) }, + [SENTRY_KIND]: { type: 'string', value: 'client' }, + [SERVER_PORT]: { type: 'integer', value: expect.any(Number) }, + [USER_AGENT_ORIGINAL]: { type: 'string', value: 'node' }, + [SENTRY_OP]: { type: 'string', value: HTTP_CLIENT }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.http.node_fetch' }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, }, - description: `GET ${SERVER_URL}/api/v0/users`, - op: 'http.client', - origin: 'auto.http.node_fetch', + name: 'GET localhost', status: 'ok', - parent_span_id: txn.contexts?.trace?.span_id, + parent_span_id: segment?.span_id, span_id: expect.stringMatching(/[a-f\d]{16}/), - trace_id: txn.contexts?.trace?.trace_id, + trace_id: segment?.trace_id, }); }, }) diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/instrument.mjs index 170ad6f6a702..46a27dd03b74 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/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/http-client-spans/http-strip-query/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/test.ts index 115b2517e81f..e018a905ba28 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/test.ts @@ -1,3 +1,19 @@ +import { HTTP_CLIENT } from '@sentry/conventions/op'; +import { + HTTP_REQUEST_METHOD, + HTTP_RESPONSE_BODY_SIZE, + HTTP_RESPONSE_STATUS_CODE, + HTTP_RESPONSE_STATUS_TEXT, + NETWORK_PEER_ADDRESS, + NETWORK_PEER_PORT, + NETWORK_TRANSPORT, + SENTRY_KIND, + SENTRY_OP, + SENTRY_ORIGIN, + SERVER_ADDRESS, + URL_FULL, + URL_QUERY, +} from '@sentry/conventions/attributes'; import { createTestServer } from '@sentry-internal/test-utils'; import { afterAll, describe, expect } from 'vitest'; import { cleanupChildProcesses, createCjsTests } from '../../../../utils/runner'; @@ -21,32 +37,32 @@ describe('outgoing http spans - strip query', () => { await createRunner() .withEnv({ SERVER_URL }) .expect({ - transaction: txn => { - expect(txn.transaction).toEqual('test_transaction'); - expect(txn.spans).toHaveLength(1); - expect(txn.spans?.[0]).toMatchObject({ - data: { - 'url.full': `${SERVER_URL}/api/v0/users?id=1`, - 'http.request.method': 'GET', - 'url.query': 'id=1', - 'http.response.status_code': 200, - 'http.response.body.size': 0, - 'http.response.status_text': 'OK', - 'network.peer.address': '::1', - 'server.address': 'localhost', - 'network.peer.port': expect.any(Number), - 'network.transport': 'tcp', - 'sentry.kind': 'client', - 'sentry.op': 'http.client', - 'sentry.origin': 'auto.http.client', + span: container => { + const segment = container.items.find(span => span.is_segment); + const spans = container.items.filter(span => !span.is_segment); + expect(segment?.name).toEqual('test_transaction'); + expect(spans).toHaveLength(1); + expect(spans[0]).toMatchObject({ + attributes: { + [URL_FULL]: { type: 'string', value: `${SERVER_URL}/api/v0/users?id=1` }, + [HTTP_REQUEST_METHOD]: { type: 'string', value: 'GET' }, + [URL_QUERY]: { type: 'string', value: 'id=1' }, + [HTTP_RESPONSE_STATUS_CODE]: { type: 'integer', value: 200 }, + [HTTP_RESPONSE_BODY_SIZE]: { type: 'integer', value: 0 }, + [HTTP_RESPONSE_STATUS_TEXT]: { type: 'string', value: 'OK' }, + [NETWORK_PEER_ADDRESS]: { type: 'string', value: '::1' }, + [SERVER_ADDRESS]: { type: 'string', value: 'localhost' }, + [NETWORK_PEER_PORT]: { type: 'integer', value: expect.any(Number) }, + [NETWORK_TRANSPORT]: { type: 'string', value: 'tcp' }, + [SENTRY_KIND]: { type: 'string', value: 'client' }, + [SENTRY_OP]: { type: 'string', value: HTTP_CLIENT }, + [SENTRY_ORIGIN]: { type: 'string', value: 'auto.http.client' }, }, - description: `GET ${SERVER_URL}/api/v0/users`, - op: 'http.client', - origin: 'auto.http.client', + name: 'GET localhost', status: 'ok', - parent_span_id: txn.contexts?.trace?.span_id, + parent_span_id: segment?.span_id, span_id: expect.stringMatching(/[a-f\d]{16}/), - trace_id: txn.contexts?.trace?.trace_id, + trace_id: segment?.trace_id, }); }, }) From 1599bd4a5d60aee7f43010eee18bc54111f17581 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 9 Oct 2026 11:12:33 +0200 Subject: [PATCH 2/2] test(node): Assert parentless HTTP client span is a segment Co-Authored-By: GPT-6 --- .../suites/tracing/http-client-span/test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-span/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-span/test.ts index 8a40bbb27a2f..11e3e07fd557 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-span/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-span/test.ts @@ -18,6 +18,7 @@ describe('http.client span without a local parent', () => { ); expect(httpClientSpan).toBeDefined(); + expect(httpClientSpan?.is_segment).toBe(true); // The URL path is high cardinality, so a streamed span name keeps only the domain. expect(httpClientSpan?.name).toBe('GET localhost'); expect(httpClientSpan?.attributes['url.domain']?.value).toBe('localhost');