From 42b1beac8ccdfffd176cd45a53e844c8b22b2758 Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Tue, 25 Aug 2026 15:12:39 -0700 Subject: [PATCH 1/2] perf(hono): Skip throwing request event probe Hono's event getter throws for ordinary requests, allocating an Error each time request metadata is initialized. The raw request is already the same original Request in every supported runtime, including legacy FetchEvent contexts. Use req.raw directly and remove the compatibility probe. Co-Authored-By: OpenAI Codex --- .../hono/src/shared/middlewareHandlers.ts | 3 +- packages/hono/src/utils/hono-context.ts | 15 ------ .../test/shared/middlewareHandlers.test.ts | 49 +++++++++++++++++-- 3 files changed, 46 insertions(+), 21 deletions(-) delete mode 100644 packages/hono/src/utils/hono-context.ts diff --git a/packages/hono/src/shared/middlewareHandlers.ts b/packages/hono/src/shared/middlewareHandlers.ts index ffba879603ea..0eddae5e81e0 100644 --- a/packages/hono/src/shared/middlewareHandlers.ts +++ b/packages/hono/src/shared/middlewareHandlers.ts @@ -11,7 +11,6 @@ import { winterCGRequestToRequestData, } from '@sentry/core'; import type { Context } from 'hono'; -import { hasFetchEvent } from '../utils/hono-context'; import { defaultShouldHandleError } from './defaultShouldHandleError'; import { resolveRouteName } from './resolveRouteName'; import { type SentryHonoMiddlewareOptions } from '../shared/types'; @@ -29,7 +28,7 @@ export function requestHandler(context: Context, getConnInfo?: GetConnInfo): voi updateSpanRouteName(isolationScope, context); isolationScope.setSDKProcessingMetadata({ - normalizedRequest: winterCGRequestToRequestData(hasFetchEvent(context) ? context.event.request : context.req.raw), + normalizedRequest: winterCGRequestToRequestData(context.req.raw), }); if (getConnInfo) { diff --git a/packages/hono/src/utils/hono-context.ts b/packages/hono/src/utils/hono-context.ts deleted file mode 100644 index 96df44ee655a..000000000000 --- a/packages/hono/src/utils/hono-context.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Context } from 'hono'; - -/** - * Checks whether the given Hono context has a fetch event. - */ -export function hasFetchEvent(c: Context): boolean { - let hasFetchEvent = true; - try { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - c.event; - } catch { - hasFetchEvent = false; - } - return hasFetchEvent; -} diff --git a/packages/hono/test/shared/middlewareHandlers.test.ts b/packages/hono/test/shared/middlewareHandlers.test.ts index c52e4f62eb69..7534e577c50f 100644 --- a/packages/hono/test/shared/middlewareHandlers.test.ts +++ b/packages/hono/test/shared/middlewareHandlers.test.ts @@ -1,5 +1,6 @@ import * as SentryCore from '@sentry/core'; import { SENTRY_SEGMENT_NAME_SOURCE, HTTP_ROUTE } from '@sentry/conventions/attributes'; +import { Context } from 'hono'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { requestHandler, responseHandler } from '../../src/shared/middlewareHandlers'; @@ -8,10 +9,6 @@ vi.mock('hono/route', () => ({ matchedRoutes: () => [{ basePath: '/', path: '/test', method: 'GET', handler: (_c: unknown) => undefined }], })); -vi.mock('../../src/utils/hono-context', () => ({ - hasFetchEvent: () => false, -})); - const mockSetTransactionName = vi.fn(); const mockSetSDKProcessingMetadata = vi.fn(); const mockSetUser = vi.fn(); @@ -44,12 +41,14 @@ vi.mock('@sentry/core', async () => { })), getClient: vi.fn(() => undefined), captureException: vi.fn(), + winterCGRequestToRequestData: vi.fn(actual.winterCGRequestToRequestData), }; }); const getClientMock = SentryCore.getClient as ReturnType; const captureExceptionMock = SentryCore.captureException as ReturnType; const getActiveSpanMock = SentryCore.getActiveSpan as ReturnType; +const winterCGRequestToRequestDataMock = SentryCore.winterCGRequestToRequestData as ReturnType; function createMockContext(status: number, error?: Error): unknown { return { @@ -332,3 +331,45 @@ describe('requestHandler — connection info', () => { expect(mockSetUser).not.toHaveBeenCalled(); }); }); + +describe('requestHandler — request metadata', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('uses the raw request without reading the event getter', () => { + const raw = new Request('http://localhost/test?source=raw'); + const eventGetter = vi.fn(() => { + throw new Error('This context has no FetchEvent'); + }); + const context = createMockContext(200) as ReturnType & { + req: { raw: Request }; + }; + context.req.raw = raw; + Object.defineProperty(context, 'event', { get: eventGetter }); + + // oxlint-disable-next-line typescript/no-explicit-any + requestHandler(context as any); + + expect(winterCGRequestToRequestDataMock).toHaveBeenCalledWith(raw); + expect(eventGetter).not.toHaveBeenCalled(); + }); + + it('uses the same request exposed by a legacy FetchEvent context', () => { + const request = new Request('http://localhost/test?source=event'); + const context = new Context(request, { + env: {}, + executionCtx: { + request, + respondWith: vi.fn(), + passThroughOnException: vi.fn(), + waitUntil: vi.fn(), + }, + }); + + requestHandler(context); + + expect(context.event.request).toBe(context.req.raw); + expect(winterCGRequestToRequestDataMock).toHaveBeenCalledWith(request); + }); +}); From 429c8c913169cc303751e6f98bb92c18efa6f7ea Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Tue, 25 Aug 2026 15:49:38 -0700 Subject: [PATCH 2/2] test(hono): Reuse request handler coverage Fold the raw request assertion into the existing request handler test. Keep one focused regression test for the legacy FetchEvent compatibility path. Co-Authored-By: OpenAI Codex --- .../test/shared/middlewareHandlers.test.ts | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/packages/hono/test/shared/middlewareHandlers.test.ts b/packages/hono/test/shared/middlewareHandlers.test.ts index 7534e577c50f..0f406192ea8d 100644 --- a/packages/hono/test/shared/middlewareHandlers.test.ts +++ b/packages/hono/test/shared/middlewareHandlers.test.ts @@ -184,11 +184,21 @@ describe('responseHandler', () => { }); describe('transaction name', () => { - it('sets transaction name on isolation scope', () => { + it('sets request metadata and transaction name without reading the event', () => { + const context = createMockContext(200) as ReturnType & { + req: { raw: Request }; + }; + const eventGetter = vi.fn(() => { + throw new Error('This context has no FetchEvent'); + }); + Object.defineProperty(context, 'event', { get: eventGetter }); + // oxlint-disable-next-line typescript/no-explicit-any - requestHandler(createMockContext(200) as any); + requestHandler(context as any); expect(mockSetTransactionName).toHaveBeenCalledWith('GET /test'); + expect(winterCGRequestToRequestDataMock).toHaveBeenCalledWith(context.req.raw); + expect(eventGetter).not.toHaveBeenCalled(); }); it('sets http.route and segment name source on the root span', () => { @@ -337,24 +347,6 @@ describe('requestHandler — request metadata', () => { vi.clearAllMocks(); }); - it('uses the raw request without reading the event getter', () => { - const raw = new Request('http://localhost/test?source=raw'); - const eventGetter = vi.fn(() => { - throw new Error('This context has no FetchEvent'); - }); - const context = createMockContext(200) as ReturnType & { - req: { raw: Request }; - }; - context.req.raw = raw; - Object.defineProperty(context, 'event', { get: eventGetter }); - - // oxlint-disable-next-line typescript/no-explicit-any - requestHandler(context as any); - - expect(winterCGRequestToRequestDataMock).toHaveBeenCalledWith(raw); - expect(eventGetter).not.toHaveBeenCalled(); - }); - it('uses the same request exposed by a legacy FetchEvent context', () => { const request = new Request('http://localhost/test?source=event'); const context = new Context(request, {