Skip to content

Commit

Permalink
feat(cloudflare): Allow users to pass handler to sentryPagesPlugin (#…
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
AbhiPrasad authored Aug 2, 2024
1 parent d996c22 commit 07a30e5
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 5 deletions.
31 changes: 31 additions & 0 deletions packages/cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,37 @@ export const onRequest = [
];
```

If you need to access the `context` object (for example to grab environmental variables), you can pass a function to
`sentryPagesPlugin` that takes the `context` object as an argument and returns `init` options:

```javascript
export const onRequest = Sentry.sentryPagesPlugin(context => ({
dsn: context.env.SENTRY_DSN,
tracesSampleRate: 1.0,
}));
```

If you do not have access to the `onRequest` middleware API, you can use the `wrapRequestHandler` API instead.

Here is an example with SvelteKit:

```javascript
// hooks.server.js
import * as Sentry from '@sentry/cloudflare';

export const handle = ({ event, resolve }) => {
const requestHandlerOptions = {
options: {
dsn: event.platform.env.SENTRY_DSN,
tracesSampleRate: 1.0,
},
request: event.request,
context: event.platform.ctx,
};
return Sentry.wrapRequestHandler(requestHandlerOptions, () => resolve(event));
};
```

## Setup (Cloudflare Workers)

To use this SDK, wrap your handler with the `withSentry` function. This will initialize the SDK and hook into the
Expand Down
2 changes: 2 additions & 0 deletions packages/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export {
export { withSentry } from './handler';
export { sentryPagesPlugin } from './pages-plugin';

export { wrapRequestHandler } from './request';

export { CloudflareClient } from './client';
export { getDefaultIntegrations } from './sdk';

Expand Down
35 changes: 30 additions & 5 deletions packages/cloudflare/src/pages-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,48 @@ import { wrapRequestHandler } from './request';
*
* Initializes the SDK and wraps cloudflare pages requests with SDK instrumentation.
*
* @example
* @example Simple usage
*
* ```javascript
* // functions/_middleware.js
* import * as Sentry from '@sentry/cloudflare';
*
* export const onRequest = Sentry.sentryPagesPlugin({
* dsn: process.env.SENTRY_DSN,
* tracesSampleRate: 1.0,
* dsn: process.env.SENTRY_DSN,
* tracesSampleRate: 1.0,
* });
* ```
*
* @example Usage with handler function to access context for environmental variables
*
* ```javascript
* import * as Sentry from '@sentry/cloudflare';
*
* const const onRequest = Sentry.sentryPagesPlugin((context) => ({
* dsn: context.env.SENTRY_DSN,
* tracesSampleRate: 1.0,
* })
* ```
*
* @param handlerOrOptions Configuration options or a function that returns configuration options.
* @returns A plugin function that can be used in Cloudflare Pages.
*/
export function sentryPagesPlugin<
Env = unknown,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Params extends string = any,
Data extends Record<string, unknown> = Record<string, unknown>,
>(options: CloudflareOptions): PagesPluginFunction<Env, Params, Data, CloudflareOptions> {
// Although it is not ideal to use `any` here, it makes usage more flexible for different setups.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
PluginParams = any,
>(
handlerOrOptions:
| CloudflareOptions
| ((context: EventPluginContext<Env, Params, Data, PluginParams>) => CloudflareOptions),
): PagesPluginFunction<Env, Params, Data, PluginParams> {
setAsyncLocalStorageAsyncContextStrategy();
return context => wrapRequestHandler({ options, request: context.request, context }, () => context.next());
return context => {
const options = typeof handlerOrOptions === 'function' ? handlerOrOptions(context) : handlerOrOptions;
return wrapRequestHandler({ options, request: context.request, context }, () => context.next());
};
}
22 changes: 22 additions & 0 deletions packages/cloudflare/test/pages-plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,28 @@ describe('sentryPagesPlugin', () => {
vi.clearAllMocks();
});

test('calls handler function if a function is provided', async () => {
const mockOptionsHandler = vi.fn().mockReturnValue(MOCK_OPTIONS);
const mockOnRequest = sentryPagesPlugin(mockOptionsHandler);

const MOCK_CONTEXT = {
request: new Request('https://example.com'),
functionPath: 'test',
waitUntil: vi.fn(),
passThroughOnException: vi.fn(),
next: () => Promise.resolve(new Response('test')),
env: { ASSETS: { fetch: vi.fn() } },
params: {},
data: {},
pluginArgs: MOCK_OPTIONS,
};

await mockOnRequest(MOCK_CONTEXT);

expect(mockOptionsHandler).toHaveBeenCalledTimes(1);
expect(mockOptionsHandler).toHaveBeenLastCalledWith(MOCK_CONTEXT);
});

test('passes through the response from the handler', async () => {
const response = new Response('test');
const mockOnRequest = sentryPagesPlugin(MOCK_OPTIONS);
Expand Down

0 comments on commit 07a30e5

Please sign in to comment.