Skip to content
Merged
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
22 changes: 14 additions & 8 deletions dev-packages/cloudflare-integration-tests/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,16 +256,22 @@ export function createRunner(...paths: string[]) {
return;
}

// Check per-request waiters first (FIFO order)
// Resolve per-request waiters first, matching in any order so a request
// expecting multiple envelopes isn't sensitive to their arrival order.
if (envelopeWaiters.length > 0) {
const waiter = envelopeWaiters.shift()!;
try {
assertEnvelopeMatches(waiter.expected, envelope);
waiter.resolve();
} catch (e) {
waiter.reject(e);
const waiterIndex = envelopeWaiters.findIndex(waiter => {
try {
assertEnvelopeMatches(waiter.expected, envelope);
return true;
} catch {
return false;
}
});

if (waiterIndex >= 0) {
envelopeWaiters.splice(waiterIndex, 1)[0]!.resolve();
return;
}
return;
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,24 @@ export const TestDurableObject = Sentry.instrumentDurableObjectWithSentry(
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
instrumentPrototypeMethods: true,
enableRpcTracePropagation: true,
}),
TestDurableObjectBase,
);

export default {
async fetch(_request: Request, env: Env): Promise<Response> {
const id: DurableObjectId = env.TEST_DURABLE_OBJECT.idFromName('test');
const stub = env.TEST_DURABLE_OBJECT.get(id) as unknown as TestDurableObjectBase;
const result = await stub.doWork();
return new Response(result);
},
};
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
traceLifecycle: 'static',
tracesSampleRate: 1.0,
enableRpcTracePropagation: true,
}),
{
async fetch(_request: Request, env: Env): Promise<Response> {
const id: DurableObjectId = env.TEST_DURABLE_OBJECT.idFromName('test');
const stub = env.TEST_DURABLE_OBJECT.get(id) as unknown as TestDurableObjectBase;
const result = await stub.doWork();
return new Response(result);
},
} satisfies ExportedHandler<Env>,
);
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,28 @@ it('sends child spans on repeated Durable Object calls', async ({ signal }) => {
}
}

function assertOuterRequestEnvelope(envelope: unknown): void {
const transactionEvent = (envelope as any)[1]?.[0]?.[1];

expect(transactionEvent).toEqual(
expect.objectContaining({
transaction: 'GET /',
contexts: expect.objectContaining({
trace: expect.objectContaining({
op: 'http.server',
origin: 'auto.http.cloudflare',
}),
}),
}),
);
}

const runner = createRunner(__dirname).start(signal);

// Each request waits for its envelope to be received and validated before proceeding.
await runner.makeRequestAndWaitForEnvelope('get', '/', assertDoWorkEnvelope);
await runner.makeRequestAndWaitForEnvelope('get', '/', assertDoWorkEnvelope);
await runner.makeRequestAndWaitForEnvelope('get', '/', assertDoWorkEnvelope);
await runner.makeRequestAndWaitForEnvelope('get', '/', assertDoWorkEnvelope);
await runner.makeRequestAndWaitForEnvelope('get', '/', assertDoWorkEnvelope);
// Make 5 requests and assert that the envelopes are received and validated.
await runner.makeRequestAndWaitForEnvelope('get', '/', [assertDoWorkEnvelope, assertOuterRequestEnvelope]);
await runner.makeRequestAndWaitForEnvelope('get', '/', [assertDoWorkEnvelope, assertOuterRequestEnvelope]);
await runner.makeRequestAndWaitForEnvelope('get', '/', [assertDoWorkEnvelope, assertOuterRequestEnvelope]);
await runner.makeRequestAndWaitForEnvelope('get', '/', [assertDoWorkEnvelope, assertOuterRequestEnvelope]);
await runner.makeRequestAndWaitForEnvelope('get', '/', [assertDoWorkEnvelope, assertOuterRequestEnvelope]);
});
14 changes: 0 additions & 14 deletions packages/cloudflare/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,20 +281,6 @@ interface BaseCloudflareOptions {
*/
durableObjectStorageSpanAllowlist?: Array<string | RegExp>;

/**
* @deprecated Use `enableRpcTracePropagation` instead. This option will be removed in a future major version.
*
* Enable instrumentation of prototype methods for DurableObjects.
*
* When `true`, the SDK will wrap all methods on the DurableObject prototype chain
* to automatically create spans and capture errors for RPC method calls.
*
* When an array of strings is provided, only the specified method names will be instrumented.
*
* @default false
*/
instrumentPrototypeMethods?: boolean | string[];

/**
* If you use Spotlight by Sentry during development, use
* this option to forward captured Sentry events to Spotlight.
Expand Down
33 changes: 2 additions & 31 deletions packages/cloudflare/src/durableobject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { wrapRequestHandlerWithInit } from './request';
import { init } from './sdk';
import { instrumentContext } from './utils/instrumentContext';
import { extractRpcMeta } from './utils/rpcMeta';
import { getEffectiveRpcPropagation } from './utils/rpcOptions';
import { instrumentCloudflareAgent } from './instrumentations/agents';
import { type UncheckedMethod, wrapMethodWithSentry } from './wrapMethodWithSentry';

Expand Down Expand Up @@ -161,27 +160,11 @@ export function finalizeWithRpcInstrumentation<T extends object>(
options: CloudflareOptions,
context: InstrumentedDurableObjectContext,
): T {
// Get effective RPC propagation setting (handles deprecation of instrumentPrototypeMethods)
const rpcPropagation = getEffectiveRpcPropagation(options);

// Skip RPC instrumentation if not enabled
if (!rpcPropagation) {
if (!options.enableRpcTracePropagation) {
return obj;
}

// If `instrumentPrototypeMethods` was passed as an array (deprecated),
// only the listed method names should be instrumented.
// eslint-disable-next-line typescript/no-deprecated
const instrumentPrototypeMethods = Array.isArray(options.instrumentPrototypeMethods)
? // eslint-disable-next-line typescript/no-deprecated
options.instrumentPrototypeMethods
: undefined;
const allowSet = instrumentPrototypeMethods ? new Set(instrumentPrototypeMethods) : null;

// When using the deprecated `instrumentPrototypeMethods` option, always create spans.
// When using the new `enableRpcTracePropagation`, only create spans when RPC metadata is present.
const alwaysTrace = options.enableRpcTracePropagation === undefined;

// Return a Proxy that binds all methods to the original object and creates spans
// for RPC calls that have Sentry trace context propagated.
// Binding is required because frameworks may use private fields (babel WeakMap pattern),
Expand All @@ -204,11 +187,7 @@ export function finalizeWithRpcInstrumentation<T extends object>(

const boundMethod = (value as UncheckedMethod).bind(proxyTarget);

if (
prop in Object.prototype ||
Object.prototype.hasOwnProperty.call(proxyTarget, prop) ||
(allowSet && !allowSet.has(prop))
) {
if (prop in Object.prototype || Object.prototype.hasOwnProperty.call(proxyTarget, prop)) {
methodCache.set(prop, boundMethod);

return boundMethod;
Expand All @@ -222,14 +201,6 @@ export function finalizeWithRpcInstrumentation<T extends object>(
true,
);

// For deprecated `instrumentPrototypeMethods`, always trace.
// For new `enableRpcTracePropagation`, only trace when RPC metadata is present.
if (alwaysTrace) {
methodCache.set(prop, tracedMethod);

return tracedMethod;
}

// Wrapper that checks for Sentry RPC metadata at call time
const wrappedMethod = ((...args: unknown[]) => {
const { rpcMeta } = extractRpcMeta(args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
} from '../../utils/isBinding';
import { instrumentD1 } from './instrumentD1';
import { appendRpcMeta } from '../../utils/rpcMeta';
import { getEffectiveRpcPropagation } from '../../utils/rpcOptions';
import { instrumentDurableObjectNamespace, STUB_NON_RPC_METHODS } from '../instrumentDurableObjectNamespace';
import { instrumentFetcher } from './instrumentFetcher';
import { instrumentQueueProducer } from './instrumentQueueProducer';
Expand Down Expand Up @@ -45,8 +44,6 @@ export function instrumentEnv<Env extends Record<string, unknown>>(env: Env, opt
return env;
}

const rpcPropagation = options ? getEffectiveRpcPropagation(options) : false;

return new Proxy(env, {
get(target, prop, receiver) {
const item = Reflect.get(target, prop, receiver);
Expand Down Expand Up @@ -94,7 +91,7 @@ export function instrumentEnv<Env extends Record<string, unknown>>(env: Env, opt
return instrumented;
}

if (!rpcPropagation) {
if (!options?.enableRpcTracePropagation) {
return item;
}

Expand Down
46 changes: 0 additions & 46 deletions packages/cloudflare/src/utils/rpcOptions.ts

This file was deleted.

Loading
Loading