Skip to content

Commit 51c11b4

Browse files
committed
feat(cloudflare)!: Remove wrapRequestHandlerWithInit as export
1 parent 359e1f3 commit 51c11b4

5 files changed

Lines changed: 218 additions & 216 deletions

File tree

packages/cloudflare/src/durableobject.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { CloudflareOptions } from './client';
66
import { ensureInstrumented, getInstrumented, markAsInstrumented } from './instrument';
77
import { instrumentEnv } from './instrumentations/worker/instrumentEnv';
88
import { getFinalOptions } from './options';
9-
import { wrapRequestHandlerWithInit } from './request';
9+
import { wrapRequestHandlerWithInit } from './wrapRequestHandlerWithInit';
1010
import { init } from './sdk';
1111
import { instrumentContext } from './utils/instrumentContext';
1212
import { hasRpcMeta } from './utils/rpcMeta';

packages/cloudflare/src/instrumentations/worker/instrumentFetch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { env as cloudflareEnv, WorkerEntrypoint } from 'cloudflare:workers'
33
import type { CloudflareOptions } from '../../client';
44
import { ensureInstrumented } from '../../instrument';
55
import { getFinalOptions } from '../../options';
6-
import { wrapRequestHandlerWithInit } from '../../request';
6+
import { wrapRequestHandlerWithInit } from '../../wrapRequestHandlerWithInit';
77
import { init } from '../../sdk';
88
import { instrumentContext } from '../../utils/instrumentContext';
99
import { instrumentEnv } from './instrumentEnv';

packages/cloudflare/src/pages-plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels';
22
import type { CloudflareOptions } from './client';
33
import type { ExecutionContextCompat } from './executionContext';
4-
import { wrapRequestHandlerWithInit } from './request';
4+
import { wrapRequestHandlerWithInit } from './wrapRequestHandlerWithInit';
55
import { init } from './sdk';
66

77
/**

packages/cloudflare/src/request.ts

Lines changed: 2 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,6 @@
1-
import type { CfProperties, IncomingRequestCfProperties } from '@cloudflare/workers-types';
2-
import {
3-
captureException,
4-
continueTrace,
5-
getHttpSpanDetailsFromUrlObject,
6-
httpHeadersToSpanAttributes,
7-
parseStringToURLObject,
8-
SEMANTIC_ATTRIBUTE_SENTRY_OP,
9-
setHttpStatus,
10-
startSpanManual,
11-
winterCGHeadersToDict,
12-
} from '@sentry/core';
13-
import { captureIncomingRequestBody } from './integrations/httpServer';
141
import { initBaseSdk } from './baseSdk';
15-
import type { CloudflareClient, CloudflareOptions } from './client';
16-
import type { ExecutionContextCompat } from './executionContext';
17-
import { flushAndDispose, getOriginalWaitUntil } from './flush';
18-
import { addCloudResourceContext, addCultureContext, addRequest } from './scope-utils';
19-
import { withInvocationIsolationScope } from './utils/invocationScope';
20-
import { classifyResponseStreaming } from './utils/streaming';
21-
22-
function getRequestErrorMechanismType(context: ExecutionContextCompat | undefined): string {
23-
// Durable Object fetch handlers use DO state as context (see instrumentDurableObjectWithSentry)
24-
return context && 'storage' in context ? 'auto.faas.cloudflare.durable_object' : 'auto.http.cloudflare';
25-
}
26-
27-
interface RequestHandlerWrapperOptions {
28-
options: CloudflareOptions;
29-
request: Request<unknown, IncomingRequestCfProperties<unknown> | CfProperties<unknown>>;
30-
context: ExecutionContextCompat | undefined;
31-
/**
32-
* If true, errors will be captured, rethrown and sent to Sentry.
33-
* Otherwise, errors are rethrown but not captured.
34-
*
35-
* You most likely don't want to set this to `false`, if you use `wrapRequestHandler` directly.
36-
* This is primarily meant as an escape hatch for higher-level SDKs relying on additional error
37-
* capturing mechanisms where this wrapper captures errors too early or too generally.
38-
*
39-
* @default true
40-
*/
41-
captureErrors?: boolean;
42-
}
43-
44-
type InitSdk = (options: CloudflareOptions) => CloudflareClient | undefined;
2+
import type { CloudflareOptions } from './client';
3+
import { type RequestHandlerWrapperOptions, wrapRequestHandlerWithInit } from './wrapRequestHandlerWithInit';
454

465
/**
476
* Wraps a cloudflare request handler in Sentry instrumentation.
@@ -61,173 +20,3 @@ export function wrapRequestHandler(
6120
): Promise<Response> {
6221
return wrapRequestHandlerWithInit(wrapperOptions, handler, initBaseSdk);
6322
}
64-
65-
/**
66-
* Same as {@link wrapRequestHandler}, but with the SDK initialization injected.
67-
*
68-
* Wrappers that are only reachable from the main entry point — where `nodejs_compat` is a
69-
* requirement anyway — pass `init` from `sdk.ts` to get the full default integrations.
70-
*
71-
* @internal
72-
*/
73-
export function wrapRequestHandlerWithInit(
74-
wrapperOptions: RequestHandlerWrapperOptions,
75-
handler: (...args: unknown[]) => Response | Promise<Response>,
76-
initSdk: InitSdk,
77-
): Promise<Response> {
78-
return withInvocationIsolationScope(async isolationScope => {
79-
const { options, request, captureErrors = true } = wrapperOptions;
80-
const context = wrapperOptions.context;
81-
82-
// Use getOriginalWaitUntil to get the un-instrumented waitUntil function.
83-
// This is crucial to avoid deadlock: the flush lock mechanism wraps waitUntil
84-
// to track pending tasks. If we use the instrumented version for flushAndDispose,
85-
// it acquires the lock, then flushAndDispose tries to wait for the same lock,
86-
// creating a deadlock.
87-
const waitUntil = context ? getOriginalWaitUntil(context).bind(context) : undefined;
88-
const errorMechanismType = getRequestErrorMechanismType(context);
89-
90-
const client = initSdk({ ...options, ctx: context });
91-
isolationScope.setClient(client);
92-
93-
const urlObject = parseStringToURLObject(request.url);
94-
const [name, attributes] = getHttpSpanDetailsFromUrlObject(
95-
urlObject,
96-
'server',
97-
'auto.http.cloudflare',
98-
request,
99-
undefined,
100-
client,
101-
);
102-
103-
const contentLength = request.headers.get('content-length');
104-
if (contentLength) {
105-
attributes['http.request.body.size'] = parseInt(contentLength, 10);
106-
}
107-
108-
const userAgentHeader = request.headers.get('user-agent');
109-
if (userAgentHeader) {
110-
attributes['user_agent.original'] = userAgentHeader;
111-
}
112-
113-
if (client) {
114-
Object.assign(
115-
attributes,
116-
httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), client.getDataCollectionOptions()),
117-
);
118-
}
119-
120-
attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';
121-
122-
addCloudResourceContext(isolationScope);
123-
addRequest(isolationScope, request);
124-
if (request.cf) {
125-
addCultureContext(isolationScope, request.cf);
126-
127-
if (typeof request.cf.httpProtocol === 'string') {
128-
attributes['network.protocol.name'] = request.cf.httpProtocol;
129-
}
130-
}
131-
132-
// Do not capture spans for OPTIONS and HEAD requests
133-
if (request.method === 'OPTIONS' || request.method === 'HEAD') {
134-
try {
135-
return await handler();
136-
} catch (e) {
137-
if (captureErrors) {
138-
captureException(e, { mechanism: { handled: false, type: errorMechanismType } });
139-
}
140-
throw e;
141-
} finally {
142-
waitUntil?.(flushAndDispose(client));
143-
}
144-
}
145-
146-
if (client) {
147-
await captureIncomingRequestBody(client, request);
148-
}
149-
150-
return continueTrace(
151-
{ sentryTrace: request.headers.get('sentry-trace') || '', baggage: request.headers.get('baggage') },
152-
() => {
153-
// Note: This span will not have a duration unless I/O happens in the handler. This is
154-
// because of how the cloudflare workers runtime works.
155-
// See: https://developers.cloudflare.com/workers/runtime-apis/performance/
156-
157-
// Use startSpanManual to control when span ends (needed for streaming responses)
158-
return startSpanManual({ name, attributes }, async span => {
159-
let res: Response;
160-
161-
try {
162-
res = await handler();
163-
setHttpStatus(span, res.status);
164-
165-
// After the handler runs, the span name might have been updated by nested instrumentation
166-
// (e.g., Remix parameterizing routes). The span should already have the correct name
167-
// from that instrumentation, so we don't need to do anything here.
168-
} catch (e) {
169-
span.end();
170-
if (captureErrors) {
171-
captureException(e, { mechanism: { handled: false, type: errorMechanismType } });
172-
}
173-
waitUntil?.(flushAndDispose(client));
174-
throw e;
175-
}
176-
177-
// Classify response to detect actual streaming
178-
const classification = classifyResponseStreaming(res);
179-
180-
if (classification.isStreaming && res.body) {
181-
try {
182-
let ended = false;
183-
184-
const endSpanOnce = (): void => {
185-
if (ended) return;
186-
187-
ended = true;
188-
span.end();
189-
waitUntil?.(flushAndDispose(client));
190-
};
191-
192-
const transform = new TransformStream({
193-
flush() {
194-
// Source stream completed normally.
195-
endSpanOnce();
196-
},
197-
cancel() {
198-
// Client disconnected (or downstream cancelled). The `cancel`
199-
// is being called while the response is still considered
200-
// active, so this is a safe place to end the span.
201-
endSpanOnce();
202-
},
203-
});
204-
205-
return new Response(res.body.pipeThrough(transform), {
206-
status: res.status,
207-
statusText: res.statusText,
208-
headers: res.headers,
209-
});
210-
} catch {
211-
span.end();
212-
waitUntil?.(flushAndDispose(client));
213-
return res;
214-
}
215-
}
216-
217-
// Non-streaming response - end span immediately and return original
218-
span.end();
219-
220-
// Don't dispose for protocol upgrades (101 Switching Protocols) - the connection stays alive.
221-
// This includes WebSocket upgrades where webSocketMessage/webSocketClose handlers
222-
// will still be called and may need the client to capture errors.
223-
if (res.status === 101) {
224-
waitUntil?.(client?.flush(2000));
225-
} else {
226-
waitUntil?.(flushAndDispose(client));
227-
}
228-
return res;
229-
});
230-
},
231-
);
232-
});
233-
}

0 commit comments

Comments
 (0)