Skip to content

Commit 07a30e5

Browse files
authored
feat(cloudflare): Allow users to pass handler to sentryPagesPlugin (#13192)
While working on adding the cloudflare sdk to some open source projects, I noticed that setup for the cloudflare plugin was a bit of a hassle when you needed access to environmental variables. This PR allows users to pass a function to `sentryPagesPlugin` that looks like so: ```ts handler: (context: EventPluginContext<Env, Params, Data, PluginParams>) => CloudflareOptions ``` This means that users can access the cloudflare `context` (which only exists at the request level) to get environmental variables. ```javascript export const onRequest = Sentry.sentryPagesPlugin(context => ({ dsn: context.env.SENTRY_DSN, tracesSampleRate: 1.0, })); ``` To make some other use cases easier, this PR also exposes the `wrapRequestHandler` API to users.
1 parent d996c22 commit 07a30e5

File tree

4 files changed

+85
-5
lines changed

4 files changed

+85
-5
lines changed

packages/cloudflare/README.md

+31
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,37 @@ export const onRequest = [
7272
];
7373
```
7474

75+
If you need to access the `context` object (for example to grab environmental variables), you can pass a function to
76+
`sentryPagesPlugin` that takes the `context` object as an argument and returns `init` options:
77+
78+
```javascript
79+
export const onRequest = Sentry.sentryPagesPlugin(context => ({
80+
dsn: context.env.SENTRY_DSN,
81+
tracesSampleRate: 1.0,
82+
}));
83+
```
84+
85+
If you do not have access to the `onRequest` middleware API, you can use the `wrapRequestHandler` API instead.
86+
87+
Here is an example with SvelteKit:
88+
89+
```javascript
90+
// hooks.server.js
91+
import * as Sentry from '@sentry/cloudflare';
92+
93+
export const handle = ({ event, resolve }) => {
94+
const requestHandlerOptions = {
95+
options: {
96+
dsn: event.platform.env.SENTRY_DSN,
97+
tracesSampleRate: 1.0,
98+
},
99+
request: event.request,
100+
context: event.platform.ctx,
101+
};
102+
return Sentry.wrapRequestHandler(requestHandlerOptions, () => resolve(event));
103+
};
104+
```
105+
75106
## Setup (Cloudflare Workers)
76107

77108
To use this SDK, wrap your handler with the `withSentry` function. This will initialize the SDK and hook into the

packages/cloudflare/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ export {
8888
export { withSentry } from './handler';
8989
export { sentryPagesPlugin } from './pages-plugin';
9090

91+
export { wrapRequestHandler } from './request';
92+
9193
export { CloudflareClient } from './client';
9294
export { getDefaultIntegrations } from './sdk';
9395

packages/cloudflare/src/pages-plugin.ts

+30-5
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,48 @@ import { wrapRequestHandler } from './request';
77
*
88
* Initializes the SDK and wraps cloudflare pages requests with SDK instrumentation.
99
*
10-
* @example
10+
* @example Simple usage
11+
*
1112
* ```javascript
1213
* // functions/_middleware.js
1314
* import * as Sentry from '@sentry/cloudflare';
1415
*
1516
* export const onRequest = Sentry.sentryPagesPlugin({
16-
* dsn: process.env.SENTRY_DSN,
17-
* tracesSampleRate: 1.0,
17+
* dsn: process.env.SENTRY_DSN,
18+
* tracesSampleRate: 1.0,
1819
* });
1920
* ```
21+
*
22+
* @example Usage with handler function to access context for environmental variables
23+
*
24+
* ```javascript
25+
* import * as Sentry from '@sentry/cloudflare';
26+
*
27+
* const const onRequest = Sentry.sentryPagesPlugin((context) => ({
28+
* dsn: context.env.SENTRY_DSN,
29+
* tracesSampleRate: 1.0,
30+
* })
31+
* ```
32+
*
33+
* @param handlerOrOptions Configuration options or a function that returns configuration options.
34+
* @returns A plugin function that can be used in Cloudflare Pages.
2035
*/
2136
export function sentryPagesPlugin<
2237
Env = unknown,
2338
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2439
Params extends string = any,
2540
Data extends Record<string, unknown> = Record<string, unknown>,
26-
>(options: CloudflareOptions): PagesPluginFunction<Env, Params, Data, CloudflareOptions> {
41+
// Although it is not ideal to use `any` here, it makes usage more flexible for different setups.
42+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
43+
PluginParams = any,
44+
>(
45+
handlerOrOptions:
46+
| CloudflareOptions
47+
| ((context: EventPluginContext<Env, Params, Data, PluginParams>) => CloudflareOptions),
48+
): PagesPluginFunction<Env, Params, Data, PluginParams> {
2749
setAsyncLocalStorageAsyncContextStrategy();
28-
return context => wrapRequestHandler({ options, request: context.request, context }, () => context.next());
50+
return context => {
51+
const options = typeof handlerOrOptions === 'function' ? handlerOrOptions(context) : handlerOrOptions;
52+
return wrapRequestHandler({ options, request: context.request, context }, () => context.next());
53+
};
2954
}

packages/cloudflare/test/pages-plugin.test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,28 @@ describe('sentryPagesPlugin', () => {
1515
vi.clearAllMocks();
1616
});
1717

18+
test('calls handler function if a function is provided', async () => {
19+
const mockOptionsHandler = vi.fn().mockReturnValue(MOCK_OPTIONS);
20+
const mockOnRequest = sentryPagesPlugin(mockOptionsHandler);
21+
22+
const MOCK_CONTEXT = {
23+
request: new Request('https://example.com'),
24+
functionPath: 'test',
25+
waitUntil: vi.fn(),
26+
passThroughOnException: vi.fn(),
27+
next: () => Promise.resolve(new Response('test')),
28+
env: { ASSETS: { fetch: vi.fn() } },
29+
params: {},
30+
data: {},
31+
pluginArgs: MOCK_OPTIONS,
32+
};
33+
34+
await mockOnRequest(MOCK_CONTEXT);
35+
36+
expect(mockOptionsHandler).toHaveBeenCalledTimes(1);
37+
expect(mockOptionsHandler).toHaveBeenLastCalledWith(MOCK_CONTEXT);
38+
});
39+
1840
test('passes through the response from the handler', async () => {
1941
const response = new Response('test');
2042
const mockOnRequest = sentryPagesPlugin(MOCK_OPTIONS);

0 commit comments

Comments
 (0)