Skip to content

Commit b170bdd

Browse files
JPeer264claude
andcommitted
test(cloudflare): Port the tracing propagation suites to span streaming
Removes the `traceLifecycle: 'static'` pin from the twelve `suites/tracing/propagation` suites, and rewrites the assertions from transaction envelopes to span v2. Each hop of a propagated trace runs in its own isolate and streams its own envelope, so a suite reads one segment span per envelope and then checks `trace_id` and `parent_span_id` between them. Most suites keep one envelope expectation per hop with `.unordered()`, which stays close to the shape they had. `worker-service-binding` uses `collectStreamedSpans` instead, because it only needs the two segment spans of one trace. Every route in these suites is a raw URL, so the streamed segment name keeps the method only. `GET /chain` becomes `GET`, and the route is read from the `url.path` attribute. The `sed` that removed the pins took a whole line with it in `worker-workerentrypoint-rpc/index.ts`, where the options callback sat on one line. This restores `LoopbackEntrypoint` with its `tracesSampleRate: 0`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent d88f7b6 commit b170bdd

28 files changed

Lines changed: 435 additions & 993 deletions

File tree

‎dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-fetch-no-bindings/index.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class MyDurableObjectBase extends DurableObject<Env> {
1616
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
1717
(env: Env) => ({
1818
dsn: env.SENTRY_DSN,
19-
traceLifecycle: 'static',
2019
tracesSampleRate: 1.0,
2120
}),
2221
MyDurableObjectBase,
@@ -25,7 +24,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
2524
export default Sentry.withSentry(
2625
(env: Env) => ({
2726
dsn: env.SENTRY_DSN,
28-
traceLifecycle: 'static',
2927
tracesSampleRate: 1.0,
3028
}),
3129
{
Lines changed: 65 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,206 +1,119 @@
11
import { expect, it } from 'vitest';
2-
import type { Event } from '@sentry/core';
2+
import type { SerializedStreamedSpan } from '@sentry/core';
33
import { createRunner } from '../../../../runner';
4+
import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils';
45

56
it('propagates trace from worker to durable object over stub.fetch() when rpcTracePropagationBindings is empty', async ({
67
signal,
78
}) => {
8-
let workerTraceId: string | undefined;
9-
let workerSpanId: string | undefined;
10-
let doTraceId: string | undefined;
11-
let doParentSpanId: string | undefined;
9+
let workerSpan: SerializedStreamedSpan | undefined;
10+
let doSpan: SerializedStreamedSpan | undefined;
1211

1312
const runner = createRunner(__dirname)
1413
.expect(envelope => {
15-
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
16-
17-
expect(transactionEvent).toEqual(
18-
expect.objectContaining({
19-
contexts: expect.objectContaining({
20-
trace: expect.objectContaining({
21-
op: 'http.server',
22-
data: expect.objectContaining({
23-
'sentry.origin': 'auto.http.cloudflare',
24-
}),
25-
origin: 'auto.http.cloudflare',
26-
}),
27-
}),
28-
transaction: 'GET /hello',
29-
}),
30-
);
31-
doTraceId = transactionEvent.contexts?.trace?.trace_id as string;
32-
doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string;
14+
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);
15+
16+
expect(getSpanOp(segmentSpan!)).toBe('http.server');
17+
expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' });
18+
// `/hello` is a raw URL, so the streamed segment name keeps the method only.
19+
expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' });
20+
doSpan = segmentSpan;
3321
})
3422
.expect(envelope => {
35-
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
36-
37-
expect(transactionEvent).toEqual(
38-
expect.objectContaining({
39-
contexts: expect.objectContaining({
40-
trace: expect.objectContaining({
41-
op: 'http.server',
42-
data: expect.objectContaining({
43-
'sentry.origin': 'auto.http.cloudflare',
44-
}),
45-
origin: 'auto.http.cloudflare',
46-
}),
47-
}),
48-
transaction: 'GET /',
49-
}),
50-
);
51-
workerTraceId = transactionEvent.contexts?.trace?.trace_id as string;
52-
workerSpanId = transactionEvent.contexts?.trace?.span_id as string;
23+
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);
24+
25+
expect(getSpanOp(segmentSpan!)).toBe('http.server');
26+
expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' });
27+
expect(segmentSpan?.name).toBe('GET /');
28+
workerSpan = segmentSpan;
5329
})
5430
.unordered()
5531
.start(signal);
5632
await runner.makeRequest('get', '/');
5733
await runner.completed();
5834

59-
expect(workerTraceId).toBeDefined();
60-
expect(doTraceId).toBe(workerTraceId);
61-
62-
expect(workerSpanId).toBeDefined();
63-
expect(doParentSpanId).toBe(workerSpanId);
35+
expect(workerSpan?.trace_id).toBeDefined();
36+
expect(doSpan?.trace_id).toBe(workerSpan?.trace_id);
37+
expect(doSpan?.parent_span_id).toBe(workerSpan?.span_id);
6438
});
6539

6640
it('propagates trace from queue handler to durable object over stub.fetch() when rpcTracePropagationBindings is empty', async ({
6741
signal,
6842
}) => {
69-
let queueTraceId: string | undefined;
70-
let queueSpanId: string | undefined;
71-
let doTraceId: string | undefined;
72-
let doParentSpanId: string | undefined;
43+
let queueSpan: SerializedStreamedSpan | undefined;
44+
let doSpan: SerializedStreamedSpan | undefined;
7345

7446
const runner = createRunner(__dirname)
7547
.expect(envelope => {
76-
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
77-
78-
expect(transactionEvent).toEqual(
79-
expect.objectContaining({
80-
contexts: expect.objectContaining({
81-
trace: expect.objectContaining({
82-
op: 'http.server',
83-
data: expect.objectContaining({
84-
'sentry.origin': 'auto.http.cloudflare',
85-
}),
86-
origin: 'auto.http.cloudflare',
87-
}),
88-
}),
89-
transaction: 'GET /hello',
90-
}),
91-
);
92-
doTraceId = transactionEvent.contexts?.trace?.trace_id as string;
93-
doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string;
48+
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);
49+
50+
expect(getSpanOp(segmentSpan!)).toBe('http.server');
51+
expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' });
52+
expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' });
53+
doSpan = segmentSpan;
9454
})
9555
.expect(envelope => {
96-
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
97-
98-
expect(transactionEvent).toEqual(
99-
expect.objectContaining({
100-
contexts: expect.objectContaining({
101-
trace: expect.objectContaining({
102-
op: 'queue.process',
103-
data: expect.objectContaining({
104-
'sentry.origin': 'auto.faas.cloudflare.queue',
105-
}),
106-
origin: 'auto.faas.cloudflare.queue',
107-
}),
108-
}),
109-
transaction: 'process my-queue',
110-
}),
111-
);
112-
queueTraceId = transactionEvent.contexts?.trace?.trace_id as string;
113-
queueSpanId = transactionEvent.contexts?.trace?.span_id as string;
56+
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);
57+
58+
expect(segmentSpan?.name).toBe('process my-queue');
59+
expect(getSpanOp(segmentSpan!)).toBe('queue.process');
60+
expect(segmentSpan?.attributes['sentry.origin']).toEqual({
61+
type: 'string',
62+
value: 'auto.faas.cloudflare.queue',
63+
});
64+
queueSpan = segmentSpan;
11465
})
115-
// Also expect the fetch transaction from the /queue/send request
66+
// Also expect the fetch span from the /queue/send request
11667
.expect(envelope => {
117-
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
118-
119-
expect(transactionEvent).toEqual(
120-
expect.objectContaining({
121-
contexts: expect.objectContaining({
122-
trace: expect.objectContaining({
123-
op: 'http.server',
124-
data: expect.objectContaining({
125-
'sentry.origin': 'auto.http.cloudflare',
126-
}),
127-
origin: 'auto.http.cloudflare',
128-
}),
129-
}),
130-
transaction: 'GET /queue/send',
131-
}),
132-
);
68+
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);
69+
70+
expect(getSpanOp(segmentSpan!)).toBe('http.server');
71+
expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' });
72+
expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/queue/send' });
13373
})
13474
.unordered()
13575
.start(signal);
13676
// The fetch handler sends a message to the queue, which triggers the queue consumer
13777
await runner.makeRequest('get', '/queue/send');
13878
await runner.completed();
13979

140-
expect(queueTraceId).toBeDefined();
141-
expect(doTraceId).toBe(queueTraceId);
142-
143-
expect(queueSpanId).toBeDefined();
144-
expect(doParentSpanId).toBe(queueSpanId);
80+
expect(queueSpan?.trace_id).toBeDefined();
81+
expect(doSpan?.trace_id).toBe(queueSpan?.trace_id);
82+
expect(doSpan?.parent_span_id).toBe(queueSpan?.span_id);
14583
});
14684

14785
it('propagates trace from scheduled handler to durable object over stub.fetch() when rpcTracePropagationBindings is empty', async ({
14886
signal,
14987
}) => {
150-
let scheduledTraceId: string | undefined;
151-
let scheduledSpanId: string | undefined;
152-
let doTraceId: string | undefined;
153-
let doParentSpanId: string | undefined;
88+
let scheduledSpan: SerializedStreamedSpan | undefined;
89+
let doSpan: SerializedStreamedSpan | undefined;
15490

15591
const runner = createRunner(__dirname)
15692
.withWranglerArgs('--test-scheduled')
15793
.expect(envelope => {
158-
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
159-
160-
expect(transactionEvent).toEqual(
161-
expect.objectContaining({
162-
contexts: expect.objectContaining({
163-
trace: expect.objectContaining({
164-
op: 'http.server',
165-
data: expect.objectContaining({
166-
'sentry.origin': 'auto.http.cloudflare',
167-
}),
168-
origin: 'auto.http.cloudflare',
169-
}),
170-
}),
171-
transaction: 'GET /hello',
172-
}),
173-
);
174-
doTraceId = transactionEvent.contexts?.trace?.trace_id as string;
175-
doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string;
94+
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);
95+
96+
expect(getSpanOp(segmentSpan!)).toBe('http.server');
97+
expect(segmentSpan?.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.cloudflare' });
98+
expect(segmentSpan?.attributes['url.path']).toEqual({ type: 'string', value: '/hello' });
99+
doSpan = segmentSpan;
176100
})
177101
.expect(envelope => {
178-
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
179-
180-
expect(transactionEvent).toEqual(
181-
expect.objectContaining({
182-
contexts: expect.objectContaining({
183-
trace: expect.objectContaining({
184-
op: 'function',
185-
data: expect.objectContaining({
186-
'sentry.origin': 'auto.faas.cloudflare.scheduled',
187-
}),
188-
origin: 'auto.faas.cloudflare.scheduled',
189-
}),
190-
}),
191-
}),
192-
);
193-
scheduledTraceId = transactionEvent.contexts?.trace?.trace_id as string;
194-
scheduledSpanId = transactionEvent.contexts?.trace?.span_id as string;
102+
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);
103+
104+
expect(getSpanOp(segmentSpan!)).toBe('function');
105+
expect(segmentSpan?.attributes['sentry.origin']).toEqual({
106+
type: 'string',
107+
value: 'auto.faas.cloudflare.scheduled',
108+
});
109+
scheduledSpan = segmentSpan;
195110
})
196111
.unordered()
197112
.start(signal);
198113
await runner.makeRequest('get', '/__scheduled?cron=*+*+*+*+*');
199114
await runner.completed();
200115

201-
expect(scheduledTraceId).toBeDefined();
202-
expect(doTraceId).toBe(scheduledTraceId);
203-
204-
expect(scheduledSpanId).toBeDefined();
205-
expect(doParentSpanId).toBe(scheduledSpanId);
116+
expect(scheduledSpan?.trace_id).toBeDefined();
117+
expect(doSpan?.trace_id).toBe(scheduledSpan?.trace_id);
118+
expect(doSpan?.parent_span_id).toBe(scheduledSpan?.span_id);
206119
});

‎dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-binding-casing/index.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ class MyDurableObjectBase extends DurableObject<Env> {
2525
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
2626
(env: Env) => ({
2727
dsn: env.SENTRY_DSN,
28-
traceLifecycle: 'static',
2928
tracesSampleRate: 1.0,
3029
}),
3130
MyDurableObjectBase,
@@ -34,7 +33,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
3433
export default Sentry.withSentry(
3534
(env: Env) => ({
3635
dsn: env.SENTRY_DSN,
37-
traceLifecycle: 'static',
3836
tracesSampleRate: 1.0,
3937
// Both targets are written in a casing the bindings do not use, and the regex carries the `g`
4038
// flag, which makes `test()` stateful unless the SDK normalizes it away.
Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { expect, it } from 'vitest';
2-
import type { Envelope, Event } from '@sentry/core';
2+
import type { Envelope, SerializedStreamedSpan } from '@sentry/core';
33
import { createRunner } from '../../../../runner';
4+
import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils';
45

56
it('propagates trace over RPC when the binding casing differs from rpcTracePropagationBindings', async ({ signal }) => {
6-
const transactionsByName = new Map<string, Event>();
7+
const segmentSpansByName = new Map<string, SerializedStreamedSpan>();
78

89
const collect = (envelope: Envelope): void => {
9-
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
10-
transactionsByName.set(transactionEvent.transaction as string, transactionEvent);
10+
const segmentSpan = getSpansFromEnvelope(envelope).find(span => span.is_segment);
11+
expect(segmentSpan).toBeDefined();
12+
segmentSpansByName.set(segmentSpan!.name, segmentSpan!);
1113
};
1214

1315
const runner = createRunner(__dirname)
@@ -23,16 +25,18 @@ it('propagates trace over RPC when the binding casing differs from rpcTracePropa
2325

2426
await runner.completed();
2527

26-
const worker = transactionsByName.get('GET /rpc/all');
27-
expect(worker?.contexts?.trace?.op).toBe('http.server');
28+
// `/rpc/all` is a raw URL, so the streamed segment name keeps the method only.
29+
const worker = segmentSpansByName.get('GET');
30+
expect(getSpanOp(worker!)).toBe('http.server');
31+
expect(worker?.attributes['url.path']).toEqual({ type: 'string', value: '/rpc/all' });
2832

2933
// `sayHello` comes from the string target, `alpha` and `beta` from the regex target. `beta` is the
3034
// one a stateful `g` regex would miss, because `alpha` already advanced its `lastIndex`.
3135
for (const methodName of ['sayHello', 'alpha', 'beta']) {
32-
const durableObject = transactionsByName.get(methodName);
36+
const durableObject = segmentSpansByName.get(methodName);
3337

34-
expect(durableObject?.contexts?.trace?.op).toBe('rpc');
35-
expect(durableObject?.contexts?.trace?.trace_id).toBe(worker?.contexts?.trace?.trace_id);
36-
expect(durableObject?.contexts?.trace?.parent_span_id).toBe(worker?.contexts?.trace?.span_id);
38+
expect(getSpanOp(durableObject!)).toBe('rpc');
39+
expect(durableObject?.trace_id).toBe(worker?.trace_id);
40+
expect(durableObject?.parent_span_id).toBe(worker?.span_id);
3741
}
3842
});

‎dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-do-rpc-disabled/index.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ class MyDurableObjectBase extends DurableObject<Env> {
1515
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
1616
(env: Env) => ({
1717
dsn: env.SENTRY_DSN,
18-
traceLifecycle: 'static',
1918
tracesSampleRate: 1.0,
2019
}),
2120
MyDurableObjectBase,
@@ -24,7 +23,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
2423
export default Sentry.withSentry(
2524
(env: Env) => ({
2625
dsn: env.SENTRY_DSN,
27-
traceLifecycle: 'static',
2826
tracesSampleRate: 1.0,
2927
}),
3028
{

0 commit comments

Comments
 (0)