Skip to content

Commit 5f72df5

Browse files
authored
feat(cloudflare): Enable RPC trace propagation with enableRpcTracePropagation (#20345)
follow up to #19991 It is better to release it first with an option to be enabled, that would then also be in line with #20343, otherwise `.fetch()` RPC calls would work without any option and the actual Cap'n'Proto RPC calls wouldn't work without. That would be an odd experience. ### New option: `enableRpcTracePropagation` > `instrumentPrototypeMethods` has been deprecated in favor of `enableRpcTracePropagation` Replaces the deprecated `instrumentPrototypeMethods` option with a clearer name that describes what it actually does. This option must be enabled on **both** the caller (Worker) and receiver (Durable Object) sides for trace propagation to work. It is also worth to mention that the implementation of "instrumenting prototype methods" has changed to a Proxy. ```ts // Worker side export default Sentry.withSentry( (env) => ({ dsn: env.SENTRY_DSN, enableRpcTracePropagation: true, }), handler, ); // Durable Object side export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( (env) => ({ dsn: env.SENTRY_DSN, enableRpcTracePropagation: true, }), MyDurableObjectBase, ); ```
1 parent 50438f9 commit 5f72df5

File tree

23 files changed

+607
-40
lines changed

23 files changed

+607
-40
lines changed

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: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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 ({
6+
signal,
7+
}) => {
8+
let workerTraceId: string | undefined;
9+
let workerSpanId: string | undefined;
10+
let doTraceId: string | undefined;
11+
let doParentSpanId: string | undefined;
12+
13+
const runner = createRunner(__dirname)
14+
.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;
33+
})
34+
.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;
53+
})
54+
.unordered()
55+
.start(signal);
56+
await runner.makeRequest('get', '/');
57+
await runner.completed();
58+
59+
expect(workerTraceId).toBeDefined();
60+
expect(doTraceId).toBeDefined();
61+
expect(workerTraceId).not.toBe(doTraceId);
62+
63+
expect(workerSpanId).toBeDefined();
64+
expect(doParentSpanId).toBeUndefined();
65+
});
66+
67+
it('does not propagate trace from queue handler to durable object when enableRpcTracePropagation is disabled', async ({
68+
signal,
69+
}) => {
70+
let queueTraceId: string | undefined;
71+
let queueSpanId: string | undefined;
72+
let doTraceId: string | undefined;
73+
let doParentSpanId: string | undefined;
74+
75+
const runner = createRunner(__dirname)
76+
.expect(envelope => {
77+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
78+
79+
expect(transactionEvent).toEqual(
80+
expect.objectContaining({
81+
contexts: expect.objectContaining({
82+
trace: expect.objectContaining({
83+
op: 'http.server',
84+
data: expect.objectContaining({
85+
'sentry.origin': 'auto.http.cloudflare',
86+
}),
87+
origin: 'auto.http.cloudflare',
88+
}),
89+
}),
90+
transaction: 'GET /hello',
91+
}),
92+
);
93+
doTraceId = transactionEvent.contexts?.trace?.trace_id as string;
94+
doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string;
95+
})
96+
.expect(envelope => {
97+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
98+
99+
expect(transactionEvent).toEqual(
100+
expect.objectContaining({
101+
contexts: expect.objectContaining({
102+
trace: expect.objectContaining({
103+
op: 'queue.process',
104+
data: expect.objectContaining({
105+
'sentry.origin': 'auto.faas.cloudflare.queue',
106+
}),
107+
origin: 'auto.faas.cloudflare.queue',
108+
}),
109+
}),
110+
transaction: 'process my-queue',
111+
}),
112+
);
113+
queueTraceId = transactionEvent.contexts?.trace?.trace_id as string;
114+
queueSpanId = transactionEvent.contexts?.trace?.span_id as string;
115+
})
116+
// Also expect the fetch transaction from the /queue/send request
117+
.expect(envelope => {
118+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
119+
120+
expect(transactionEvent).toEqual(
121+
expect.objectContaining({
122+
contexts: expect.objectContaining({
123+
trace: expect.objectContaining({
124+
op: 'http.server',
125+
data: expect.objectContaining({
126+
'sentry.origin': 'auto.http.cloudflare',
127+
}),
128+
origin: 'auto.http.cloudflare',
129+
}),
130+
}),
131+
transaction: 'GET /queue/send',
132+
}),
133+
);
134+
})
135+
.unordered()
136+
.start(signal);
137+
// The fetch handler sends a message to the queue, which triggers the queue consumer
138+
await runner.makeRequest('get', '/queue/send');
139+
await runner.completed();
140+
141+
expect(queueTraceId).toBeDefined();
142+
expect(doTraceId).toBeDefined();
143+
expect(queueTraceId).not.toBe(doTraceId);
144+
145+
expect(queueSpanId).toBeDefined();
146+
expect(doParentSpanId).toBeUndefined();
147+
});
148+
149+
it('does not propagate trace from scheduled handler to durable object when enableRpcTracePropagation is disabled', async ({
150+
signal,
151+
}) => {
152+
let scheduledTraceId: string | undefined;
153+
let scheduledSpanId: string | undefined;
154+
let doTraceId: string | undefined;
155+
let doParentSpanId: string | undefined;
156+
157+
const runner = createRunner(__dirname)
158+
.withWranglerArgs('--test-scheduled')
159+
.expect(envelope => {
160+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
161+
162+
expect(transactionEvent).toEqual(
163+
expect.objectContaining({
164+
contexts: expect.objectContaining({
165+
trace: expect.objectContaining({
166+
op: 'http.server',
167+
data: expect.objectContaining({
168+
'sentry.origin': 'auto.http.cloudflare',
169+
}),
170+
origin: 'auto.http.cloudflare',
171+
}),
172+
}),
173+
transaction: 'GET /hello',
174+
}),
175+
);
176+
doTraceId = transactionEvent.contexts?.trace?.trace_id as string;
177+
doParentSpanId = transactionEvent.contexts?.trace?.parent_span_id as string;
178+
})
179+
.expect(envelope => {
180+
const transactionEvent = envelope[1]?.[0]?.[1] as Event;
181+
182+
expect(transactionEvent).toEqual(
183+
expect.objectContaining({
184+
contexts: expect.objectContaining({
185+
trace: expect.objectContaining({
186+
op: 'faas.cron',
187+
data: expect.objectContaining({
188+
'sentry.origin': 'auto.faas.cloudflare.scheduled',
189+
}),
190+
origin: 'auto.faas.cloudflare.scheduled',
191+
}),
192+
}),
193+
}),
194+
);
195+
scheduledTraceId = transactionEvent.contexts?.trace?.trace_id as string;
196+
scheduledSpanId = transactionEvent.contexts?.trace?.span_id as string;
197+
})
198+
.unordered()
199+
.start(signal);
200+
await runner.makeRequest('get', '/__scheduled?cron=*+*+*+*+*');
201+
await runner.completed();
202+
203+
expect(scheduledTraceId).toBeDefined();
204+
expect(doTraceId).toBeDefined();
205+
expect(scheduledTraceId).not.toBe(doTraceId);
206+
207+
expect(scheduledSpanId).toBeDefined();
208+
expect(doParentSpanId).toBeUndefined();
209+
});
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)