From cee7f0493f642861ed783f515fdaafdc3e178767 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Wed, 21 May 2025 14:31:54 +0200 Subject: [PATCH 1/3] fix(cloudflare): Capture exceptions thrown in hono --- packages/cloudflare/src/handler.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/cloudflare/src/handler.ts b/packages/cloudflare/src/handler.ts index 62956cff62cf..21f4e0fc37da 100644 --- a/packages/cloudflare/src/handler.ts +++ b/packages/cloudflare/src/handler.ts @@ -26,6 +26,7 @@ import { init } from './sdk'; * @param handler {ExportedHandler} The handler to wrap. * @returns The wrapped handler. */ +// eslint-disable-next-line complexity export function withSentry( optionsCallback: (env: Env) => CloudflareOptions, handler: ExportedHandler, @@ -47,6 +48,31 @@ export function withSentry>) { From d5a46ad5c7f444e391d96b8715c0130111de9887 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Wed, 21 May 2025 14:53:22 +0200 Subject: [PATCH 2/3] add tests --- packages/cloudflare/test/handler.test.ts | 91 ++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/packages/cloudflare/test/handler.test.ts b/packages/cloudflare/test/handler.test.ts index 6ae688f316f9..bced0fdbe277 100644 --- a/packages/cloudflare/test/handler.test.ts +++ b/packages/cloudflare/test/handler.test.ts @@ -7,6 +7,17 @@ import * as SentryCore from '@sentry/core'; import { beforeEach, describe, expect, test, vi } from 'vitest'; import { CloudflareClient } from '../src/client'; import { withSentry } from '../src/handler'; +import { markAsInstrumented } from '../src/instrument'; + +// Custom type for hono-like apps (cloudflare handlers) that include errorHandler and onError +type HonoLikeApp = ExportedHandler< + Env, + QueueHandlerMessage, + CfHostMetadata +> & { + onError?: () => void; + errorHandler?: (err: Error) => Response; +}; const MOCK_ENV = { SENTRY_DSN: 'https://public@dsn.ingest.sentry.io/1337', @@ -931,6 +942,86 @@ describe('withSentry', () => { }); }); }); + + describe('hono errorHandler', () => { + test('captures errors handled by the errorHandler', async () => { + const captureExceptionSpy = vi.spyOn(SentryCore, 'captureException'); + const error = new Error('test hono error'); + + const honoApp = { + fetch(_request, _env, _context) { + return new Response('test'); + }, + onError() {}, // hono-like onError + errorHandler(err: Error) { + return new Response(`Error: ${err.message}`, { status: 500 }); + }, + } satisfies HonoLikeApp; + + withSentry(env => ({ dsn: env.SENTRY_DSN }), honoApp); + + // simulates hono's error handling + const errorHandlerResponse = honoApp.errorHandler?.(error); + + expect(captureExceptionSpy).toHaveBeenCalledTimes(1); + expect(captureExceptionSpy).toHaveBeenLastCalledWith(error, { + mechanism: { handled: false, type: 'cloudflare' }, + }); + expect(errorHandlerResponse?.status).toBe(500); + }); + + test('preserves the original errorHandler functionality', async () => { + const originalErrorHandlerSpy = vi.fn().mockImplementation((err: Error) => { + return new Response(`Error: ${err.message}`, { status: 500 }); + }); + + const error = new Error('test hono error'); + + const honoApp = { + fetch(_request, _env, _context) { + return new Response('test'); + }, + onError() {}, // hono-like onError + errorHandler: originalErrorHandlerSpy, + } satisfies HonoLikeApp; + + withSentry(env => ({ dsn: env.SENTRY_DSN }), honoApp); + + // Call the errorHandler directly to simulate Hono's error handling + const errorHandlerResponse = honoApp.errorHandler?.(error); + + expect(originalErrorHandlerSpy).toHaveBeenCalledTimes(1); + expect(originalErrorHandlerSpy).toHaveBeenLastCalledWith(error); + expect(errorHandlerResponse?.status).toBe(500); + }); + + test('does not instrument an already instrumented errorHandler', async () => { + const captureExceptionSpy = vi.spyOn(SentryCore, 'captureException'); + const error = new Error('test hono error'); + + // Create a handler with an errorHandler that's already been instrumented + const originalErrorHandler = (err: Error) => { + return new Response(`Error: ${err.message}`, { status: 500 }); + }; + + // Mark as instrumented before wrapping + markAsInstrumented(originalErrorHandler); + + const honoApp = { + fetch(_request, _env, _context) { + return new Response('test'); + }, + onError() {}, // hono-like onError + errorHandler: originalErrorHandler, + } satisfies HonoLikeApp; + + withSentry(env => ({ dsn: env.SENTRY_DSN }), honoApp); + + // The errorHandler should not have been wrapped again + honoApp.errorHandler?.(error); + expect(captureExceptionSpy).not.toHaveBeenCalled(); + }); + }); }); function createMockExecutionContext(): ExecutionContext { From 05e0f29b96ac64d03e4f5629cd293b59cd92bae5 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Wed, 21 May 2025 16:01:07 +0200 Subject: [PATCH 3/3] small formatting change --- packages/cloudflare/src/handler.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/cloudflare/src/handler.ts b/packages/cloudflare/src/handler.ts index 21f4e0fc37da..d3d1f80dbbd5 100644 --- a/packages/cloudflare/src/handler.ts +++ b/packages/cloudflare/src/handler.ts @@ -59,12 +59,7 @@ export function withSentry