Skip to content

Commit f0e1cb4

Browse files
authored
ref(cloudflare): Move internal files and functions around (#19369)
This actually doesn't add any functionality or remove functionality. This PR only prepares for future PRs where we add instrumentations for DurableObjects. As the functionality will grow and reduce line size I moved them around Following is being moved: - `copyExecutionContext` -> `instrumentContext` - outsourced `wrapMethodWithSentry` Closes #19370 (added automatically)
1 parent 2baab5b commit f0e1cb4

File tree

6 files changed

+203
-183
lines changed

6 files changed

+203
-183
lines changed

packages/cloudflare/src/durableobject.ts

Lines changed: 5 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,159 +1,14 @@
11
/* eslint-disable @typescript-eslint/unbound-method */
2-
import {
3-
captureException,
4-
flush,
5-
getClient,
6-
isThenable,
7-
type Scope,
8-
SEMANTIC_ATTRIBUTE_SENTRY_OP,
9-
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
10-
startSpan,
11-
withIsolationScope,
12-
withScope,
13-
} from '@sentry/core';
2+
import { captureException } from '@sentry/core';
143
import type { DurableObject } from 'cloudflare:workers';
154
import { setAsyncLocalStorageAsyncContextStrategy } from './async';
165
import type { CloudflareOptions } from './client';
176
import { isInstrumented, markAsInstrumented } from './instrument';
187
import { getFinalOptions } from './options';
198
import { wrapRequestHandler } from './request';
20-
import { init } from './sdk';
21-
import { copyExecutionContext } from './utils/copyExecutionContext';
22-
23-
type MethodWrapperOptions = {
24-
spanName?: string;
25-
spanOp?: string;
26-
options: CloudflareOptions;
27-
context: ExecutionContext | DurableObjectState;
28-
};
29-
30-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
31-
type UncheckedMethod = (...args: any[]) => any;
32-
type OriginalMethod = UncheckedMethod;
33-
34-
function wrapMethodWithSentry<T extends OriginalMethod>(
35-
wrapperOptions: MethodWrapperOptions,
36-
handler: T,
37-
callback?: (...args: Parameters<T>) => void,
38-
noMark?: true,
39-
): T {
40-
if (isInstrumented(handler)) {
41-
return handler;
42-
}
43-
44-
if (!noMark) {
45-
markAsInstrumented(handler);
46-
}
47-
48-
return new Proxy(handler, {
49-
apply(target, thisArg, args: Parameters<T>) {
50-
const currentClient = getClient();
51-
// if a client is already set, use withScope, otherwise use withIsolationScope
52-
const sentryWithScope = currentClient ? withScope : withIsolationScope;
53-
54-
const wrappedFunction = (scope: Scope): unknown => {
55-
// In certain situations, the passed context can become undefined.
56-
// For example, for Astro while prerendering pages at build time.
57-
// see: https://github.com/getsentry/sentry-javascript/issues/13217
58-
const context = wrapperOptions.context as ExecutionContext | undefined;
59-
60-
const waitUntil = context?.waitUntil?.bind?.(context);
61-
62-
const currentClient = scope.getClient();
63-
if (!currentClient) {
64-
const client = init({ ...wrapperOptions.options, ctx: context });
65-
scope.setClient(client);
66-
}
67-
68-
if (!wrapperOptions.spanName) {
69-
try {
70-
if (callback) {
71-
callback(...args);
72-
}
73-
const result = Reflect.apply(target, thisArg, args);
74-
75-
if (isThenable(result)) {
76-
return result.then(
77-
(res: unknown) => {
78-
waitUntil?.(flush(2000));
79-
return res;
80-
},
81-
(e: unknown) => {
82-
captureException(e, {
83-
mechanism: {
84-
type: 'auto.faas.cloudflare.durable_object',
85-
handled: false,
86-
},
87-
});
88-
waitUntil?.(flush(2000));
89-
throw e;
90-
},
91-
);
92-
} else {
93-
waitUntil?.(flush(2000));
94-
return result;
95-
}
96-
} catch (e) {
97-
captureException(e, {
98-
mechanism: {
99-
type: 'auto.faas.cloudflare.durable_object',
100-
handled: false,
101-
},
102-
});
103-
waitUntil?.(flush(2000));
104-
throw e;
105-
}
106-
}
107-
108-
const attributes = wrapperOptions.spanOp
109-
? {
110-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: wrapperOptions.spanOp,
111-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.faas.cloudflare.durable_object',
112-
}
113-
: {};
114-
115-
return startSpan({ name: wrapperOptions.spanName, attributes }, () => {
116-
try {
117-
const result = Reflect.apply(target, thisArg, args);
118-
119-
if (isThenable(result)) {
120-
return result.then(
121-
(res: unknown) => {
122-
waitUntil?.(flush(2000));
123-
return res;
124-
},
125-
(e: unknown) => {
126-
captureException(e, {
127-
mechanism: {
128-
type: 'auto.faas.cloudflare.durable_object',
129-
handled: false,
130-
},
131-
});
132-
waitUntil?.(flush(2000));
133-
throw e;
134-
},
135-
);
136-
} else {
137-
waitUntil?.(flush(2000));
138-
return result;
139-
}
140-
} catch (e) {
141-
captureException(e, {
142-
mechanism: {
143-
type: 'auto.faas.cloudflare.durable_object',
144-
handled: false,
145-
},
146-
});
147-
waitUntil?.(flush(2000));
148-
throw e;
149-
}
150-
});
151-
};
152-
153-
return sentryWithScope(wrappedFunction);
154-
},
155-
});
156-
}
9+
import { instrumentContext } from './utils/instrumentContext';
10+
import type { UncheckedMethod } from './wrapMethodWithSentry';
11+
import { wrapMethodWithSentry } from './wrapMethodWithSentry';
15712

15813
/**
15914
* Instruments a Durable Object class to capture errors and performance data.
@@ -196,7 +51,7 @@ export function instrumentDurableObjectWithSentry<
19651
return new Proxy(DurableObjectClass, {
19752
construct(target, [ctx, env]) {
19853
setAsyncLocalStorageAsyncContextStrategy();
199-
const context = copyExecutionContext(ctx);
54+
const context = instrumentContext(ctx);
20055

20156
const options = getFinalOptions(optionsCallback(env), env);
20257

packages/cloudflare/src/handler.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { getFinalOptions } from './options';
1515
import { wrapRequestHandler } from './request';
1616
import { addCloudResourceContext } from './scope-utils';
1717
import { init } from './sdk';
18-
import { copyExecutionContext } from './utils/copyExecutionContext';
18+
import { instrumentContext } from './utils/instrumentContext';
1919

2020
/**
2121
* Wrapper for Cloudflare handlers.
@@ -46,7 +46,7 @@ export function withSentry<
4646
handler.fetch = new Proxy(handler.fetch, {
4747
apply(target, thisArg, args: Parameters<ExportedHandlerFetchHandler<Env, CfHostMetadata>>) {
4848
const [request, env, ctx] = args;
49-
const context = copyExecutionContext(ctx);
49+
const context = instrumentContext(ctx);
5050
args[2] = context;
5151

5252
const options = getFinalOptions(optionsCallback(env), env);
@@ -82,7 +82,7 @@ export function withSentry<
8282
handler.scheduled = new Proxy(handler.scheduled, {
8383
apply(target, thisArg, args: Parameters<ExportedHandlerScheduledHandler<Env>>) {
8484
const [event, env, ctx] = args;
85-
const context = copyExecutionContext(ctx);
85+
const context = instrumentContext(ctx);
8686
args[2] = context;
8787

8888
return withIsolationScope(isolationScope => {
@@ -128,7 +128,7 @@ export function withSentry<
128128
handler.email = new Proxy(handler.email, {
129129
apply(target, thisArg, args: Parameters<EmailExportedHandler<Env>>) {
130130
const [emailMessage, env, ctx] = args;
131-
const context = copyExecutionContext(ctx);
131+
const context = instrumentContext(ctx);
132132
args[2] = context;
133133

134134
return withIsolationScope(isolationScope => {
@@ -172,7 +172,7 @@ export function withSentry<
172172
handler.queue = new Proxy(handler.queue, {
173173
apply(target, thisArg, args: Parameters<ExportedHandlerQueueHandler<Env, QueueHandlerMessage>>) {
174174
const [batch, env, ctx] = args;
175-
const context = copyExecutionContext(ctx);
175+
const context = instrumentContext(ctx);
176176
args[2] = context;
177177

178178
return withIsolationScope(isolationScope => {
@@ -224,7 +224,7 @@ export function withSentry<
224224
handler.tail = new Proxy(handler.tail, {
225225
apply(target, thisArg, args: Parameters<ExportedHandlerTailHandler<Env>>) {
226226
const [, env, ctx] = args;
227-
const context = copyExecutionContext(ctx);
227+
const context = instrumentContext(ctx);
228228
args[2] = context;
229229

230230
return withIsolationScope(async isolationScope => {

packages/cloudflare/src/utils/copyExecutionContext.ts renamed to packages/cloudflare/src/utils/instrumentContext.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,35 @@ type ContextType = ExecutionContext | DurableObjectState;
44
type OverridesStore<T extends ContextType> = Map<keyof T, (...args: unknown[]) => unknown>;
55

66
/**
7-
* Creates a new copy of the given execution context, optionally overriding methods.
7+
* Instruments an execution context or DurableObjectState with Sentry tracing.
88
*
9-
* @param {ContextType|void} ctx - The execution context to be copied. Can be of type `ContextType` or `void`.
10-
* @return {ContextType|void} A new execution context with the same properties and overridden methods if applicable.
9+
* Creates a copy of the context that:
10+
* - Allows overriding of methods (e.g., waitUntil)
11+
*
12+
* @param ctx - The execution context or DurableObjectState to instrument
13+
* @returns An instrumented copy of the context
1114
*/
12-
export function copyExecutionContext<T extends ContextType>(ctx: T): T {
15+
export function instrumentContext<T extends ContextType>(ctx: T): T {
1316
if (!ctx) return ctx;
1417

1518
const overrides: OverridesStore<T> = new Map();
1619
const contextPrototype = Object.getPrototypeOf(ctx);
1720
const prototypeMethodNames = Object.getOwnPropertyNames(contextPrototype) as unknown as (keyof T)[];
1821
const ownPropertyNames = Object.getOwnPropertyNames(ctx) as unknown as (keyof T)[];
1922
const instrumented = new Set<unknown>(['constructor']);
20-
const descriptors = [...ownPropertyNames, ...prototypeMethodNames].reduce((prevDescriptors, methodName) => {
21-
if (instrumented.has(methodName)) return prevDescriptors;
22-
if (typeof ctx[methodName] !== 'function') return prevDescriptors;
23-
instrumented.add(methodName);
24-
const overridableDescriptor = makeOverridableDescriptor(overrides, ctx, methodName);
25-
return {
26-
...prevDescriptors,
27-
[methodName]: overridableDescriptor,
28-
};
29-
}, {});
23+
const descriptors: PropertyDescriptorMap = [...ownPropertyNames, ...prototypeMethodNames].reduce(
24+
(prevDescriptors, methodName) => {
25+
if (instrumented.has(methodName)) return prevDescriptors;
26+
if (typeof ctx[methodName] !== 'function') return prevDescriptors;
27+
instrumented.add(methodName);
28+
const overridableDescriptor = makeOverridableDescriptor(overrides, ctx, methodName);
29+
return {
30+
...prevDescriptors,
31+
[methodName]: overridableDescriptor,
32+
};
33+
},
34+
{} as PropertyDescriptorMap,
35+
);
3036

3137
return Object.create(ctx, descriptors);
3238
}

packages/cloudflare/src/workflows.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { setAsyncLocalStorageAsyncContextStrategy } from './async';
2222
import type { CloudflareOptions } from './client';
2323
import { addCloudResourceContext } from './scope-utils';
2424
import { init } from './sdk';
25-
import { copyExecutionContext } from './utils/copyExecutionContext';
25+
import { instrumentContext } from './utils/instrumentContext';
2626

2727
const UUID_REGEX = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i;
2828

@@ -158,7 +158,7 @@ export function instrumentWorkflowWithSentry<
158158
return new Proxy(WorkFlowClass, {
159159
construct(target: C, args: [ctx: ExecutionContext, env: E], newTarget) {
160160
const [ctx, env] = args;
161-
const context = copyExecutionContext(ctx);
161+
const context = instrumentContext(ctx);
162162
args[0] = context;
163163

164164
const options = optionsCallback(env);

0 commit comments

Comments
 (0)