From ac6b554f6c4fca4ef290243cfd4c20499daefdc6 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 16 Mar 2026 13:56:12 +0100 Subject: [PATCH 1/4] export cloudflare --- packages/hono/src/index.cloudflare.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/hono/src/index.cloudflare.ts b/packages/hono/src/index.cloudflare.ts index cba517e1d295..99c04597a98f 100644 --- a/packages/hono/src/index.cloudflare.ts +++ b/packages/hono/src/index.cloudflare.ts @@ -1 +1,3 @@ export { sentry } from './cloudflare/middleware'; + +export * from '@sentry/cloudflare'; From 773fea22fefe37fcac95ab66131cfb59b186d717 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:57:12 +0100 Subject: [PATCH 2/4] fix(hono): Allow passing `env` and fix type issues --- packages/hono/README.md | 18 ++++++ packages/hono/src/cloudflare/middleware.ts | 63 ++++++++++--------- packages/hono/src/shared/patchAppUse.ts | 4 +- .../hono/test/cloudflare/middleware.test.ts | 26 +++----- 4 files changed, 64 insertions(+), 47 deletions(-) diff --git a/packages/hono/README.md b/packages/hono/README.md index cc5edbd30a02..b1b9e07760f9 100644 --- a/packages/hono/README.md +++ b/packages/hono/README.md @@ -48,6 +48,7 @@ compatibility_flags = ["nodejs_compat"] Initialize the Sentry Hono middleware as early as possible in your app: ```typescript +import { Hono } from 'hono'; import { sentry } from '@sentry/hono/cloudflare'; const app = new Hono(); @@ -64,3 +65,20 @@ app.use( export default app; ``` + +#### Access `env` from Cloudflare Worker bindings + +Pass the options as a callback instead of a plain options object. The function receives the Cloudflare Worker `env` as defined in the Worker's `Bindings`: + +```typescript +import { Hono } from 'hono'; +import { sentry } from '@sentry/hono/cloudflare'; + +type Bindings = { SENTRY_DSN: string }; + +const app = new Hono<{ Bindings: Bindings }>(); + +app.use(sentry(app, env => ({ dsn: env.SENTRY_DSN }))); + +export default app; +``` diff --git a/packages/hono/src/cloudflare/middleware.ts b/packages/hono/src/cloudflare/middleware.ts index ffcdf5e40346..76d571d2cda7 100644 --- a/packages/hono/src/cloudflare/middleware.ts +++ b/packages/hono/src/cloudflare/middleware.ts @@ -7,40 +7,47 @@ import { type Integration, type Options, } from '@sentry/core'; -import type { Context, Hono, MiddlewareHandler } from 'hono'; +import type { Env, Hono, MiddlewareHandler } from 'hono'; import { requestHandler, responseHandler } from '../shared/middlewareHandlers'; import { patchAppUse } from '../shared/patchAppUse'; -export interface HonoOptions extends Options { - context?: Context; -} +export interface HonoOptions extends Options {} const filterHonoIntegration = (integration: Integration): boolean => integration.name !== 'Hono'; -export const sentry = (app: Hono, options: HonoOptions | undefined = {}): MiddlewareHandler => { - const isDebug = options.debug; - - isDebug && debug.log('Initialized Sentry Hono middleware (Cloudflare)'); - - applySdkMetadata(options, 'hono'); - - const { integrations: userIntegrations } = options; +/** + * Sentry middleware for Hono on Cloudflare Workers. + */ +export function sentry( + app: Hono, + options: HonoOptions | ((env: E['Bindings']) => HonoOptions), +): MiddlewareHandler { withSentry( - () => ({ - ...options, - // Always filter out the Hono integration from defaults and user integrations. - // The Hono integration is already set up by withSentry, so adding it again would cause capturing too early (in Cloudflare SDK) and non-parametrized URLs. - integrations: Array.isArray(userIntegrations) - ? defaults => - getIntegrationsToSetup({ - defaultIntegrations: defaults.filter(filterHonoIntegration), - integrations: userIntegrations.filter(filterHonoIntegration), - }) - : typeof userIntegrations === 'function' - ? defaults => userIntegrations(defaults).filter(filterHonoIntegration) - : defaults => defaults.filter(filterHonoIntegration), - }), - app, + env => { + const honoOptions = typeof options === 'function' ? options(env as E['Bindings']) : options; + + applySdkMetadata(honoOptions, 'hono', ['hono', 'cloudflare']); + + honoOptions.debug && debug.log('Initialized Sentry Hono middleware (Cloudflare)'); + + const { integrations: userIntegrations } = honoOptions; + return { + ...honoOptions, + // Always filter out the Hono integration from defaults and user integrations. + // The Hono integration is already set up by withSentry, so adding it again would cause capturing too early (in Cloudflare SDK) and non-parametrized URLs. + integrations: Array.isArray(userIntegrations) + ? defaults => + getIntegrationsToSetup({ + defaultIntegrations: defaults.filter(filterHonoIntegration), + integrations: userIntegrations.filter(filterHonoIntegration), + }) + : typeof userIntegrations === 'function' + ? defaults => userIntegrations(defaults).filter(filterHonoIntegration) + : defaults => defaults.filter(filterHonoIntegration), + }; + }, + // Cast needed because Hono exposes a narrower fetch signature than ExportedHandler + app as unknown as ExportedHandler, ); patchAppUse(app); @@ -52,4 +59,4 @@ export const sentry = (app: Hono, options: HonoOptions | undefined = {}): Middle responseHandler(context); }; -}; +} diff --git a/packages/hono/src/shared/patchAppUse.ts b/packages/hono/src/shared/patchAppUse.ts index dfcd186dc38a..28c3c49e7193 100644 --- a/packages/hono/src/shared/patchAppUse.ts +++ b/packages/hono/src/shared/patchAppUse.ts @@ -6,7 +6,7 @@ import { SPAN_STATUS_OK, startInactiveSpan, } from '@sentry/core'; -import type { Hono, MiddlewareHandler } from 'hono'; +import type { Env, Hono, MiddlewareHandler } from 'hono'; const MIDDLEWARE_ORIGIN = 'auto.middleware.hono'; @@ -14,7 +14,7 @@ const MIDDLEWARE_ORIGIN = 'auto.middleware.hono'; * Patches `app.use` so that every middleware registered through it is automatically * wrapped in a Sentry span. Supports both forms: `app.use(...handlers)` and `app.use(path, ...handlers)`. */ -export function patchAppUse(app: Hono): void { +export function patchAppUse(app: Hono): void { app.use = new Proxy(app.use, { apply(target: typeof app.use, thisArg: typeof app, args: Parameters): ReturnType { const [first, ...rest] = args as [unknown, ...MiddlewareHandler[]]; diff --git a/packages/hono/test/cloudflare/middleware.test.ts b/packages/hono/test/cloudflare/middleware.test.ts index 08629d706e8b..f46192b0ac87 100644 --- a/packages/hono/test/cloudflare/middleware.test.ts +++ b/packages/hono/test/cloudflare/middleware.test.ts @@ -25,7 +25,7 @@ describe('Hono Cloudflare Middleware', () => { }); describe('sentry middleware', () => { - it('calls applySdkMetadata with "hono"', () => { + it('calls applySdkMetadata with "hono" when the options callback is invoked', () => { const app = new Hono(); const options = { dsn: 'https://public@dsn.ingest.sentry.io/1337', @@ -33,8 +33,11 @@ describe('Hono Cloudflare Middleware', () => { sentry(app, options); + const optionsCallback = withSentryMock.mock.calls[0]?.[0]; + optionsCallback(); + expect(applySdkMetadataMock).toHaveBeenCalledTimes(1); - expect(applySdkMetadataMock).toHaveBeenCalledWith(options, 'hono'); + expect(applySdkMetadataMock).toHaveBeenCalledWith(options, 'hono', ['hono', 'cloudflare']); }); it('calls withSentry with modified options', () => { @@ -63,24 +66,13 @@ describe('Hono Cloudflare Middleware', () => { name: 'npm:@sentry/hono', version: SDK_VERSION, }, + { + name: 'npm:@sentry/cloudflare', + version: SDK_VERSION, + }, ]); }); - it('calls applySdkMetadata before withSentry', () => { - const app = new Hono(); - const options = { - dsn: 'https://public@dsn.ingest.sentry.io/1337', - }; - - sentry(app, options); - - // Verify applySdkMetadata was called before withSentry - const applySdkMetadataCallOrder = applySdkMetadataMock.mock.invocationCallOrder[0]; - const withSentryCallOrder = withSentryMock.mock.invocationCallOrder[0]; - - expect(applySdkMetadataCallOrder).toBeLessThan(withSentryCallOrder as number); - }); - it('preserves all user options', () => { const app = new Hono(); const options = { From 5aeae6782599f546e8f9a42d46beaf2e429c78e1 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 16 Mar 2026 17:21:56 +0100 Subject: [PATCH 3/4] fix test --- dev-packages/cloudflare-integration-tests/expect.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-packages/cloudflare-integration-tests/expect.ts b/dev-packages/cloudflare-integration-tests/expect.ts index b33926ffce11..c3e2bd007436 100644 --- a/dev-packages/cloudflare-integration-tests/expect.ts +++ b/dev-packages/cloudflare-integration-tests/expect.ts @@ -28,6 +28,7 @@ function getSdk(sdk: 'cloudflare' | 'hono'): SdkInfo { name: `npm:@sentry/${sdk}`, version: SDK_VERSION, }, + ...(sdk === 'hono' ? [{ name: 'npm:@sentry/cloudflare', version: SDK_VERSION }] : []), ], version: SDK_VERSION, }; From 41548fb97ee069cf3509524bb6d8cb482489b248 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 17 Mar 2026 09:34:39 +0100 Subject: [PATCH 4/4] add tests --- .../hono/test/cloudflare/middleware.test.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packages/hono/test/cloudflare/middleware.test.ts b/packages/hono/test/cloudflare/middleware.test.ts index f46192b0ac87..ac512d41afee 100644 --- a/packages/hono/test/cloudflare/middleware.test.ts +++ b/packages/hono/test/cloudflare/middleware.test.ts @@ -116,6 +116,53 @@ describe('Hono Cloudflare Middleware', () => { expect(middleware.constructor.name).toBe('AsyncFunction'); }); + + describe('when options is a function (env callback)', () => { + it('calls the options function with the env argument passed by withSentry', () => { + type Bindings = { SENTRY_DSN: string }; + const app = new Hono<{ Bindings: Bindings }>(); + const mockEnv: Bindings = { SENTRY_DSN: 'https://public@dsn.ingest.sentry.io/1337' }; + const optionsFn = vi.fn((env: Bindings) => ({ dsn: env.SENTRY_DSN })); + + sentry(app, optionsFn); + + const optionsCallback = withSentryMock.mock.calls[0]?.[0]; + optionsCallback(mockEnv); + + expect(optionsFn).toHaveBeenCalledTimes(1); + expect(optionsFn).toHaveBeenCalledWith(mockEnv); + }); + + it('uses the return value of the options function as configuration', () => { + type Bindings = { SENTRY_DSN: string }; + const app = new Hono<{ Bindings: Bindings }>(); + const mockEnv: Bindings = { SENTRY_DSN: 'https://public@dsn.ingest.sentry.io/1337' }; + + sentry(app, (env: Bindings) => ({ dsn: env.SENTRY_DSN, environment: 'production' })); + + const optionsCallback = withSentryMock.mock.calls[0]?.[0]; + const result = optionsCallback(mockEnv); + + expect(result.dsn).toBe('https://public@dsn.ingest.sentry.io/1337'); + expect(result.environment).toBe('production'); + }); + + it('calls applySdkMetadata with the options object returned by the function', () => { + type Bindings = { SENTRY_DSN: string }; + const app = new Hono<{ Bindings: Bindings }>(); + const mockEnv: Bindings = { SENTRY_DSN: 'https://public@dsn.ingest.sentry.io/1337' }; + const returnedOptions = { dsn: 'https://public@dsn.ingest.sentry.io/1337' }; + const optionsFn = vi.fn(() => returnedOptions); + + sentry(app, optionsFn); + + const optionsCallback = withSentryMock.mock.calls[0]?.[0]; + optionsCallback(mockEnv); + + expect(applySdkMetadataMock).toHaveBeenCalledTimes(1); + expect(applySdkMetadataMock).toHaveBeenCalledWith(returnedOptions, 'hono', ['hono', 'cloudflare']); + }); + }); }); describe('filters Hono integration from user-provided integrations', () => {