Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -835,14 +835,26 @@ Sentry.init({
);
```

- The `instrumentPrototypeMethods` option of `instrumentDurableObjectWithSentry` was removed. Use `enableRpcTracePropagation` instead, which was introduced as its replacement in v10.
- The `enableRpcTracePropagation` option was removed. Trace context is no longer propagated to every binding on `env`. List the bindings you call in `rpcTracePropagationTargets` instead. Strings match a binding name exactly, regular expressions match by pattern. Receivers no longer take the option at all: an instrumented Durable Object or WorkerEntrypoint reads the trace context whenever a caller sends it.

```diff
export default Sentry.withSentry(
(env) => ({
dsn: env.SENTRY_DSN,
- enableRpcTracePropagation: true,
+ rpcTracePropagationTargets: ['ORDERS', /^SVC_/],
}),
handler,
);
```

- The `instrumentPrototypeMethods` option of `instrumentDurableObjectWithSentry` was removed. A Durable Object instruments its RPC methods unconditionally now, so there is nothing to replace it with on the receiver.

```diff
export const MyDO = Sentry.instrumentDurableObjectWithSentry(
(env) => ({
dsn: env.SENTRY_DSN,
- instrumentPrototypeMethods: true,
+ enableRpcTracePropagation: true,
}),
MyDOBase,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export const ScopeDurableObject = Sentry.instrumentDurableObjectWithSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1,
enableRpcTracePropagation: true,
}),
ScopeDurableObjectBase,
);
Expand All @@ -84,7 +83,7 @@ export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['SCOPE_DO'],
}),
{
async fetch(request, env) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export const TestDurableObject = Sentry.instrumentDurableObjectWithSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
}),
SyncAlarmDurableObjectBase,
);
Expand All @@ -37,7 +36,7 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['TEST_DURABLE_OBJECT'],
}),
{
async fetch(request: Request, env: Env): Promise<Response> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export const TestDurableObject = Sentry.instrumentDurableObjectWithSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
}),
AlarmDurableObjectBase,
);
Expand All @@ -35,7 +34,7 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['TEST_DURABLE_OBJECT'],
}),
{
async fetch(request: Request, env: Env): Promise<Response> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
}),
MyDurableObjectBase,
);
Expand All @@ -47,7 +46,7 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['MY_DURABLE_OBJECT'],
}),
{
async fetch(request, env) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Event } from '@sentry/core';
import { createRunner } from '../../../runner';

// Regression for #23040 — a Durable Object using native private fields must stay functional when
// instrumented with `enableRpcTracePropagation: true`. Native RPC dispatch (Durable Object facets,
// instrumented with Sentry. Native RPC dispatch (Durable Object facets,
// the Agents SDK bootstrap) invokes prototype methods with the stored instance as the receiver,
// so the instrumented instance must not be a Proxy: a Proxy does not carry the private-field
// brand and `this.#field` throws "Cannot read private member".
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export const TestDurableObject = Sentry.instrumentDurableObjectWithSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
}),
TestDurableObjectBase,
);
Expand All @@ -42,7 +41,7 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['TEST_DURABLE_OBJECT'],
}),
{
async fetch(_request: Request, env: Env): Promise<Response> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export const TestDurableObject = Sentry.instrumentDurableObjectWithSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
}),
TestDurableObjectBase,
);
Expand All @@ -56,7 +55,7 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['TEST_DURABLE_OBJECT'],
}),
{
async fetch(request: Request, env: Env): Promise<Response> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ it('handles consecutive RPC calls without throwing "RPC receiver does not implem
});

// Regression test: RPC methods that access private fields should work correctly.
// When enableRpcTracePropagation wraps the DO in a Proxy, calling methods through
// When rpcTracePropagationTargets wraps the DO in a Proxy, calling methods through
// the Proxy must ensure `this` refers to the original object (not the Proxy),
// otherwise private field access throws: "Cannot read private member from an object
// whose class did not declare it"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['ECHO_HEADERS_DO'],
}),
{
async fetch(request, env) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, it } from 'vitest';
import type { Event } from '@sentry/core';
import { createRunner } from '../../../../runner';

it('does not propagate trace from worker to durable object when enableRpcTracePropagation is disabled', async ({
it('does not propagate trace from worker to durable object when rpcTracePropagationTargets is empty', async ({
signal,
}) => {
let workerTraceId: string | undefined;
Expand Down Expand Up @@ -64,7 +64,7 @@ it('does not propagate trace from worker to durable object when enableRpcTracePr
expect(doParentSpanId).toBeUndefined();
});

it('does not propagate trace from queue handler to durable object when enableRpcTracePropagation is disabled', async ({
it('does not propagate trace from queue handler to durable object when rpcTracePropagationTargets is empty', async ({
signal,
}) => {
let queueTraceId: string | undefined;
Expand Down Expand Up @@ -146,7 +146,7 @@ it('does not propagate trace from queue handler to durable object when enableRpc
expect(doParentSpanId).toBeUndefined();
});

it('does not propagate trace from scheduled handler to durable object when enableRpcTracePropagation is disabled', async ({
it('does not propagate trace from scheduled handler to durable object when rpcTracePropagationTargets is empty', async ({
signal,
}) => {
let scheduledTraceId: string | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, it } from 'vitest';
import type { Event } from '@sentry/core';
import { createRunner } from '../../../../runner';

it('does not propagate trace when enableRpcTracePropagation is disabled', async ({ signal }) => {
it('does not propagate trace when rpcTracePropagationTargets is empty', async ({ signal }) => {
let workerTraceId: string | undefined;
let doTraceId: string | undefined;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
}),
MyDurableObjectBase,
);
Expand All @@ -31,7 +30,7 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['MY_DURABLE_OBJECT'],
}),
{
async fetch(request, env) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
}),
MyDurableObjectBase,
);
Expand All @@ -28,7 +27,7 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['MY_DURABLE_OBJECT'],
}),
{
async fetch(request, env) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['ANOTHER_WORKER'],
}),
{
async fetch(request, env) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
}),
MyDurableObjectBase,
);
Expand All @@ -27,7 +26,7 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['MY_DURABLE_OBJECT'],
}),
{
async fetch(request, env) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['SUB_WORKER'],
}),
{
async fetch(request, env) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export const BindingEntrypoint = Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
initialScope: { tags: { initial_scope: 'applied' } },
beforeSend(event) {
event.tags = { ...event.tags, before_send: 'applied' };
Expand All @@ -57,6 +56,8 @@ export const BindingEntrypoint = Sentry.withSentry(
MySubWorkerEntrypointBase,
);

// Instrumented like any other receiver. It is the caller that leaves this binding out of its
// targets, which is now the only way to opt a binding out of trace propagation.
export const NoPropagationEntrypoint = Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
Expand All @@ -67,4 +68,12 @@ export const NoPropagationEntrypoint = Sentry.withSentry(
MySubWorkerEntrypointBase,
);

// Deliberately not wrapped with Sentry: nothing strips a trailing RPC metadata argument here, so
// this is what a caller corrupts if it propagates to a receiver it has no guarantees about.
export class UninstrumentedEntrypoint extends WorkerEntrypoint<Env> {
get(key: string): { argumentCount: number; key: string } {
return { argumentCount: arguments.length, key };
}
}

export default BindingEntrypoint;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ interface Env {
SUB_WORKER_NO_PROPAGATION: Fetcher & {
get(key: string): Promise<{ argumentCount: number; key: string }>;
};
SUB_WORKER_UNINSTRUMENTED: Fetcher & {
get(key: string): Promise<{ argumentCount: number; key: string }>;
};
}

class LoopbackEntrypointBase extends WorkerEntrypoint<Env> {
Expand All @@ -29,7 +32,10 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
// Targeted by binding name. Two bindings are deliberately left out:
// `SUB_WORKER_UNINSTRUMENTED`, whose receiver has no Sentry to strip a trailing metadata
// argument, and `SUB_WORKER_NO_PROPAGATION`, which covers the untargeted-binding path.
rpcTracePropagationTargets: ['SUB_WORKER'],
}),
{
async fetch(request, env, ctx) {
Expand Down Expand Up @@ -61,6 +67,10 @@ export default Sentry.withSentry(
}
}

if (url.pathname === '/call-uninstrumented-rpc') {
return Response.json(await env.SUB_WORKER_UNINSTRUMENTED.get('uninstrumented-key'));
}

if (url.pathname === '/call-entrypoint-rpc-no-propagation') {
const result = await env.SUB_WORKER_NO_PROPAGATION.get('no-prop-key');
return Response.json(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,24 @@ it('captures errors thrown by custom WorkerEntrypoint RPC methods', async ({ sig
await runner.completed();
});

it('does not inject RPC trace metadata into receiver calls when enableRpcTracePropagation is disabled', async ({
// Regression test for https://github.com/getsentry/sentry-javascript/issues/23233: a receiver that
// is not instrumented never strips Sentry's trailing metadata argument, so a caller must only
// propagate to bindings it was explicitly told about.
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');
})
.start(signal);

const response = await runner.makeRequest<{ argumentCount: number; key: string }>('get', '/call-uninstrumented-rpc');
expect(response).toEqual({ argumentCount: 1, key: 'uninstrumented-key' });

await runner.completed();
});

it('does not inject RPC trace metadata into receiver calls when rpcTracePropagationTargets is empty', async ({
signal,
}) => {
const runner = createRunner(__dirname)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@
"service": "cloudflare-worker-workerentrypoint-rpc-sub",
"entrypoint": "NoPropagationEntrypoint",
},
{
"binding": "SUB_WORKER_UNINSTRUMENTED",
"service": "cloudflare-worker-workerentrypoint-rpc-sub",
"entrypoint": "UninstrumentedEntrypoint",
},
],
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, it } from 'vitest';
import type { Event } from '@sentry/core';
import { createRunner } from '../../../../runner';

it('does not propagate trace when enableRpcTracePropagation is disabled (WorkerEntrypoint)', async ({ signal }) => {
it('does not propagate trace when rpcTracePropagationTargets is empty (WorkerEntrypoint)', async ({ signal }) => {
let workerTraceId: string | undefined;
let doTraceId: string | undefined;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
}),
MyDurableObjectBase,
);
Expand Down Expand Up @@ -51,7 +50,7 @@ export default Sentry.withSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
rpcTracePropagationTargets: ['MY_DURABLE_OBJECT'],
}),
MyWorkerEntrypointBase,
);
Loading
Loading