Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/cloudflare/src/durableobject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { DurableObject } from 'cloudflare:workers';
import { setAsyncLocalStorageAsyncContextStrategy } from './async';
import type { CloudflareOptions } from './client';
import { isInstrumented, markAsInstrumented } from './instrument';
import { getFinalOptions } from './options';
import { wrapRequestHandler } from './request';
import { init } from './sdk';

Expand Down Expand Up @@ -140,7 +141,7 @@ export function instrumentDurableObjectWithSentry<E, T extends DurableObject<E>>
construct(target, [context, env]) {
setAsyncLocalStorageAsyncContextStrategy();

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

const obj = new target(context, env);

Expand Down
8 changes: 6 additions & 2 deletions packages/cloudflare/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { setAsyncLocalStorageAsyncContextStrategy } from './async';
import type { CloudflareOptions } from './client';
import { isInstrumented, markAsInstrumented } from './instrument';
import { getFinalOptions } from './options';
import { wrapRequestHandler } from './request';
import { addCloudResourceContext } from './scope-utils';
import { init } from './sdk';
Expand All @@ -35,7 +36,9 @@ export function withSentry<Env = unknown, QueueHandlerMessage = unknown, CfHostM
handler.fetch = new Proxy(handler.fetch, {
apply(target, thisArg, args: Parameters<ExportedHandlerFetchHandler<Env, CfHostMetadata>>) {
const [request, env, context] = args;
const options = optionsCallback(env);

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

return wrapRequestHandler({ options, request, context }, () => target.apply(thisArg, args));
},
});
Expand All @@ -48,7 +51,8 @@ export function withSentry<Env = unknown, QueueHandlerMessage = unknown, CfHostM
apply(target, thisArg, args: Parameters<ExportedHandlerScheduledHandler<Env>>) {
const [event, env, context] = args;
return withIsolationScope(isolationScope => {
const options = optionsCallback(env);
const options = getFinalOptions(optionsCallback(env), env);

const client = init(options);
isolationScope.setClient(client);

Expand Down
20 changes: 20 additions & 0 deletions packages/cloudflare/src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { CloudflareOptions } from './client';

/**
* Merges the options passed in from the user with the options we read from
* the Cloudflare `env` environment variable object.
*
* @param userOptions - The options passed in from the user.
* @param env - The environment variables.
*
* @returns The final options.
*/
export function getFinalOptions(userOptions: CloudflareOptions, env: unknown): CloudflareOptions {
if (typeof env !== 'object' || env === null) {
return userOptions;
}

const release = 'SENTRY_RELEASE' in env && typeof env.SENTRY_RELEASE === 'string' ? env.SENTRY_RELEASE : undefined;

return { release, ...userOptions };
}
109 changes: 109 additions & 0 deletions packages/cloudflare/test/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { withSentry } from '../src/handler';

const MOCK_ENV = {
SENTRY_DSN: 'https://[email protected]/1337',
SENTRY_RELEASE: '1.1.1',
};

describe('withSentry', () => {
Expand Down Expand Up @@ -51,6 +52,65 @@ describe('withSentry', () => {

expect(result).toBe(response);
});

test('merges options from env and callback', async () => {
const handler = {
fetch(_request, _env, _context) {
throw new Error('test');
},
} satisfies ExportedHandler<typeof MOCK_ENV>;

let sentryEvent: Event = {};

const wrappedHandler = withSentry(
env => ({
dsn: env.SENTRY_DSN,
beforeSend(event) {
sentryEvent = event;
return null;
},
}),
handler,
);

try {
await wrappedHandler.fetch(new Request('https://example.com'), MOCK_ENV, createMockExecutionContext());
} catch {
// ignore
}

expect(sentryEvent.release).toEqual('1.1.1');
});

test('callback options take precedence over env options', async () => {
const handler = {
fetch(_request, _env, _context) {
throw new Error('test');
},
} satisfies ExportedHandler<typeof MOCK_ENV>;

let sentryEvent: Event = {};

const wrappedHandler = withSentry(
env => ({
dsn: env.SENTRY_DSN,
release: '2.0.0',
beforeSend(event) {
sentryEvent = event;
return null;
},
}),
handler,
);

try {
await wrappedHandler.fetch(new Request('https://example.com'), MOCK_ENV, createMockExecutionContext());
} catch {
// ignore
}

expect(sentryEvent.release).toEqual('2.0.0');
});
});

describe('scheduled handler', () => {
Expand All @@ -70,6 +130,55 @@ describe('withSentry', () => {
expect(optionsCallback).toHaveBeenLastCalledWith(MOCK_ENV);
});

test('merges options from env and callback', async () => {
const handler = {
scheduled(_controller, _env, _context) {
SentryCore.captureMessage('cloud_resource');
return;
},
} satisfies ExportedHandler<typeof MOCK_ENV>;

let sentryEvent: Event = {};
const wrappedHandler = withSentry(
env => ({
dsn: env.SENTRY_DSN,
beforeSend(event) {
sentryEvent = event;
return null;
},
}),
handler,
);
await wrappedHandler.scheduled(createMockScheduledController(), MOCK_ENV, createMockExecutionContext());

expect(sentryEvent.release).toBe('1.1.1');
});

test('callback options take precedence over env options', async () => {
const handler = {
scheduled(_controller, _env, _context) {
SentryCore.captureMessage('cloud_resource');
return;
},
} satisfies ExportedHandler<typeof MOCK_ENV>;

let sentryEvent: Event = {};
const wrappedHandler = withSentry(
env => ({
dsn: env.SENTRY_DSN,
release: '2.0.0',
beforeSend(event) {
sentryEvent = event;
return null;
},
}),
handler,
);
await wrappedHandler.scheduled(createMockScheduledController(), MOCK_ENV, createMockExecutionContext());

expect(sentryEvent.release).toEqual('2.0.0');
});

test('flushes the event after the handler is done using the cloudflare context.waitUntil', async () => {
const handler = {
scheduled(_controller, _env, _context) {
Expand Down
58 changes: 58 additions & 0 deletions packages/cloudflare/test/options.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describe, expect, it } from 'vitest';
import { getFinalOptions } from '../src/options';

describe('getFinalOptions', () => {
it('returns user options when env is not an object', () => {
const userOptions = { dsn: 'test-dsn', release: 'test-release' };
const env = 'not-an-object';

const result = getFinalOptions(userOptions, env);

expect(result).toEqual(userOptions);
});

it('returns user options when env is null', () => {
const userOptions = { dsn: 'test-dsn', release: 'test-release' };
const env = null;

const result = getFinalOptions(userOptions, env);

expect(result).toEqual(userOptions);
});

it('merges options from env with user options', () => {
const userOptions = { dsn: 'test-dsn', release: 'user-release' };
const env = { SENTRY_RELEASE: 'env-release' };

const result = getFinalOptions(userOptions, env);

expect(result).toEqual({ dsn: 'test-dsn', release: 'user-release' });
});

it('uses user options when SENTRY_RELEASE exists but is not a string', () => {
const userOptions = { dsn: 'test-dsn', release: 'user-release' };
const env = { SENTRY_RELEASE: 123 };

const result = getFinalOptions(userOptions, env);

expect(result).toEqual(userOptions);
});

it('uses user options when SENTRY_RELEASE does not exist', () => {
const userOptions = { dsn: 'test-dsn', release: 'user-release' };
const env = { OTHER_VAR: 'some-value' };

const result = getFinalOptions(userOptions, env);

expect(result).toEqual(userOptions);
});

it('takes user options over env options', () => {
const userOptions = { dsn: 'test-dsn', release: 'user-release' };
const env = { SENTRY_RELEASE: 'env-release' };

const result = getFinalOptions(userOptions, env);

expect(result).toEqual(userOptions);
});
});
2 changes: 1 addition & 1 deletion packages/cloudflare/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.json",

"include": ["src/**/*"],
"include": ["src/**/*", "test/options.test.ts"],

"compilerOptions": {
"module": "esnext",
Expand Down
Loading