diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-fetch-no-bindings/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-fetch-no-bindings/index.ts index 1b56811556cd..6ec278fc9ace 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-fetch-no-bindings/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-fetch-no-bindings/index.ts @@ -16,7 +16,6 @@ class MyDurableObjectBase extends DurableObject { export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), MyDurableObjectBase, @@ -25,7 +24,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), { diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-fetch-no-bindings/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-fetch-no-bindings/test.ts index 48abf6f3d609..70045a795c93 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-fetch-no-bindings/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-fetch-no-bindings/test.ts @@ -1,135 +1,75 @@ import { expect, it } from 'vitest'; -import type { Event } from '@sentry/core'; +import type { SerializedStreamedSpan } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; it('propagates trace from worker to durable object over stub.fetch() when rpcTracePropagationBindings is empty', async ({ signal, }) => { - let workerTraceId: string | undefined; - let workerSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let workerSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /hello', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + // `/hello` is a raw URL, so the streamed segment name keeps the method only. + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' }); + doSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /', - }), - ); - workerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - workerSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.name).toBe('GET /'); + workerSpan = segmentSpan; }) .unordered() .start(signal); await runner.makeRequest('get', '/'); await runner.completed(); - expect(workerTraceId).toBeDefined(); - expect(doTraceId).toBe(workerTraceId); - - expect(workerSpanId).toBeDefined(); - expect(doParentSpanId).toBe(workerSpanId); + expect(workerSpan?.trace_id).toBeDefined(); + expect(doSpan?.trace_id).toBe(workerSpan?.trace_id); + expect(doSpan?.parent_span_id).toBe(workerSpan?.span_id); }); it('propagates trace from queue handler to durable object over stub.fetch() when rpcTracePropagationBindings is empty', async ({ signal, }) => { - let queueTraceId: string | undefined; - let queueSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let queueSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /hello', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' }); + doSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'queue.process', - data: expect.objectContaining({ - 'sentry.origin': 'auto.faas.cloudflare.queue', - }), - origin: 'auto.faas.cloudflare.queue', - }), - }), - transaction: 'process my-queue', - }), - ); - queueTraceId = transactionEvent.contexts?.trace?.trace_id as string; - queueSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(segmentSpan?.name).toBe('process my-queue'); + expect(getSpanOp(segmentSpan!)).toBe('queue.process'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.faas.cloudflare.queue', + }); + queueSpan = segmentSpan; }) - // Also expect the fetch transaction from the /queue/send request + // Also expect the fetch span from the /queue/send request .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /queue/send', - }), - ); + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/queue/send' }); }) .unordered() .start(signal); @@ -137,70 +77,43 @@ it('propagates trace from queue handler to durable object over stub.fetch() when await runner.makeRequest('get', '/queue/send'); await runner.completed(); - expect(queueTraceId).toBeDefined(); - expect(doTraceId).toBe(queueTraceId); - - expect(queueSpanId).toBeDefined(); - expect(doParentSpanId).toBe(queueSpanId); + expect(queueSpan?.trace_id).toBeDefined(); + expect(doSpan?.trace_id).toBe(queueSpan?.trace_id); + expect(doSpan?.parent_span_id).toBe(queueSpan?.span_id); }); it('propagates trace from scheduled handler to durable object over stub.fetch() when rpcTracePropagationBindings is empty', async ({ signal, }) => { - let scheduledTraceId: string | undefined; - let scheduledSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let scheduledSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .withWranglerArgs('--test-scheduled') .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /hello', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' }); + doSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'function', - data: expect.objectContaining({ - 'sentry.origin': 'auto.faas.cloudflare.scheduled', - }), - origin: 'auto.faas.cloudflare.scheduled', - }), - }), - }), - ); - scheduledTraceId = transactionEvent.contexts?.trace?.trace_id as string; - scheduledSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('function'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.faas.cloudflare.scheduled', + }); + scheduledSpan = segmentSpan; }) .unordered() .start(signal); await runner.makeRequest('get', '/__scheduled?cron=*+*+*+*+*'); await runner.completed(); - expect(scheduledTraceId).toBeDefined(); - expect(doTraceId).toBe(scheduledTraceId); - - expect(scheduledSpanId).toBeDefined(); - expect(doParentSpanId).toBe(scheduledSpanId); + expect(scheduledSpan?.trace_id).toBeDefined(); + expect(doSpan?.trace_id).toBe(scheduledSpan?.trace_id); + expect(doSpan?.parent_span_id).toBe(scheduledSpan?.span_id); }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-binding-casing/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-binding-casing/index.ts index bff88cbdcdd7..9ddb6ac416e9 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-binding-casing/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-binding-casing/index.ts @@ -25,7 +25,6 @@ class MyDurableObjectBase extends DurableObject { export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), MyDurableObjectBase, @@ -34,7 +33,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, // Both targets are written in a casing the bindings do not use, and the regex carries the `g` // flag, which makes `test()` stateful unless the SDK normalizes it away. diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-binding-casing/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-binding-casing/test.ts index feb00eae6a1e..e7ee022bc503 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-binding-casing/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-binding-casing/test.ts @@ -1,13 +1,15 @@ import { expect, it } from 'vitest'; -import type { Envelope, Event } from '@sentry/core'; +import type { Envelope, SerializedStreamedSpan } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; it('propagates trace over RPC when the binding casing differs from rpcTracePropagationBindings', async ({ signal }) => { - const transactionsByName = new Map(); + const segmentSpansByName = new Map(); const collect = (envelope: Envelope): void => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - transactionsByName.set(transactionEvent.transaction as string, transactionEvent); + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + expect(segmentSpan).toBeDefined(); + segmentSpansByName.set(segmentSpan!.name, segmentSpan!); }; const runner = createRunner(__dirname) @@ -23,16 +25,18 @@ it('propagates trace over RPC when the binding casing differs from rpcTracePropa await runner.completed(); - const worker = transactionsByName.get('GET /rpc/all'); - expect(worker?.contexts?.trace?.op).toBe('http.server'); + // `/rpc/all` is a raw URL, so the streamed segment name keeps the method only. + const worker = segmentSpansByName.get('GET'); + expect(getSpanOp(worker!)).toBe('http.server'); + expect(worker?.attributes['url.path']).toEqual({ type: 'string', value: '/rpc/all' }); // `sayHello` comes from the string target, `alpha` and `beta` from the regex target. `beta` is the // one a stateful `g` regex would miss, because `alpha` already advanced its `lastIndex`. for (const methodName of ['sayHello', 'alpha', 'beta']) { - const durableObject = transactionsByName.get(methodName); + const durableObject = segmentSpansByName.get(methodName); - expect(durableObject?.contexts?.trace?.op).toBe('rpc'); - expect(durableObject?.contexts?.trace?.trace_id).toBe(worker?.contexts?.trace?.trace_id); - expect(durableObject?.contexts?.trace?.parent_span_id).toBe(worker?.contexts?.trace?.span_id); + expect(getSpanOp(durableObject!)).toBe('rpc'); + expect(durableObject?.trace_id).toBe(worker?.trace_id); + expect(durableObject?.parent_span_id).toBe(worker?.span_id); } }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-disabled/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-disabled/index.ts index 75b9e36cd377..cada6d7ac40c 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-disabled/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-disabled/index.ts @@ -15,7 +15,6 @@ class MyDurableObjectBase extends DurableObject { export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), MyDurableObjectBase, @@ -24,7 +23,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), { diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-disabled/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-disabled/test.ts index 38e320c7a41d..e5a23a362d68 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-disabled/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-disabled/test.ts @@ -1,34 +1,22 @@ import { expect, it } from 'vitest'; -import type { Event } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; it('does not trace an RPC method call when rpcTracePropagationBindings is empty', async ({ signal }) => { const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ op: 'http.server' }), - }), - transaction: 'GET /rpc/hello', - }), - ); + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/rpc/hello' }); }) - // Ordered: a `sayHello` transaction from the receiver would arrive here and fail this - // expectation. Without the trailing Sentry argument the receiver never traces the call. + // Ordered: a `sayHello` span from the receiver would arrive here and fail this expectation. + // Without the trailing Sentry argument the receiver never traces the call. .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ op: 'http.server' }), - }), - transaction: 'GET /sentinel', - }), - ); + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/sentinel' }); }) .start(signal); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc/index.ts index 50bbdf5a8a2c..9bbf994c23bc 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc/index.ts @@ -19,7 +19,6 @@ class MyDurableObjectBase extends DurableObject { export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), MyDurableObjectBase, @@ -28,7 +27,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, rpcTracePropagationBindings: ['MY_DURABLE_OBJECT'], }), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc/test.ts index f86348ab6fbc..e52aeaa20ce4 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc/test.ts @@ -1,53 +1,32 @@ import { expect, it } from 'vitest'; -import type { Event } from '@sentry/core'; +import type { SerializedStreamedSpan } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; it('propagates trace from worker to durable object via RPC method call', async ({ signal }) => { - let workerTraceId: string | undefined; - let workerSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let workerSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'rpc', - data: expect.objectContaining({ - 'sentry.origin': 'auto.faas.cloudflare.durable_object', - }), - origin: 'auto.faas.cloudflare.durable_object', - }), - }), - transaction: 'sayHello', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(segmentSpan?.name).toBe('sayHello'); + expect(getSpanOp(segmentSpan!)).toBe('rpc'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.faas.cloudflare.durable_object', + }); + doSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /rpc/hello', - }), - ); - workerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - workerSpanId = transactionEvent.contexts?.trace?.span_id as string; + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + // `/rpc/hello` is a raw URL, so the streamed segment name keeps the method only. + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/rpc/hello' }); + workerSpan = segmentSpan; }) .unordered() .start(signal); @@ -57,53 +36,29 @@ it('propagates trace from worker to durable object via RPC method call', async ( await runner.completed(); - expect(workerTraceId).toBeDefined(); - expect(doTraceId).toBeDefined(); - expect(workerTraceId).toBe(doTraceId); - - expect(workerSpanId).toBeDefined(); - expect(doParentSpanId).toBeDefined(); - expect(doParentSpanId).toBe(workerSpanId); + expect(workerSpan?.trace_id).toBeDefined(); + expect(doSpan?.trace_id).toBe(workerSpan?.trace_id); + expect(doSpan?.parent_span_id).toBe(workerSpan?.span_id); }); it('propagates trace for RPC method with multiple arguments', async ({ signal }) => { - let workerTraceId: string | undefined; - let workerSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let workerSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'rpc', - }), - }), - transaction: 'multiply', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + expect(segmentSpan?.name).toBe('multiply'); + expect(getSpanOp(segmentSpan!)).toBe('rpc'); + doSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - }), - }), - transaction: 'GET /rpc/multiply', - }), - ); - workerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - workerSpanId = transactionEvent.contexts?.trace?.span_id as string; + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/rpc/multiply' }); + workerSpan = segmentSpan; }) .unordered() .start(signal); @@ -113,11 +68,7 @@ it('propagates trace for RPC method with multiple arguments', async ({ signal }) await runner.completed(); - expect(workerTraceId).toBeDefined(); - expect(doTraceId).toBeDefined(); - expect(workerTraceId).toBe(doTraceId); - - expect(workerSpanId).toBeDefined(); - expect(doParentSpanId).toBeDefined(); - expect(doParentSpanId).toBe(workerSpanId); + expect(workerSpan?.trace_id).toBeDefined(); + expect(doSpan?.trace_id).toBe(workerSpan?.trace_id); + expect(doSpan?.parent_span_id).toBe(workerSpan?.span_id); }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do/index.ts index 42b5ec00a180..a3cebae5e9bd 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do/index.ts @@ -16,7 +16,6 @@ class MyDurableObjectBase extends DurableObject { export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), MyDurableObjectBase, @@ -25,7 +24,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, rpcTracePropagationBindings: ['MY_DURABLE_OBJECT'], }), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do/test.ts index 4b1b24e4cf48..22208e487578 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do/test.ts @@ -1,133 +1,71 @@ import { expect, it } from 'vitest'; -import type { Event } from '@sentry/core'; +import type { SerializedStreamedSpan } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; it('propagates trace from worker to durable object', async ({ signal }) => { - let workerTraceId: string | undefined; - let workerSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let workerSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /hello', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + // `/hello` is a raw URL, so the streamed segment name keeps the method only. + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' }); + doSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /', - }), - ); - workerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - workerSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.name).toBe('GET /'); + workerSpan = segmentSpan; }) .unordered() .start(signal); await runner.makeRequest('get', '/'); await runner.completed(); - expect(workerTraceId).toBeDefined(); - expect(doTraceId).toBeDefined(); - expect(workerTraceId).toBe(doTraceId); - - expect(workerSpanId).toBeDefined(); - expect(doParentSpanId).toBeDefined(); - expect(doParentSpanId).toBe(workerSpanId); + expect(workerSpan?.trace_id).toBeDefined(); + expect(doSpan?.trace_id).toBe(workerSpan?.trace_id); + expect(doSpan?.parent_span_id).toBe(workerSpan?.span_id); }); it('propagates trace from queue handler to durable object', async ({ signal }) => { - let queueTraceId: string | undefined; - let queueSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let queueSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /hello', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' }); + doSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'queue.process', - data: expect.objectContaining({ - 'sentry.origin': 'auto.faas.cloudflare.queue', - }), - origin: 'auto.faas.cloudflare.queue', - }), - }), - transaction: 'process my-queue', - }), - ); - queueTraceId = transactionEvent.contexts?.trace?.trace_id as string; - queueSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(segmentSpan?.name).toBe('process my-queue'); + expect(getSpanOp(segmentSpan!)).toBe('queue.process'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.faas.cloudflare.queue', + }); + queueSpan = segmentSpan; }) - // Also expect the fetch transaction from the /queue/send request + // Also expect the fetch span from the /queue/send request .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /queue/send', - }), - ); + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/queue/send' }); }) .unordered() .start(signal); @@ -135,72 +73,41 @@ it('propagates trace from queue handler to durable object', async ({ signal }) = await runner.makeRequest('get', '/queue/send'); await runner.completed(); - expect(queueTraceId).toBeDefined(); - expect(doTraceId).toBeDefined(); - expect(queueTraceId).toBe(doTraceId); - - expect(queueSpanId).toBeDefined(); - expect(doParentSpanId).toBeDefined(); - expect(doParentSpanId).toBe(queueSpanId); + expect(queueSpan?.trace_id).toBeDefined(); + expect(doSpan?.trace_id).toBe(queueSpan?.trace_id); + expect(doSpan?.parent_span_id).toBe(queueSpan?.span_id); }); it('propagates trace from scheduled handler to durable object', async ({ signal }) => { - let scheduledTraceId: string | undefined; - let scheduledSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let scheduledSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .withWranglerArgs('--test-scheduled') .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /hello', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' }); + doSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'function', - data: expect.objectContaining({ - 'sentry.origin': 'auto.faas.cloudflare.scheduled', - }), - origin: 'auto.faas.cloudflare.scheduled', - }), - }), - }), - ); - scheduledTraceId = transactionEvent.contexts?.trace?.trace_id as string; - scheduledSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('function'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.faas.cloudflare.scheduled', + }); + scheduledSpan = segmentSpan; }) .unordered() .start(signal); await runner.makeRequest('get', '/__scheduled?cron=*+*+*+*+*'); await runner.completed(); - expect(scheduledTraceId).toBeDefined(); - expect(doTraceId).toBeDefined(); - expect(scheduledTraceId).toBe(doTraceId); - - expect(scheduledSpanId).toBeDefined(); - expect(doParentSpanId).toBeDefined(); - expect(doParentSpanId).toBe(scheduledSpanId); + expect(scheduledSpan?.trace_id).toBeDefined(); + expect(doSpan?.trace_id).toBe(scheduledSpan?.trace_id); + expect(doSpan?.parent_span_id).toBe(scheduledSpan?.span_id); }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/index-sub-worker.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/index-sub-worker.ts index 15162528648c..95de55198929 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/index-sub-worker.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/index-sub-worker.ts @@ -13,7 +13,6 @@ const myWorker = { export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), myWorker, diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/index.ts index 93f2d260e213..539984eb5f48 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/index.ts @@ -8,7 +8,6 @@ interface Env { export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, rpcTracePropagationBindings: ['ANOTHER_WORKER'], }), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/test.ts index 878b307ca5f4..6fd5fa551512 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/test.ts @@ -1,62 +1,30 @@ import { expect, it } from 'vitest'; -import type { Event } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp } from '../../../../spanUtils'; it('propagates trace from worker to worker via service binding', async ({ signal }) => { - let workerTraceId: string | undefined; - let workerSpanId: string | undefined; - let subWorkerTraceId: string | undefined; - let subWorkerParentSpanId: string | undefined; + const runner = createRunner(__dirname).start(signal); + + // The worker and the sub-worker stream from separate isolates, so the two segment spans of the + // trace arrive in separate envelopes. + const spansPromise = runner.collectStreamedSpans( + spansOfTrace => spansOfTrace.filter(span => span.is_segment && getSpanOp(span) === 'http.server').length === 2, + ); - const runner = createRunner(__dirname) - .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /', - }), - ); - workerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - workerSpanId = transactionEvent.contexts?.trace?.span_id as string; - }) - .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /hello', - }), - ); - subWorkerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - subWorkerParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; - }) - .unordered() - .start(signal); await runner.makeRequest('get', '/'); - await runner.completed(); - expect(workerTraceId).toBeDefined(); - expect(subWorkerTraceId).toBeDefined(); - expect(workerTraceId).toBe(subWorkerTraceId); + const segmentSpans = (await spansPromise).filter(span => span.is_segment); + const worker = segmentSpans.find(span => !span.parent_span_id); + const subWorker = segmentSpans.find(span => span.parent_span_id); + + expect(worker?.name).toBe('GET /'); + expect(worker?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + + // `/hello` is a raw URL, so the streamed segment name keeps the method only. + expect(subWorker?.name).toBe('GET'); + expect(subWorker?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' }); + expect(subWorker?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); - expect(workerSpanId).toBeDefined(); - expect(subWorkerParentSpanId).toBeDefined(); - expect(subWorkerParentSpanId).toBe(workerSpanId); + expect(subWorker?.trace_id).toBe(worker?.trace_id); + expect(subWorker?.parent_span_id).toBe(worker?.span_id); }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/index-sub-worker.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/index-sub-worker.ts index 6979952ed448..b5dce37cab86 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/index-sub-worker.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/index-sub-worker.ts @@ -15,7 +15,6 @@ class MyDurableObjectBase extends DurableObject { export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), MyDurableObjectBase, @@ -24,7 +23,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, rpcTracePropagationBindings: ['MY_DURABLE_OBJECT'], }), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/index.ts index b5d5984c6a12..c38bce0872fe 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/index.ts @@ -8,7 +8,6 @@ interface Env { export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, rpcTracePropagationBindings: ['SUB_WORKER'], }), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/test.ts index a6f5818b8489..5701294f9826 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-worker-do-rpc/test.ts @@ -1,80 +1,44 @@ import { expect, it } from 'vitest'; -import type { Event } from '@sentry/core'; +import type { SerializedStreamedSpan } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; it('propagates trace from worker to worker to durable object (3 levels deep)', async ({ signal }) => { - let mainWorkerTraceId: string | undefined; - let mainWorkerSpanId: string | undefined; - let subWorkerTraceId: string | undefined; - let subWorkerSpanId: string | undefined; - let subWorkerParentSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let mainWorkerSpan: SerializedStreamedSpan | undefined; + let subWorkerSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - // Main worker HTTP server transaction - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /chain', - }), - ); - mainWorkerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - mainWorkerSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + // Main worker HTTP server segment span + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + // `/chain` is a raw URL, so the streamed segment name keeps the method only. + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/chain' }); + mainWorkerSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - // Sub-worker HTTP server transaction (from service binding fetch) - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /call-do', - }), - ); - subWorkerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - subWorkerSpanId = transactionEvent.contexts?.trace?.span_id as string; - subWorkerParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + // Sub-worker HTTP server segment span (from service binding fetch) + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/call-do' }); + subWorkerSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - // Durable Object RPC transaction - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'rpc', - data: expect.objectContaining({ - 'sentry.origin': 'auto.faas.cloudflare.durable_object', - }), - origin: 'auto.faas.cloudflare.durable_object', - }), - }), - transaction: 'computeAnswer', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + // Durable Object RPC segment span + expect(segmentSpan?.name).toBe('computeAnswer'); + expect(getSpanOp(segmentSpan!)).toBe('rpc'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.faas.cloudflare.durable_object', + }); + doSpan = segmentSpan; }) .unordered() .start(signal); @@ -84,20 +48,13 @@ it('propagates trace from worker to worker to durable object (3 levels deep)', a await runner.completed(); - // All three transactions should share the same trace_id - expect(mainWorkerTraceId).toBeDefined(); - expect(subWorkerTraceId).toBeDefined(); - expect(doTraceId).toBeDefined(); - expect(mainWorkerTraceId).toBe(subWorkerTraceId); - expect(subWorkerTraceId).toBe(doTraceId); + // All three segment spans should share the same trace_id + expect(mainWorkerSpan?.trace_id).toBeDefined(); + expect(subWorkerSpan?.trace_id).toBe(mainWorkerSpan?.trace_id); + expect(doSpan?.trace_id).toBe(subWorkerSpan?.trace_id); // Verify the parent-child relationships form a chain: // Main Worker -> Sub Worker -> DO - expect(mainWorkerSpanId).toBeDefined(); - expect(subWorkerParentSpanId).toBeDefined(); - expect(subWorkerParentSpanId).toBe(mainWorkerSpanId); - - expect(subWorkerSpanId).toBeDefined(); - expect(doParentSpanId).toBeDefined(); - expect(doParentSpanId).toBe(subWorkerSpanId); + expect(subWorkerSpan?.parent_span_id).toBe(mainWorkerSpan?.span_id); + expect(doSpan?.parent_span_id).toBe(subWorkerSpan?.span_id); }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/index-sub-worker.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/index-sub-worker.ts index 46508f2791be..bdc3f479716e 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/index-sub-worker.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/index-sub-worker.ts @@ -44,7 +44,6 @@ class MySubWorkerEntrypointBase extends BaseEntrypoint { export const BindingEntrypoint = Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, initialScope: { tags: { initial_scope: 'applied' } }, beforeSend(event) { @@ -61,7 +60,6 @@ export const BindingEntrypoint = Sentry.withSentry( export const NoPropagationEntrypoint = Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, transportOptions: { fetch: fetch.bind(globalThis) }, }), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/index.ts index a34ea3edb338..58ddec5080f3 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/index.ts @@ -23,14 +23,13 @@ class LoopbackEntrypointBase extends WorkerEntrypoint { } export const LoopbackEntrypoint = Sentry.withSentry( - (env: Env) => ({ dsn: env.SENTRY_DSN, traceLifecycle: 'static', tracesSampleRate: 0 }), + (env: Env) => ({ dsn: env.SENTRY_DSN, tracesSampleRate: 0 }), LoopbackEntrypointBase, ); export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, // Targeted by binding name. Two bindings are deliberately left out: // `SUB_WORKER_UNINSTRUMENTED`, whose receiver has no Sentry to strip a trailing metadata diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/test.ts index 623ea9779fcc..4ff5eeed7fbb 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-workerentrypoint-rpc/test.ts @@ -1,57 +1,35 @@ import { expect, it } from 'vitest'; -import type { Event } from '@sentry/core'; +import type { Event, SerializedStreamedSpan } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; + +// Every route here is a raw URL, so the streamed segment name keeps the method only and the route +// is identified through `url.path`. it('propagates trace from Worker (ExportedHandler) to WorkerEntrypoint via service binding fetch', async ({ signal, }) => { - let workerTraceId: string | undefined; - let workerSpanId: string | undefined; - let entrypointTraceId: string | undefined; - let entrypointParentSpanId: string | undefined; + let workerSpan: SerializedStreamedSpan | undefined; + let entrypointSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - // Main worker HTTP server transaction - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /call-entrypoint', - }), - ); - workerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - workerSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + // Main worker HTTP server segment span + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/call-entrypoint' }); + workerSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - // WorkerEntrypoint HTTP server transaction (from service binding fetch) - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /answer', - }), - ); - entrypointTraceId = transactionEvent.contexts?.trace?.trace_id as string; - entrypointParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + // WorkerEntrypoint HTTP server segment span (from service binding fetch) + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/answer' }); + entrypointSpan = segmentSpan; }) .unordered() .start(signal); @@ -61,55 +39,32 @@ it('propagates trace from Worker (ExportedHandler) to WorkerEntrypoint via servi await runner.completed(); - // Both transactions should share the same trace_id - expect(workerTraceId).toBeDefined(); - expect(entrypointTraceId).toBeDefined(); - expect(workerTraceId).toBe(entrypointTraceId); + // Both segment spans should share the same trace_id + expect(workerSpan?.trace_id).toBeDefined(); + expect(entrypointSpan?.trace_id).toBe(workerSpan?.trace_id); // Verify the parent-child relationship: Worker -> WorkerEntrypoint - expect(workerSpanId).toBeDefined(); - expect(entrypointParentSpanId).toBeDefined(); - expect(entrypointParentSpanId).toBe(workerSpanId); + expect(entrypointSpan?.parent_span_id).toBe(workerSpan?.span_id); }); it('propagates trace for request with query params from Worker to WorkerEntrypoint', async ({ signal }) => { - let workerTraceId: string | undefined; - let workerSpanId: string | undefined; - let entrypointTraceId: string | undefined; - let entrypointParentSpanId: string | undefined; + let workerSpan: SerializedStreamedSpan | undefined; + let entrypointSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - }), - }), - transaction: 'GET /call-entrypoint-greet', - }), - ); - workerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - workerSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/call-entrypoint-greet' }); + workerSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - }), - }), - transaction: 'GET /greet', - }), - ); - entrypointTraceId = transactionEvent.contexts?.trace?.trace_id as string; - entrypointParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/greet' }); + entrypointSpan = segmentSpan; }) .unordered() .start(signal); @@ -119,81 +74,44 @@ it('propagates trace for request with query params from Worker to WorkerEntrypoi await runner.completed(); - expect(workerTraceId).toBeDefined(); - expect(entrypointTraceId).toBeDefined(); - expect(workerTraceId).toBe(entrypointTraceId); - - expect(workerSpanId).toBeDefined(); - expect(entrypointParentSpanId).toBeDefined(); - expect(entrypointParentSpanId).toBe(workerSpanId); + expect(workerSpan?.trace_id).toBeDefined(); + expect(entrypointSpan?.trace_id).toBe(workerSpan?.trace_id); + expect(entrypointSpan?.parent_span_id).toBe(workerSpan?.span_id); }); it('instruments inherited custom WorkerEntrypoint RPC methods and strips metadata', async ({ signal }) => { - let callerTraceId: string | undefined; - let callerSpanId: string | undefined; - let receiverGetTraceId: string | undefined; - let receiverGetParentSpanId: string | undefined; + let callerSpan: SerializedStreamedSpan | undefined; + let receiverGetSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - origin: 'auto.http.cloudflare', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - }), - }), - transaction: 'GET /call-entrypoint-rpc', - }), - ); - callerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - callerSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/call-entrypoint-rpc' }); + callerSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'rpc', - origin: 'auto.faas.cloudflare.worker_entrypoint', - data: expect.objectContaining({ - 'sentry.op': 'rpc', - 'sentry.origin': 'auto.faas.cloudflare.worker_entrypoint', - }), - }), - }), - transaction: 'get', - }), - ); - receiverGetTraceId = transactionEvent.contexts?.trace?.trace_id as string; - receiverGetParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(segmentSpan?.name).toBe('get'); + expect(getSpanOp(segmentSpan!)).toBe('rpc'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.faas.cloudflare.worker_entrypoint', + }); + receiverGetSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'rpc', - origin: 'auto.faas.cloudflare.worker_entrypoint', - data: expect.objectContaining({ - 'sentry.op': 'rpc', - 'sentry.origin': 'auto.faas.cloudflare.worker_entrypoint', - }), - }), - }), - transaction: 'inherited', - }), - ); + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(segmentSpan?.name).toBe('inherited'); + expect(getSpanOp(segmentSpan!)).toBe('rpc'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.faas.cloudflare.worker_entrypoint', + }); }) .unordered() .start(signal); @@ -206,13 +124,9 @@ it('instruments inherited custom WorkerEntrypoint RPC methods and strips metadat await runner.completed(); - expect(receiverGetTraceId).toBeDefined(); - expect(callerTraceId).toBeDefined(); - expect(receiverGetTraceId).toBe(callerTraceId); - - expect(receiverGetParentSpanId).toBeDefined(); - expect(callerSpanId).toBeDefined(); - expect(receiverGetParentSpanId).toBe(callerSpanId); + expect(receiverGetSpan?.trace_id).toBeDefined(); + expect(receiverGetSpan?.trace_id).toBe(callerSpan?.trace_id); + expect(receiverGetSpan?.parent_span_id).toBe(callerSpan?.span_id); }); it('captures errors thrown by custom WorkerEntrypoint RPC methods', async ({ signal }) => { @@ -228,12 +142,15 @@ it('captures errors thrown by custom WorkerEntrypoint RPC methods', async ({ sig expect(event.tags?.before_send).toBe('applied'); }) .expect(envelope => { - const event = envelope[1]?.[0]?.[1] as Event; - expect(event.transaction).toBe('throwError'); + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + expect(segmentSpan?.name).toBe('throwError'); }) .expect(envelope => { - const event = envelope[1]?.[0]?.[1] as Event; - expect(event.transaction).toBe('GET /call-entrypoint-rpc-error'); + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + expect(segmentSpan?.attributes['url.path']).toEqual({ + type: 'string', + value: '/call-entrypoint-rpc-error', + }); }) .unordered() .start(signal); @@ -250,8 +167,8 @@ it('captures errors thrown by custom WorkerEntrypoint RPC methods', async ({ sig it('does not change RPC method arguments for a binding left off the allowlist', async ({ signal }) => { const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - expect(transactionEvent.transaction).toBe('GET /call-uninstrumented-rpc'); + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/call-uninstrumented-rpc' }); }) .start(signal); @@ -266,22 +183,14 @@ it('does not inject RPC trace metadata into receiver calls when rpcTracePropagat }) => { const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /call-entrypoint-rpc-no-propagation', - }), - ); + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ + type: 'string', + value: '/call-entrypoint-rpc-no-propagation', + }); }) .start(signal); @@ -305,8 +214,8 @@ it('captures errors from loopback WorkerEntrypoint RPC without trace propagation }); }) .expect(envelope => { - const event = envelope[1]?.[0]?.[1] as Event; - expect(event.transaction).toBe('GET /call-loopback-rpc-error'); + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/call-loopback-rpc-error' }); }) .unordered() .start(signal); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc-disabled/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc-disabled/index.ts index 30190e3c0603..b97179565a3a 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc-disabled/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc-disabled/index.ts @@ -15,7 +15,6 @@ class MyDurableObjectBase extends DurableObject { export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), MyDurableObjectBase, @@ -44,7 +43,6 @@ class MyWorkerEntrypointBase extends WorkerEntrypoint { export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), MyWorkerEntrypointBase, diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc-disabled/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc-disabled/test.ts index f0bef1e2e402..104d73f79db5 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc-disabled/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc-disabled/test.ts @@ -1,36 +1,24 @@ import { expect, it } from 'vitest'; -import type { Event } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; it('does not trace an RPC method call when rpcTracePropagationBindings is empty (WorkerEntrypoint)', async ({ signal, }) => { const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ op: 'http.server' }), - }), - transaction: 'GET /rpc/hello', - }), - ); + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/rpc/hello' }); }) - // Ordered: a `sayHello` transaction from the receiver would arrive here and fail this - // expectation. Without the trailing Sentry argument the receiver never traces the call. + // Ordered: a `sayHello` span from the receiver would arrive here and fail this expectation. + // Without the trailing Sentry argument the receiver never traces the call. .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ op: 'http.server' }), - }), - transaction: 'GET /sentinel', - }), - ); + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/sentinel' }); }) .start(signal); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc/index.ts index 5c74e8824ee3..b9fd238d684e 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc/index.ts @@ -19,7 +19,6 @@ class MyDurableObjectBase extends DurableObject { export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), MyDurableObjectBase, @@ -48,7 +47,6 @@ class MyWorkerEntrypointBase extends WorkerEntrypoint { export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, rpcTracePropagationBindings: ['MY_DURABLE_OBJECT'], }), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc/test.ts index 2dd17269ae23..42b6ebc70338 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-do-rpc/test.ts @@ -1,53 +1,32 @@ import { expect, it } from 'vitest'; -import type { Event } from '@sentry/core'; +import type { SerializedStreamedSpan } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; it('propagates trace from WorkerEntrypoint to durable object via this.env RPC call', async ({ signal }) => { - let workerTraceId: string | undefined; - let workerSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let workerSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'rpc', - data: expect.objectContaining({ - 'sentry.origin': 'auto.faas.cloudflare.durable_object', - }), - origin: 'auto.faas.cloudflare.durable_object', - }), - }), - transaction: 'sayHello', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(segmentSpan?.name).toBe('sayHello'); + expect(getSpanOp(segmentSpan!)).toBe('rpc'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.faas.cloudflare.durable_object', + }); + doSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /rpc/hello', - }), - ); - workerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - workerSpanId = transactionEvent.contexts?.trace?.span_id as string; + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + // `/rpc/hello` is a raw URL, so the streamed segment name keeps the method only. + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/rpc/hello' }); + workerSpan = segmentSpan; }) .unordered() .start(signal); @@ -57,53 +36,29 @@ it('propagates trace from WorkerEntrypoint to durable object via this.env RPC ca await runner.completed(); - expect(workerTraceId).toBeDefined(); - expect(doTraceId).toBeDefined(); - expect(workerTraceId).toBe(doTraceId); - - expect(workerSpanId).toBeDefined(); - expect(doParentSpanId).toBeDefined(); - expect(doParentSpanId).toBe(workerSpanId); + expect(workerSpan?.trace_id).toBeDefined(); + expect(doSpan?.trace_id).toBe(workerSpan?.trace_id); + expect(doSpan?.parent_span_id).toBe(workerSpan?.span_id); }); it('propagates trace for RPC method with multiple arguments via this.env', async ({ signal }) => { - let workerTraceId: string | undefined; - let workerSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let workerSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'rpc', - }), - }), - transaction: 'multiply', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + expect(segmentSpan?.name).toBe('multiply'); + expect(getSpanOp(segmentSpan!)).toBe('rpc'); + doSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - }), - }), - transaction: 'GET /rpc/multiply', - }), - ); - workerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - workerSpanId = transactionEvent.contexts?.trace?.span_id as string; + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/rpc/multiply' }); + workerSpan = segmentSpan; }) .unordered() .start(signal); @@ -113,11 +68,7 @@ it('propagates trace for RPC method with multiple arguments via this.env', async await runner.completed(); - expect(workerTraceId).toBeDefined(); - expect(doTraceId).toBeDefined(); - expect(workerTraceId).toBe(doTraceId); - - expect(workerSpanId).toBeDefined(); - expect(doParentSpanId).toBeDefined(); - expect(doParentSpanId).toBe(workerSpanId); + expect(workerSpan?.trace_id).toBeDefined(); + expect(doSpan?.trace_id).toBe(workerSpan?.trace_id); + expect(doSpan?.parent_span_id).toBe(workerSpan?.span_id); }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/index-sub-worker.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/index-sub-worker.ts index f3576ebf5210..f9cba22b1bb6 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/index-sub-worker.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/index-sub-worker.ts @@ -15,7 +15,6 @@ class MyDurableObjectBase extends DurableObject { export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), MyDurableObjectBase, @@ -39,7 +38,6 @@ class MySubWorkerEntrypointBase extends WorkerEntrypoint { export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, rpcTracePropagationBindings: ['MY_DURABLE_OBJECT'], }), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/index.ts index 654d37c411fe..25dcf4c4770b 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/index.ts @@ -23,7 +23,6 @@ class MyWorkerEntrypointBase extends WorkerEntrypoint { export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, rpcTracePropagationBindings: ['SUB_WORKER'], }), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/test.ts index 474624fa2145..506f7529b677 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workerentrypoint-workerentrypoint-do-rpc/test.ts @@ -1,82 +1,46 @@ import { expect, it } from 'vitest'; -import type { Event } from '@sentry/core'; +import type { SerializedStreamedSpan } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; it('propagates trace from WorkerEntrypoint to WorkerEntrypoint to durable object (3 levels deep)', async ({ signal, }) => { - let mainWorkerTraceId: string | undefined; - let mainWorkerSpanId: string | undefined; - let subWorkerTraceId: string | undefined; - let subWorkerSpanId: string | undefined; - let subWorkerParentSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let mainWorkerSpan: SerializedStreamedSpan | undefined; + let subWorkerSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - // Main worker HTTP server transaction - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /chain', - }), - ); - mainWorkerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - mainWorkerSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + // Main worker HTTP server segment span + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + // `/chain` is a raw URL, so the streamed segment name keeps the method only. + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/chain' }); + mainWorkerSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); - // Sub-worker HTTP server transaction (from service binding fetch) - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /call-do', - }), - ); - subWorkerTraceId = transactionEvent.contexts?.trace?.trace_id as string; - subWorkerSpanId = transactionEvent.contexts?.trace?.span_id as string; - subWorkerParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + // Sub-worker HTTP server segment span (from service binding fetch) + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/call-do' }); + subWorkerSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - - // Durable Object RPC transaction - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'rpc', - data: expect.objectContaining({ - 'sentry.origin': 'auto.faas.cloudflare.durable_object', - }), - origin: 'auto.faas.cloudflare.durable_object', - }), - }), - transaction: 'computeAnswer', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + // Durable Object RPC segment span + expect(segmentSpan?.name).toBe('computeAnswer'); + expect(getSpanOp(segmentSpan!)).toBe('rpc'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.faas.cloudflare.durable_object', + }); + doSpan = segmentSpan; }) .unordered() .start(signal); @@ -86,20 +50,13 @@ it('propagates trace from WorkerEntrypoint to WorkerEntrypoint to durable object await runner.completed(); - // All three transactions should share the same trace_id - expect(mainWorkerTraceId).toBeDefined(); - expect(subWorkerTraceId).toBeDefined(); - expect(doTraceId).toBeDefined(); - expect(mainWorkerTraceId).toBe(subWorkerTraceId); - expect(subWorkerTraceId).toBe(doTraceId); + // All three segment spans should share the same trace_id + expect(mainWorkerSpan?.trace_id).toBeDefined(); + expect(subWorkerSpan?.trace_id).toBe(mainWorkerSpan?.trace_id); + expect(doSpan?.trace_id).toBe(subWorkerSpan?.trace_id); // Verify the parent-child relationships form a chain: // Main WorkerEntrypoint -> Sub WorkerEntrypoint -> DO - expect(mainWorkerSpanId).toBeDefined(); - expect(subWorkerParentSpanId).toBeDefined(); - expect(subWorkerParentSpanId).toBe(mainWorkerSpanId); - - expect(subWorkerSpanId).toBeDefined(); - expect(doParentSpanId).toBeDefined(); - expect(doParentSpanId).toBe(subWorkerSpanId); + expect(subWorkerSpan?.parent_span_id).toBe(mainWorkerSpan?.span_id); + expect(doSpan?.parent_span_id).toBe(subWorkerSpan?.span_id); }); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workflow-do/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workflow-do/index.ts index 8badbd2fd65e..91e7450307ca 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workflow-do/index.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workflow-do/index.ts @@ -17,7 +17,6 @@ class MyDurableObjectBase extends DurableObject { export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, }), MyDurableObjectBase, @@ -37,7 +36,6 @@ class MyWorkflowBase extends WorkflowEntrypoint { export const MyWorkflow = Sentry.instrumentWorkflowWithSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, // The workflow is itself a caller: `run` reaches the Durable Object through `this.env`. rpcTracePropagationBindings: ['MY_DURABLE_OBJECT'], @@ -48,7 +46,6 @@ export const MyWorkflow = Sentry.instrumentWorkflowWithSentry( export default Sentry.withSentry( (env: Env) => ({ dsn: env.SENTRY_DSN, - traceLifecycle: 'static', tracesSampleRate: 1.0, rpcTracePropagationBindings: ['MY_WORKFLOW'], }), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workflow-do/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workflow-do/test.ts index 1e33349b5bb0..525b1d404cb7 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workflow-do/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workflow-do/test.ts @@ -1,63 +1,40 @@ import { expect, it } from 'vitest'; -import type { Event } from '@sentry/core'; +import type { SerializedStreamedSpan } from '@sentry/core'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; it('traces a workflow that calls a durable object with the same trace id', async ({ signal }) => { - let workflowTraceId: string | undefined; - let workflowSpanId: string | undefined; - let doTraceId: string | undefined; - let doParentSpanId: string | undefined; + let workflowSpan: SerializedStreamedSpan | undefined; + let doSpan: SerializedStreamedSpan | undefined; const runner = createRunner(__dirname) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'function', - data: expect.objectContaining({ - 'sentry.op': 'function', - 'sentry.origin': 'auto.faas.cloudflare.workflow', - }), - origin: 'auto.faas.cloudflare.workflow', - }), - }), - transaction: 'workflow-env-test', - }), - ); - workflowTraceId = transactionEvent.contexts?.trace?.trace_id as string; - workflowSpanId = transactionEvent.contexts?.trace?.span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + expect(segmentSpan?.name).toBe('workflow-env-test'); + expect(getSpanOp(segmentSpan!)).toBe('function'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.faas.cloudflare.workflow', + }); + workflowSpan = segmentSpan; }) .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1] as Event; - expect(transactionEvent).toEqual( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - data: expect.objectContaining({ - 'sentry.origin': 'auto.http.cloudflare', - }), - origin: 'auto.http.cloudflare', - }), - }), - transaction: 'GET /workflow-test', - }), - ); - doTraceId = transactionEvent.contexts?.trace?.trace_id as string; - doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string; + const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment); + + // `/workflow-test` is a raw URL, so the streamed segment name keeps the method only. + expect(segmentSpan?.name).toBe('GET'); + expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/workflow-test' }); + expect(getSpanOp(segmentSpan!)).toBe('http.server'); + expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' }); + doSpan = segmentSpan; }) .unordered() .start(signal); await runner.makeRequest('get', '/workflow/trigger'); await runner.completed(); - expect(workflowTraceId).toBeDefined(); - expect(doTraceId).toBeDefined(); - expect(workflowTraceId).toBe(doTraceId); - - expect(workflowSpanId).toBeDefined(); - expect(doParentSpanId).toBeDefined(); - expect(doParentSpanId).toBe(workflowSpanId); + expect(workflowSpan?.trace_id).toBeDefined(); + expect(doSpan?.trace_id).toBe(workflowSpan?.trace_id); + expect(doSpan?.parent_span_id).toBe(workflowSpan?.span_id); });