Skip to content

Commit 79a0430

Browse files
authored
ref(cloudflare)!: Remove wrapRequestHandlerWithInit as export (#23322)
This was added by accident in the first v11 alpha. The `/request` entrypoint should only export `wrapRequestHandler` and nothing else. This PR only moves the export in its own file
1 parent f228ee4 commit 79a0430

5 files changed

Lines changed: 221 additions & 219 deletions

File tree

packages/cloudflare/src/durableobject.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { CloudflareOptions } from './client';
77
import { ensureInstrumented, getInstrumented, markAsInstrumented } from './instrument';
88
import { instrumentEnv } from './instrumentations/worker/instrumentEnv';
99
import { getFinalOptions } from './options';
10-
import { wrapRequestHandlerWithInit } from './request';
10+
import { wrapRequestHandlerWithInit } from './wrapRequestHandlerWithInit';
1111
import { init } from './sdk';
1212
import { instrumentContext } from './utils/instrumentContext';
1313
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
import type { StrictCloudflareOptions } from './types';
77

packages/cloudflare/src/request.ts

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

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

0 commit comments

Comments
 (0)