Skip to content

Commit 79e6052

Browse files
committed
feat(cloudflare): Enable RPC trace propagation with enableRpcTracePropagation
1 parent 50438f9 commit 79e6052

22 files changed

Lines changed: 515 additions & 40 deletions

File tree

dev-packages/cloudflare-integration-tests/suites/tracing/instrument-fetcher/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default withSentry(
3131
(env: Env) => ({
3232
dsn: env.SENTRY_DSN,
3333
tracesSampleRate: 1.0,
34+
enableRpcTracePropagation: true,
3435
}),
3536
{
3637
async fetch(request, env) {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as Sentry from '@sentry/cloudflare';
2+
import { DurableObject } from 'cloudflare:workers';
3+
4+
interface Env {
5+
SENTRY_DSN: string;
6+
MY_DURABLE_OBJECT: DurableObjectNamespace;
7+
MY_QUEUE: Queue;
8+
}
9+
10+
class MyDurableObjectBase extends DurableObject<Env> {
11+
async fetch(_request: Request) {
12+
return new Response('DO is fine');
13+
}
14+
}
15+
16+
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
17+
(env: Env) => ({
18+
dsn: env.SENTRY_DSN,
19+
tracesSampleRate: 1.0,
20+
}),
21+
MyDurableObjectBase,
22+
);
23+
24+
export default Sentry.withSentry(
25+
(env: Env) => ({
26+
dsn: env.SENTRY_DSN,
27+
tracesSampleRate: 1.0,
28+
}),
29+
{
30+
async fetch(request, env) {
31+
const url = new URL(request.url);
32+
33+
if (url.pathname === '/queue/send') {
34+
await env.MY_QUEUE.send({ action: 'test' });
35+
return new Response('Queued');
36+
}
37+
38+
const id = env.MY_DURABLE_OBJECT.idFromName('test');
39+
const stub = env.MY_DURABLE_OBJECT.get(id);
40+
const response = await stub.fetch(new Request('http://fake-host/hello'));
41+
const text = await response.text();
42+
return new Response(text);
43+
},
44+
45+
async queue(batch, env, _ctx) {
46+
const id = env.MY_DURABLE_OBJECT.idFromName('test');
47+
const stub = env.MY_DURABLE_OBJECT.get(id);
48+
for (const message of batch.messages) {
49+
await stub.fetch(new Request('http://fake-host/hello'));
50+
message.ack();
51+
}
52+
},
53+
54+
async scheduled(controller, env, _ctx) {
55+
const id = env.MY_DURABLE_OBJECT.idFromName('test');
56+
const stub = env.MY_DURABLE_OBJECT.get(id);
57+
await stub.fetch(new Request('http://fake-host/hello'));
58+
},
59+
} satisfies ExportedHandler<Env>,
60+
);
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import { expect, it } from 'vitest';
2+
import type { Event } from '@sentry/core';
3+
import { createRunner } from '../../../../runner';
4+
5+
it('does not propagate trace from worker to durable object when enableRpcTracePropagation is disabled', async ({ signal }) => {
6+
let workerTraceId: string | undefined;
7+
let workerSpanId: string | undefined;
8+
let doTraceId: string | undefined;
9+
let doParentSpanId: string | undefined;
10+
11+
const runner = createRunner(__dirname)
12+
.expect(envelope => {
13+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
14+
15+
expect(transactionEvent).toEqual(
16+
expect.objectContaining({
17+
contexts: expect.objectContaining({
18+
trace: expect.objectContaining({
19+
op: 'http.server',
20+
data: expect.objectContaining({
21+
'sentry.origin': 'auto.http.cloudflare',
22+
}),
23+
origin: 'auto.http.cloudflare',
24+
}),
25+
}),
26+
transaction: 'GET /hello',
27+
}),
28+
);
29+
doTraceId = transactionEvent.contexts?.trace?.trace_id as string;
30+
doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string;
31+
})
32+
.expect(envelope => {
33+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
34+
35+
expect(transactionEvent).toEqual(
36+
expect.objectContaining({
37+
contexts: expect.objectContaining({
38+
trace: expect.objectContaining({
39+
op: 'http.server',
40+
data: expect.objectContaining({
41+
'sentry.origin': 'auto.http.cloudflare',
42+
}),
43+
origin: 'auto.http.cloudflare',
44+
}),
45+
}),
46+
transaction: 'GET /',
47+
}),
48+
);
49+
workerTraceId = transactionEvent.contexts?.trace?.trace_id as string;
50+
workerSpanId = transactionEvent.contexts?.trace?.span_id as string;
51+
})
52+
.unordered()
53+
.start(signal);
54+
await runner.makeRequest('get', '/');
55+
await runner.completed();
56+
57+
expect(workerTraceId).toBeDefined();
58+
expect(doTraceId).toBeDefined();
59+
expect(workerTraceId).not.toBe(doTraceId);
60+
61+
expect(workerSpanId).toBeDefined();
62+
expect(doParentSpanId).toBeUndefined();
63+
});
64+
65+
it('does not propagate trace from queue handler to durable object when enableRpcTracePropagation is disabled', async ({ signal }) => {
66+
let queueTraceId: string | undefined;
67+
let queueSpanId: string | undefined;
68+
let doTraceId: string | undefined;
69+
let doParentSpanId: string | undefined;
70+
71+
const runner = createRunner(__dirname)
72+
.expect(envelope => {
73+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
74+
75+
expect(transactionEvent).toEqual(
76+
expect.objectContaining({
77+
contexts: expect.objectContaining({
78+
trace: expect.objectContaining({
79+
op: 'http.server',
80+
data: expect.objectContaining({
81+
'sentry.origin': 'auto.http.cloudflare',
82+
}),
83+
origin: 'auto.http.cloudflare',
84+
}),
85+
}),
86+
transaction: 'GET /hello',
87+
}),
88+
);
89+
doTraceId = transactionEvent.contexts?.trace?.trace_id as string;
90+
doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string;
91+
})
92+
.expect(envelope => {
93+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
94+
95+
expect(transactionEvent).toEqual(
96+
expect.objectContaining({
97+
contexts: expect.objectContaining({
98+
trace: expect.objectContaining({
99+
op: 'queue.process',
100+
data: expect.objectContaining({
101+
'sentry.origin': 'auto.faas.cloudflare.queue',
102+
}),
103+
origin: 'auto.faas.cloudflare.queue',
104+
}),
105+
}),
106+
transaction: 'process my-queue',
107+
}),
108+
);
109+
queueTraceId = transactionEvent.contexts?.trace?.trace_id as string;
110+
queueSpanId = transactionEvent.contexts?.trace?.span_id as string;
111+
})
112+
// Also expect the fetch transaction from the /queue/send request
113+
.expect(envelope => {
114+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
115+
116+
expect(transactionEvent).toEqual(
117+
expect.objectContaining({
118+
contexts: expect.objectContaining({
119+
trace: expect.objectContaining({
120+
op: 'http.server',
121+
data: expect.objectContaining({
122+
'sentry.origin': 'auto.http.cloudflare',
123+
}),
124+
origin: 'auto.http.cloudflare',
125+
}),
126+
}),
127+
transaction: 'GET /queue/send',
128+
}),
129+
);
130+
})
131+
.unordered()
132+
.start(signal);
133+
// The fetch handler sends a message to the queue, which triggers the queue consumer
134+
await runner.makeRequest('get', '/queue/send');
135+
await runner.completed();
136+
137+
expect(queueTraceId).toBeDefined();
138+
expect(doTraceId).toBeDefined();
139+
expect(queueTraceId).not.toBe(doTraceId);
140+
141+
expect(queueSpanId).toBeDefined();
142+
expect(doParentSpanId).toBeUndefined();
143+
});
144+
145+
it('does not propagate trace from scheduled handler to durable object when enableRpcTracePropagation is disabled', async ({ signal }) => {
146+
let scheduledTraceId: string | undefined;
147+
let scheduledSpanId: string | undefined;
148+
let doTraceId: string | undefined;
149+
let doParentSpanId: string | undefined;
150+
151+
const runner = createRunner(__dirname)
152+
.withWranglerArgs('--test-scheduled')
153+
.expect(envelope => {
154+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
155+
156+
expect(transactionEvent).toEqual(
157+
expect.objectContaining({
158+
contexts: expect.objectContaining({
159+
trace: expect.objectContaining({
160+
op: 'http.server',
161+
data: expect.objectContaining({
162+
'sentry.origin': 'auto.http.cloudflare',
163+
}),
164+
origin: 'auto.http.cloudflare',
165+
}),
166+
}),
167+
transaction: 'GET /hello',
168+
}),
169+
);
170+
doTraceId = transactionEvent.contexts?.trace?.trace_id as string;
171+
doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string;
172+
})
173+
.expect(envelope => {
174+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
175+
176+
expect(transactionEvent).toEqual(
177+
expect.objectContaining({
178+
contexts: expect.objectContaining({
179+
trace: expect.objectContaining({
180+
op: 'faas.cron',
181+
data: expect.objectContaining({
182+
'sentry.origin': 'auto.faas.cloudflare.scheduled',
183+
}),
184+
origin: 'auto.faas.cloudflare.scheduled',
185+
}),
186+
}),
187+
}),
188+
);
189+
scheduledTraceId = transactionEvent.contexts?.trace?.trace_id as string;
190+
scheduledSpanId = transactionEvent.contexts?.trace?.span_id as string;
191+
})
192+
.unordered()
193+
.start(signal);
194+
await runner.makeRequest('get', '/__scheduled?cron=*+*+*+*+*');
195+
await runner.completed();
196+
197+
expect(scheduledTraceId).toBeDefined();
198+
expect(doTraceId).toBeDefined();
199+
expect(scheduledTraceId).not.toBe(doTraceId);
200+
201+
expect(scheduledSpanId).toBeDefined();
202+
expect(doParentSpanId).toBeUndefined();
203+
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "cloudflare-durable-objects",
3+
"main": "index.ts",
4+
"compatibility_date": "2025-06-17",
5+
"compatibility_flags": ["nodejs_als"],
6+
"migrations": [
7+
{
8+
"new_sqlite_classes": ["MyDurableObject"],
9+
"tag": "v1",
10+
},
11+
],
12+
"durable_objects": {
13+
"bindings": [
14+
{
15+
"class_name": "MyDurableObject",
16+
"name": "MY_DURABLE_OBJECT",
17+
},
18+
],
19+
},
20+
"queues": {
21+
"producers": [
22+
{
23+
"binding": "MY_QUEUE",
24+
"queue": "my-queue",
25+
},
26+
],
27+
"consumers": [
28+
{
29+
"queue": "my-queue",
30+
},
31+
],
32+
},
33+
"triggers": {
34+
"crons": ["* * * * *"],
35+
},
36+
"vars": {
37+
"SENTRY_DSN": "https://932e620ee3921c3b4a61c72558ad88ce@o447951.ingest.us.sentry.io/4509553159831552",
38+
},
39+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
1717
(env: Env) => ({
1818
dsn: env.SENTRY_DSN,
1919
tracesSampleRate: 1.0,
20+
enableRpcTracePropagation: true,
2021
}),
2122
MyDurableObjectBase,
2223
);
@@ -25,6 +26,7 @@ export default Sentry.withSentry(
2526
(env: Env) => ({
2627
dsn: env.SENTRY_DSN,
2728
tracesSampleRate: 1.0,
29+
enableRpcTracePropagation: true,
2830
}),
2931
{
3032
async fetch(request, env) {

dev-packages/cloudflare-integration-tests/suites/tracing/propagation/worker-service-binding/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default Sentry.withSentry(
99
(env: Env) => ({
1010
dsn: env.SENTRY_DSN,
1111
tracesSampleRate: 1.0,
12+
enableRpcTracePropagation: true,
1213
}),
1314
{
1415
async fetch(request, env) {

dev-packages/cloudflare-integration-tests/suites/tracing/propagation/workflow-do/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const MyWorkflow = Sentry.instrumentWorkflowWithSentry(
3737
(env: Env) => ({
3838
dsn: env.SENTRY_DSN,
3939
tracesSampleRate: 1.0,
40+
enableRpcTracePropagation: true,
4041
}),
4142
MyWorkflowBase,
4243
);
@@ -45,6 +46,7 @@ export default Sentry.withSentry(
4546
(env: Env) => ({
4647
dsn: env.SENTRY_DSN,
4748
tracesSampleRate: 1.0,
49+
enableRpcTracePropagation: true,
4850
}),
4951
{
5052
async fetch(request, env) {

dev-packages/e2e-tests/test-applications/cloudflare-workers/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
8585
// We are doing a lot of events at once in this test
8686
bufferSize: 1000,
8787
},
88-
instrumentPrototypeMethods: true,
88+
enableRpcTracePropagation: true,
8989
}),
9090
MyDurableObjectBase,
9191
);
@@ -101,6 +101,7 @@ export default Sentry.withSentry(
101101
// We are doing a lot of events at once in this test
102102
bufferSize: 1000,
103103
},
104+
enableRpcTracePropagation: true,
104105
}),
105106
{
106107
async fetch(request, env) {

dev-packages/e2e-tests/test-applications/cloudflare-workersentrypoint/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
7272
// We are doing a lot of events at once in this test
7373
bufferSize: 1000,
7474
},
75-
instrumentPrototypeMethods: true,
75+
enableRpcTracePropagation: true,
7676
}),
7777
MyDurableObjectBase,
7878
);
@@ -118,6 +118,7 @@ export default Sentry.withSentry(
118118
// We are doing a lot of events at once in this test
119119
bufferSize: 1000,
120120
},
121+
enableRpcTracePropagation: true,
121122
}),
122123
MyWorker,
123124
);

0 commit comments

Comments
 (0)