From 9034d083542f9a4cb06d24a9d29ef1407b1f68d6 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 22 Jul 2026 10:40:29 +0200 Subject: [PATCH] feat(v11): Remove deprecated `honoIntegration` Removes the deprecated `honoIntegration` (and the node-only `setupHonoErrorHandler` helper) from `@sentry/node` and `@sentry/cloudflare`, along with its re-exports, tests, dead helpers, and the e2e app that only exercised the old path. Hono is now instrumented via the dedicated `@sentry/hono` SDK. Fixes #21755 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../suites/hono-integration/index.ts | 33 --- .../suites/hono-integration/test.ts | 54 ---- .../suites/hono-integration/wrangler.jsonc | 6 - .../cloudflare-hono/package.json | 31 --- .../cloudflare-hono/src/env.d.ts | 6 - .../cloudflare-hono/src/index.ts | 34 --- .../cloudflare-hono/test/env.d.ts | 4 - .../cloudflare-hono/test/index.test.ts | 74 ----- .../cloudflare-hono/test/tsconfig.json | 8 - .../cloudflare-hono/tsconfig.json | 15 -- .../cloudflare-hono/vitest.config.ts | 12 - .../cloudflare-hono/wrangler.toml | 7 - .../suites/tracing/hono/instrument.mjs | 9 - .../suites/tracing/hono/scenario.mjs | 196 -------------- .../suites/tracing/hono/test.ts | 137 ---------- packages/astro/src/index.server.ts | 4 - packages/aws-serverless/src/index.ts | 4 - packages/bun/src/index.ts | 4 - packages/cloudflare/src/index.ts | 2 - packages/cloudflare/src/integrations/hono.ts | 108 -------- packages/cloudflare/src/sdk.ts | 3 - packages/cloudflare/src/withSentry.ts | 22 -- .../cloudflare/test/integrations/hono.test.ts | 111 -------- packages/cloudflare/test/withSentry.test.ts | 126 --------- packages/elysia/src/index.ts | 4 - packages/google-cloud-serverless/src/index.ts | 4 - .../node-core/src/utils/ensureIsWrapped.ts | 2 +- packages/node/src/index.ts | 2 - .../integrations/tracing/hono/constants.ts | 13 - .../src/integrations/tracing/hono/index.ts | 156 ----------- .../tracing/hono/instrumentation.ts | 253 ------------------ .../src/integrations/tracing/hono/types.ts | 53 ---- .../node/src/integrations/tracing/index.ts | 4 - 33 files changed, 1 insertion(+), 1500 deletions(-) delete mode 100644 dev-packages/cloudflare-integration-tests/suites/hono-integration/index.ts delete mode 100644 dev-packages/cloudflare-integration-tests/suites/hono-integration/test.ts delete mode 100644 dev-packages/cloudflare-integration-tests/suites/hono-integration/wrangler.jsonc delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/src/env.d.ts delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/src/index.ts delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/test/env.d.ts delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/test/index.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/test/tsconfig.json delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/tsconfig.json delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/vitest.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/wrangler.toml delete mode 100644 dev-packages/node-integration-tests/suites/tracing/hono/instrument.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/hono/scenario.mjs delete mode 100644 dev-packages/node-integration-tests/suites/tracing/hono/test.ts delete mode 100644 packages/cloudflare/src/integrations/hono.ts delete mode 100644 packages/cloudflare/test/integrations/hono.test.ts delete mode 100644 packages/cloudflare/test/withSentry.test.ts delete mode 100644 packages/node/src/integrations/tracing/hono/constants.ts delete mode 100644 packages/node/src/integrations/tracing/hono/index.ts delete mode 100644 packages/node/src/integrations/tracing/hono/instrumentation.ts delete mode 100644 packages/node/src/integrations/tracing/hono/types.ts diff --git a/dev-packages/cloudflare-integration-tests/suites/hono-integration/index.ts b/dev-packages/cloudflare-integration-tests/suites/hono-integration/index.ts deleted file mode 100644 index ee7d18338306..000000000000 --- a/dev-packages/cloudflare-integration-tests/suites/hono-integration/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as Sentry from '@sentry/cloudflare'; -import { Hono } from 'hono'; - -interface Env { - SENTRY_DSN: string; -} - -const app = new Hono<{ Bindings: Env }>(); - -app.get('/', c => { - return c.text('Hello from Hono on Cloudflare!'); -}); - -app.get('/json', c => { - return c.json({ message: 'Hello from Hono', framework: 'hono', platform: 'cloudflare' }); -}); - -app.get('/error', () => { - throw new Error('Test error from Hono app (Sentry Cloudflare SDK)'); -}); - -app.get('/hello/:name', c => { - const name = c.req.param('name'); - return c.text(`Hello, ${name}!`); -}); - -export default Sentry.withSentry( - (env: Env) => ({ - dsn: env.SENTRY_DSN, - tracesSampleRate: 1.0, - }), - app, -); diff --git a/dev-packages/cloudflare-integration-tests/suites/hono-integration/test.ts b/dev-packages/cloudflare-integration-tests/suites/hono-integration/test.ts deleted file mode 100644 index e69cb0951c39..000000000000 --- a/dev-packages/cloudflare-integration-tests/suites/hono-integration/test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { expect, it } from 'vitest'; -import { eventEnvelope } from '../../expect'; -import { createRunner } from '../../runner'; - -it('Hono app captures errors', async ({ signal }) => { - const runner = createRunner(__dirname) - // First envelope: error event from Hono error handler - .expect( - eventEnvelope( - { - level: 'error', - transaction: 'GET /error', - exception: { - values: [ - { - type: 'Error', - value: 'Test error from Hono app (Sentry Cloudflare SDK)', - stacktrace: { - frames: expect.any(Array), - }, - mechanism: { type: 'auto.faas.hono.error_handler', handled: false }, - }, - ], - }, - request: { - headers: expect.any(Object), - method: 'GET', - url: expect.any(String), - }, - }, - { includeSamplingFields: true, includeSampleRand: true }, - ), - ) - // Second envelope: transaction event - .expect(envelope => { - const transactionEvent = envelope[1]?.[0]?.[1]; - expect(transactionEvent).toEqual( - expect.objectContaining({ - type: 'transaction', - transaction: 'GET /error', - contexts: expect.objectContaining({ - trace: expect.objectContaining({ - op: 'http.server', - status: 'internal_error', - }), - }), - }), - ); - }) - .unordered() - .start(signal); - await runner.makeRequest('get', '/error', { expectError: true }); - await runner.completed(); -}); diff --git a/dev-packages/cloudflare-integration-tests/suites/hono-integration/wrangler.jsonc b/dev-packages/cloudflare-integration-tests/suites/hono-integration/wrangler.jsonc deleted file mode 100644 index 628ce4c028aa..000000000000 --- a/dev-packages/cloudflare-integration-tests/suites/hono-integration/wrangler.jsonc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "hono-basic-worker", - "compatibility_date": "2025-06-17", - "main": "index.ts", - "compatibility_flags": ["nodejs_compat"], -} diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-hono/package.json deleted file mode 100644 index ce3e1267e849..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-hono/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "cloudflare-hono", - "scripts": { - "dev": "wrangler dev", - "build": "wrangler deploy --dry-run --var E2E_TEST_DSN=$E2E_TEST_DSN", - "test": "vitest", - "typecheck": "tsc --noEmit", - "cf-typegen": "wrangler types --env-interface CloudflareBindings", - "test:build": "pnpm install && pnpm build", - "//": "Just checking if it builds correctly and types don't break", - "test:assert": "pnpm typecheck && vitest run ." - }, - "dependencies": { - "@sentry/cloudflare": "file:../../packed/sentry-cloudflare-packed.tgz", - "hono": "4.12.21" - }, - "devDependencies": { - "@cloudflare/vitest-pool-workers": "^0.8.31", - "@cloudflare/workers-types": "^4.20250521.0", - "typescript": "^5.9.3", - "vitest": "3.2.6", - "wrangler": "^4.61.0" - }, - "volta": { - "node": "24.15.0", - "extends": "../../package.json" - }, - "sentryTest": { - "optional": true - } -} diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/env.d.ts b/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/env.d.ts deleted file mode 100644 index 0c9e04919e42..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/env.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Generated by Wrangler on Mon Jul 29 2024 21:44:31 GMT-0400 (Eastern Daylight Time) -// by running `wrangler types` - -interface Env { - E2E_TEST_DSN: ''; -} diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/index.ts b/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/index.ts deleted file mode 100644 index ceba3494d53f..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Hono } from 'hono'; -import * as Sentry from '@sentry/cloudflare'; - -const app = new Hono(); - -app.get('/', ctx => { - return ctx.json({ message: 'Welcome to Hono API' }); -}); - -app.get('/hello/:name', ctx => { - const name = ctx.req.param('name'); - return ctx.json({ message: `Hello, ${name}!` }); -}); - -app.get('/error', () => { - throw new Error('This is a test error'); -}); - -app.onError((err, ctx) => { - console.error(`Error occurred: ${err.message}`); - return ctx.json({ error: err.message }, 500); -}); - -app.notFound(ctx => { - return ctx.json({ message: 'Not Found' }, 404); -}); - -export default Sentry.withSentry( - (env: Env) => ({ - dsn: env?.E2E_TEST_DSN, - tracesSampleRate: 1.0, - }), - app, -); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/env.d.ts b/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/env.d.ts deleted file mode 100644 index 3b9f82b9628f..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/env.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module 'cloudflare:test' { - // ProvidedEnv controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env {} -} diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/index.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/index.test.ts deleted file mode 100644 index 2ae93f9b1fd5..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/index.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import app from '../src/index'; -import { SELF, createExecutionContext, env, waitOnExecutionContext } from 'cloudflare:test'; - -describe('Hono app on Cloudflare Workers', () => { - describe('Unit Tests', () => { - it('should return welcome message', async () => { - const res = await app.request('/', {}, env); - expect(res.status).toBe(200); - const data = await res.json(); - expect(data).toEqual({ message: 'Welcome to Hono API' }); - }); - - it('should greet a user with their name', async () => { - const res = await app.request('/hello/tester', {}, env); - expect(res.status).toBe(200); - const data = await res.json(); - expect(data).toEqual({ message: 'Hello, tester!' }); - }); - - it('should handle errors with custom error handler', async () => { - const res = await app.request('/error', {}, env); - expect(res.status).toBe(500); - const data = await res.json(); - expect(data).toHaveProperty('error', 'This is a test error'); - }); - - it('should handle 404 with custom not found handler', async () => { - const res = await app.request('/non-existent-route', {}, env); - expect(res.status).toBe(404); - const data = await res.json(); - expect(data).toEqual({ message: 'Not Found' }); - }); - }); - - // Integration test style with worker.fetch - describe('Integration Tests', () => { - it('should fetch the root endpoint', async () => { - // Create request and context - const request = new Request('http://localhost/'); - const ctx = createExecutionContext(); - - const response = await app.fetch(request, env, ctx); - - await waitOnExecutionContext(ctx); - - expect(response.status).toBe(200); - const data = await response.json(); - expect(data).toEqual({ message: 'Welcome to Hono API' }); - }); - - it('should handle a parameter route', async () => { - // Create request and context - const request = new Request('http://localhost/hello/cloudflare'); - const ctx = createExecutionContext(); - - const response = await app.fetch(request, env, ctx); - - await waitOnExecutionContext(ctx); - - expect(response.status).toBe(200); - const data = await response.json(); - expect(data).toEqual({ message: 'Hello, cloudflare!' }); - }); - - it('should handle errors gracefully', async () => { - const response = await SELF.fetch('http://localhost/error'); - - expect(response.status).toBe(500); - const data = await response.json(); - expect(data).toHaveProperty('error', 'This is a test error'); - }); - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/tsconfig.json b/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/tsconfig.json deleted file mode 100644 index f536f706fa69..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "types": ["@cloudflare/workers-types/experimental", "@cloudflare/vitest-pool-workers"] - }, - "include": ["./**/*.ts", "../src/env.d.ts"], - "exclude": [] -} diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/tsconfig.json b/dev-packages/e2e-tests/test-applications/cloudflare-hono/tsconfig.json deleted file mode 100644 index 3c1c64b66cb8..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-hono/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "Bundler", - "strict": true, - "skipLibCheck": true, - "lib": ["ESNext"], - "jsx": "react-jsx", - "types": ["@cloudflare/workers-types/experimental"], - "jsxImportSource": "hono/jsx" - }, - "include": ["src/**/*"], - "exclude": ["test", "node_modules"] -} diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/vitest.config.ts b/dev-packages/e2e-tests/test-applications/cloudflare-hono/vitest.config.ts deleted file mode 100644 index 60ce2468ff28..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-hono/vitest.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineWorkersProject } from '@cloudflare/vitest-pool-workers/config'; - -export default defineWorkersProject(() => { - return { - test: { - globals: true, - poolOptions: { - workers: { wrangler: { configPath: './wrangler.toml' } }, - }, - }, - }; -}); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/wrangler.toml b/dev-packages/e2e-tests/test-applications/cloudflare-hono/wrangler.toml deleted file mode 100644 index 9fdfb60c18b9..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-hono/wrangler.toml +++ /dev/null @@ -1,7 +0,0 @@ -name = "cloudflare-hono" -main = "src/index.ts" -compatibility_date = "2023-10-30" -compatibility_flags = ["nodejs_compat"] - -# [vars] -# E2E_TEST_DSN = "" diff --git a/dev-packages/node-integration-tests/suites/tracing/hono/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/hono/instrument.mjs deleted file mode 100644 index 46a27dd03b74..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/hono/instrument.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { loggingTransport } from '@sentry-internal/node-integration-tests'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - transport: loggingTransport, -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/hono/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/hono/scenario.mjs deleted file mode 100644 index a3df113992df..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/hono/scenario.mjs +++ /dev/null @@ -1,196 +0,0 @@ -import { serve } from '@hono/node-server'; -import * as Sentry from '@sentry/node'; -import { sendPortToRunner } from '@sentry-internal/node-core-integration-tests'; -import { Hono } from 'hono'; -import { HTTPException } from 'hono/http-exception'; - -const app = new Hono(); - -Sentry.setupHonoErrorHandler(app); - -// Global middleware to capture all requests -app.use(async function global(c, next) { - await next(); -}); - -const basePaths = ['/sync', '/async']; -const methods = ['get', 'post', 'put', 'delete', 'patch']; - -basePaths.forEach(basePath => { - // Sub-path middleware to capture all requests under the basePath - app.use(`${basePath}/*`, async function base(c, next) { - await next(); - }); - - const baseApp = new Hono(); - methods.forEach(method => { - baseApp[method]('/', c => { - const response = c.text('response 200'); - if (basePath === '/sync') return response; - return Promise.resolve(response); - }); - - baseApp[method]( - '/middleware', - // anonymous middleware - async (c, next) => { - await next(); - }, - c => { - const response = c.text('response 200'); - if (basePath === '/sync') return response; - return Promise.resolve(response); - }, - ); - - // anonymous middleware - baseApp[method]('/middleware/separately', async (c, next) => { - await next(); - }); - - baseApp[method]('/middleware/separately', async c => { - const response = c.text('response 200'); - if (basePath === '/sync') return response; - return Promise.resolve(response); - }); - - baseApp.all('/all', c => { - const response = c.text('response 200'); - if (basePath === '/sync') return response; - return Promise.resolve(response); - }); - - baseApp.all( - '/all/middleware', - // anonymous middleware - async (c, next) => { - await next(); - }, - c => { - const response = c.text('response 200'); - if (basePath === '/sync') return response; - return Promise.resolve(response); - }, - ); - - // anonymous middleware - baseApp.all('/all/middleware/separately', async (c, next) => { - await next(); - }); - - baseApp.all('/all/middleware/separately', async c => { - const response = c.text('response 200'); - if (basePath === '/sync') return response; - return Promise.resolve(response); - }); - - baseApp.on(method, '/on', c => { - const response = c.text('response 200'); - if (basePath === '/sync') return response; - return Promise.resolve(response); - }); - - baseApp.on( - method, - '/on/middleware', - // anonymous middleware - async (c, next) => { - await next(); - }, - c => { - const response = c.text('response 200'); - if (basePath === '/sync') return response; - return Promise.resolve(response); - }, - ); - - // anonymous middleware - baseApp.on(method, '/on/middleware/separately', async (c, next) => { - await next(); - }); - - baseApp.on(method, '/on/middleware/separately', async c => { - const response = c.text('response 200'); - if (basePath === '/sync') return response; - return Promise.resolve(response); - }); - - baseApp[method]('/401', () => { - const response = new HTTPException(401, { message: 'response 401' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - - baseApp.all('/all/401', () => { - const response = new HTTPException(401, { message: 'response 401' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - - baseApp.on(method, '/on/401', () => { - const response = new HTTPException(401, { message: 'response 401' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - - baseApp[method]('/402', () => { - const response = new HTTPException(402, { message: 'response 402' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - - baseApp.all('/all/402', () => { - const response = new HTTPException(402, { message: 'response 402' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - - baseApp.on(method, '/on/402', () => { - const response = new HTTPException(402, { message: 'response 402' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - - baseApp[method]('/403', () => { - const response = new HTTPException(403, { message: 'response 403' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - - baseApp.all('/all/403', () => { - const response = new HTTPException(403, { message: 'response 403' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - - baseApp.on(method, '/on/403', () => { - const response = new HTTPException(403, { message: 'response 403' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - - baseApp[method]('/500', () => { - const response = new HTTPException(500, { message: 'response 500' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - - baseApp.all('/all/500', () => { - const response = new HTTPException(500, { message: 'response 500' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - - baseApp.on(method, '/on/500', () => { - const response = new HTTPException(500, { message: 'response 500' }); - if (basePath === '/sync') throw response; - return Promise.reject(response); - }); - }); - - app.route(basePath, baseApp); -}); - -serve({ fetch: app.fetch, port: 0 }, info => { - sendPortToRunner(info.port); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/hono/test.ts b/dev-packages/node-integration-tests/suites/tracing/hono/test.ts deleted file mode 100644 index 484e7c948407..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/hono/test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { afterAll, expect } from 'vitest'; -import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; - -const ROUTES = ['/sync', '/async'] as const; -const METHODS = ['get', 'post', 'put', 'delete', 'patch'] as const; -const PATHS = ['/', '/all', '/on'] as const; - -type Method = (typeof METHODS)[number]; - -function verifyHonoSpan(name: string, type: 'middleware' | 'request_handler') { - return expect.objectContaining({ - data: expect.objectContaining({ - 'hono.name': name, - 'hono.type': type, - }), - description: name, - op: type === 'request_handler' ? 'request_handler.hono' : 'middleware.hono', - origin: 'auto.http.otel.hono', - }); -} - -function baseSpans() { - return [ - verifyHonoSpan('sentryRequestMiddleware', 'middleware'), - verifyHonoSpan('sentryErrorMiddleware', 'middleware'), - verifyHonoSpan('global', 'middleware'), - verifyHonoSpan('base', 'middleware'), - ]; -} - -afterAll(() => { - cleanupChildProcesses(); -}); - -createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { - test('should handle transactions for all route/method/path combinations', async () => { - const runner = createRunner(); - const requests: Array<{ method: Method; url: string }> = []; - - for (const route of ROUTES) { - for (const method of METHODS) { - for (const path of PATHS) { - const pathSuffix = path === '/' ? '' : path; - const fullPath = `${route}${pathSuffix}`; - - runner.expect({ - transaction: { - transaction: `${method.toUpperCase()} ${fullPath}`, - spans: expect.arrayContaining([...baseSpans(), verifyHonoSpan(fullPath, 'request_handler')]), - }, - }); - requests.push({ method, url: fullPath }); - - runner.expect({ - transaction: { - transaction: `${method.toUpperCase()} ${fullPath}/middleware`, - spans: expect.arrayContaining([ - ...baseSpans(), - verifyHonoSpan('anonymous', 'middleware'), - verifyHonoSpan(`${fullPath}/middleware`, 'request_handler'), - ]), - }, - }); - requests.push({ method, url: `${fullPath}/middleware` }); - - runner.expect({ - transaction: { - transaction: `${method.toUpperCase()} ${fullPath}/middleware/separately`, - spans: expect.arrayContaining([ - ...baseSpans(), - verifyHonoSpan('anonymous', 'middleware'), - verifyHonoSpan(`${fullPath}/middleware/separately`, 'request_handler'), - ]), - }, - }); - requests.push({ method, url: `${fullPath}/middleware/separately` }); - } - } - } - - const started = runner.start(); - for (const req of requests) { - await started.makeRequest(req.method, req.url); - } - await started.completed(); - }, 60_000); - - test('should capture 500 errors for all route/method/path combinations', async () => { - const runner = createRunner().ignore('transaction'); - const requests: Array<{ method: Method; url: string }> = []; - - for (const route of ROUTES) { - for (const method of METHODS) { - for (const path of PATHS) { - const pathSuffix = path === '/' ? '' : path; - - runner.expect({ - event: { - exception: { - values: [ - { - mechanism: { - type: 'auto.middleware.hono', - handled: false, - }, - type: 'Error', - value: 'response 500', - }, - ], - }, - }, - }); - requests.push({ method, url: `${route}${pathSuffix}/500` }); - } - } - } - - const started = runner.start(); - for (const req of requests) { - await started.makeRequest(req.method, req.url, { expectError: true }); - } - await started.completed(); - }, 60_000); - - test.each(['/401', '/402', '/403', '/does-not-exist'])('should not capture %s errors', async (subPath: string) => { - const runner = createRunner() - .expect({ - transaction: { - transaction: 'GET /sync', - }, - }) - .start(); - runner.makeRequest('get', `/sync${subPath}`, { expectError: true }); - runner.makeRequest('get', '/sync'); - await runner.completed(); - }); -}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 7be3132e0fd4..840f689bbd7f 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -64,8 +64,6 @@ export { winterCGHeadersToDict, graphqlIntegration, hapiIntegration, - // eslint-disable-next-line typescript/no-deprecated - honoIntegration, httpIntegration, httpServerIntegration, httpServerSpansIntegration, @@ -128,8 +126,6 @@ export { setAttributes, setupExpressErrorHandler, setupHapiErrorHandler, - // eslint-disable-next-line typescript/no-deprecated - setupHonoErrorHandler, setupKoaErrorHandler, setUser, spanToBaggageHeader, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 5b7b9ebafeb8..5a853458e2da 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -123,10 +123,6 @@ export { createSentryWinstonTransport, hapiIntegration, setupHapiErrorHandler, - // eslint-disable-next-line typescript/no-deprecated - honoIntegration, - // eslint-disable-next-line typescript/no-deprecated - setupHonoErrorHandler, spotlightIntegration, initOpenTelemetry, spanToJSON, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 3e174941751c..5d5d973f515d 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -142,10 +142,6 @@ export { processSessionIntegration, hapiIntegration, setupHapiErrorHandler, - // eslint-disable-next-line typescript/no-deprecated - honoIntegration, - // eslint-disable-next-line typescript/no-deprecated - setupHonoErrorHandler, spotlightIntegration, initOpenTelemetry, spanToJSON, diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index 20a537c5b307..33bcf07c24e7 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -128,8 +128,6 @@ export { getDefaultIntegrations } from './sdk'; export { httpServerIntegration } from './integrations/httpServer'; export { fetchIntegration } from './integrations/fetch'; export { vercelAIIntegration } from './integrations/tracing/vercelai'; -// eslint-disable-next-line typescript/no-deprecated -export { honoIntegration } from './integrations/hono'; // eslint-disable-next-line typescript/no-deprecated export { instrumentD1WithSentry } from './instrumentations/worker/instrumentD1'; diff --git a/packages/cloudflare/src/integrations/hono.ts b/packages/cloudflare/src/integrations/hono.ts deleted file mode 100644 index d1aea318d0ac..000000000000 --- a/packages/cloudflare/src/integrations/hono.ts +++ /dev/null @@ -1,108 +0,0 @@ -import type { IntegrationFn } from '@sentry/core'; -import { - isObjectLike, - captureException, - debug, - defineIntegration, - getActiveSpan, - getClient, - getIsolationScope, - getRootSpan, - updateSpanName, -} from '@sentry/core'; -import { DEBUG_BUILD } from '../debug-build'; - -const INTEGRATION_NAME = 'Hono' as const; - -interface HonoError extends Error { - status?: number; -} - -// Minimal type - only exported for tests -export interface HonoContext { - req: { method: string; path?: string }; -} - -export interface Options { - /** - * Callback method deciding whether error should be captured and sent to Sentry - * @param error Captured middleware error - */ - shouldHandleError?(this: void, error: HonoError): boolean; -} - -/** Only exported for internal use */ -export function getHonoIntegration(): ReturnType | undefined { - return getClient()?.getIntegrationByName(INTEGRATION_NAME); -} - -function isHonoError(err: unknown): err is HonoError { - if (err instanceof Error) { - return true; - } - return isObjectLike(err) && 'status' in (err as Record); -} - -// Vendored from https://github.com/honojs/hono/blob/d3abeb1f801aaa1b334285c73da5f5f022dbcadb/src/helper/route/index.ts#L58-L59 -const routePath = (c: HonoContext): string => c.req?.path ?? ''; - -const _honoIntegration = ((options: Partial = {}) => { - return { - name: INTEGRATION_NAME, - // Hono error handler: https://github.com/honojs/hono/blob/d3abeb1f801aaa1b334285c73da5f5f022dbcadb/src/hono-base.ts#L35 - handleHonoException(err: HonoError, context: HonoContext): void { - const shouldHandleError = options.shouldHandleError || defaultShouldHandleError; - - if (!isHonoError(err)) { - DEBUG_BUILD && debug.log("[Hono] Won't capture exception in `onError` because it's not a Hono error.", err); - return; - } - - if (shouldHandleError(err)) { - if (context) { - const activeSpan = getActiveSpan(); - const spanName = `${context.req.method} ${routePath(context)}`; - - if (activeSpan) { - activeSpan.updateName(spanName); - updateSpanName(getRootSpan(activeSpan), spanName); - } - - getIsolationScope().setTransactionName(spanName); - } - - captureException(err, { mechanism: { handled: false, type: 'auto.faas.hono.error_handler' } }); - } else { - DEBUG_BUILD && debug.log('[Hono] Not capturing exception because `shouldHandleError` returned `false`.', err); - } - }, - }; -}) satisfies IntegrationFn; - -/** - * Automatically captures exceptions caught with the `onError` handler in Hono. - * - * The integration is enabled by default. - * - * @deprecated Use the `@sentry/hono` package instead. The `sentry()` middleware from `@sentry/hono/cloudflare` - * handles error capturing automatically without needing this integration. - * - * @example - * integrations: [ - * honoIntegration({ - * shouldHandleError: (err) => true; // always capture exceptions in onError - * }) - * ] - */ -export const honoIntegration = defineIntegration(_honoIntegration); - -/** - * Default function to determine if an error should be sent to Sentry - * - * 3xx and 4xx errors are not sent by default. - */ -function defaultShouldHandleError(error: HonoError): boolean { - const statusCode = error?.status; - // 3xx and 4xx errors are not sent by default. - return statusCode ? statusCode >= 500 || statusCode <= 299 : true; -} diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index 2bbd704e6004..5bdb8f07e28c 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -17,7 +17,6 @@ import { CloudflareClient } from './client'; import { makeFlushLock } from './flush'; import { httpServerIntegration } from './integrations/httpServer'; import { fetchIntegration } from './integrations/fetch'; -import { honoIntegration } from './integrations/hono'; import { setupOpenTelemetryTracer } from './opentelemetry/tracer'; import { makeCloudflareTransport } from './transport'; import { defaultStackParser } from './vendor/stacktrace'; @@ -57,8 +56,6 @@ export function getDefaultIntegrations(options: CloudflareOptions): Integration[ conversationIdIntegration(), linkedErrorsIntegration(), fetchIntegration(), - // eslint-disable-next-line typescript/no-deprecated - honoIntegration(), httpServerIntegration(), requestDataIntegration(cookiesEnabled ? undefined : { include: { cookies: false } }), consoleIntegration(), diff --git a/packages/cloudflare/src/withSentry.ts b/packages/cloudflare/src/withSentry.ts index 554e5d9cbf9b..b71fde33b30a 100644 --- a/packages/cloudflare/src/withSentry.ts +++ b/packages/cloudflare/src/withSentry.ts @@ -1,13 +1,11 @@ import type { env as cloudflareEnv } from 'cloudflare:workers'; import { setAsyncLocalStorageAsyncContextStrategy } from './async'; import type { CloudflareOptions } from './client'; -import { ensureInstrumented } from './instrument'; import { instrumentExportedHandlerEmail } from './instrumentations/worker/instrumentEmail'; import { instrumentExportedHandlerFetch } from './instrumentations/worker/instrumentFetch'; import { instrumentExportedHandlerQueue } from './instrumentations/worker/instrumentQueue'; import { instrumentExportedHandlerScheduled } from './instrumentations/worker/instrumentScheduled'; import { instrumentExportedHandlerTail } from './instrumentations/worker/instrumentTail'; -import { getHonoIntegration } from './integrations/hono'; import { isCloudflareClass } from './utils/isCloudflareClass'; import { instrumentWorkerEntrypoint, @@ -47,7 +45,6 @@ export function withSentry< try { // oxlint-disable-next-line typescript/no-explicit-any instrumentExportedHandlerFetch(handler, optionsCallback as any); - instrumentHonoErrorHandler(handler); // oxlint-disable-next-line typescript/no-explicit-any instrumentExportedHandlerScheduled(handler, optionsCallback as any); // oxlint-disable-next-line typescript/no-explicit-any @@ -63,22 +60,3 @@ export function withSentry< return handler; } - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function instrumentHonoErrorHandler>(handler: T): void { - if ('onError' in handler && 'errorHandler' in handler && typeof handler.errorHandler === 'function') { - handler.errorHandler = ensureInstrumented( - handler.errorHandler, - original => - new Proxy(original, { - apply(target, thisArg, args) { - const [err, context] = args; - - getHonoIntegration()?.handleHonoException(err, context); - - return Reflect.apply(target, thisArg, args); - }, - }), - ); - } -} diff --git a/packages/cloudflare/test/integrations/hono.test.ts b/packages/cloudflare/test/integrations/hono.test.ts deleted file mode 100644 index 94f23f684a5d..000000000000 --- a/packages/cloudflare/test/integrations/hono.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as sentryCore from '@sentry/core'; -import { type Client, createStackParser } from '@sentry/core'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { CloudflareClient } from '../../src/client'; -import type { HonoContext } from '../../src/integrations/hono'; -import { honoIntegration } from '../../src/integrations/hono'; - -class FakeClient extends CloudflareClient { - public getIntegrationByName(name: string) { - return name === 'Hono' ? (honoIntegration() as any) : undefined; - } -} - -type MockHonoIntegrationType = { handleHonoException: (err: Error, ctx: HonoContext) => void }; - -const sampleContext: HonoContext = { - req: { method: 'GET', path: '/vitest-sample' }, -}; - -describe('Hono integration', () => { - let client: FakeClient; - - beforeEach(() => { - vi.clearAllMocks(); - client = new FakeClient({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [], - transport: () => ({ send: () => Promise.resolve({}), flush: () => Promise.resolve(true) }), - stackParser: createStackParser(), - }); - - vi.spyOn(sentryCore, 'getClient').mockImplementation(() => client as Client); - }); - - it('captures in errorHandler when onError exists', () => { - const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); - const integration = honoIntegration(); - integration.setupOnce?.(); - - const error = new Error('hono boom'); - // simulate withSentry wrapping of errorHandler calling back into integration - (integration as unknown as MockHonoIntegrationType).handleHonoException(error, sampleContext); - - expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - expect(captureExceptionSpy).toHaveBeenLastCalledWith(error, { - mechanism: { handled: false, type: 'auto.faas.hono.error_handler' }, - }); - }); - - it('does not capture for 4xx status', () => { - const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); - const integration = honoIntegration(); - integration.setupOnce?.(); - - (integration as unknown as MockHonoIntegrationType).handleHonoException( - Object.assign(new Error('client err'), { status: 404 }), - sampleContext, - ); - expect(captureExceptionSpy).not.toHaveBeenCalled(); - }); - - it('does not capture for 3xx status', () => { - const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); - const integration = honoIntegration(); - integration.setupOnce?.(); - - (integration as unknown as MockHonoIntegrationType).handleHonoException( - Object.assign(new Error('redirect'), { status: 302 }), - sampleContext, - ); - expect(captureExceptionSpy).not.toHaveBeenCalled(); - }); - - it('captures for 5xx status', () => { - const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); - const integration = honoIntegration(); - integration.setupOnce?.(); - - const err = Object.assign(new Error('server err'), { status: 500 }); - (integration as unknown as MockHonoIntegrationType).handleHonoException(err, sampleContext); - expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - }); - - it('captures if no status is present on Error', () => { - const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); - const integration = honoIntegration(); - integration.setupOnce?.(); - - (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('no status'), sampleContext); - expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - }); - - it('supports custom shouldHandleError option', () => { - const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); - const integration = honoIntegration({ shouldHandleError: () => false }); - integration.setupOnce?.(); - - (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('blocked'), sampleContext); - expect(captureExceptionSpy).not.toHaveBeenCalled(); - }); - - it('does not throw error without passed context and still captures', () => { - const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); - const integration = honoIntegration(); - integration.setupOnce?.(); - - // @ts-expect-error context is not passed - (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error()); - expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/cloudflare/test/withSentry.test.ts b/packages/cloudflare/test/withSentry.test.ts deleted file mode 100644 index c4e1ed789d9f..000000000000 --- a/packages/cloudflare/test/withSentry.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -// Note: These tests run the handler in Node.js, which has some differences to the cloudflare workers runtime. -// Although this is not ideal, this is the best we can do until we have a better way to test cloudflare workers. - -import * as SentryCore from '@sentry/core'; -import { beforeEach, describe, expect, test, vi } from 'vitest'; -import { withSentry } from '../src/withSentry'; -import { markAsInstrumented } from '../src/instrument'; -import * as HonoIntegration from '../src/integrations/hono'; - -declare global { - namespace Cloudflare { - interface Env { - SENTRY_DSN: string; - } - } -} - -type HonoLikeApp = ExportedHandler< - Env, - QueueHandlerMessage, - CfHostMetadata -> & { - onError?: () => void; - errorHandler?: (err: Error) => Response; -}; - -describe('withSentry', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - describe('hono errorHandler', () => { - test('calls Hono Integration to handle error captured by the errorHandler', async () => { - const error = new Error('test hono error'); - - const handleHonoException = vi.fn(); - vi.spyOn(HonoIntegration, 'getHonoIntegration').mockReturnValue({ handleHonoException } as any); - - const honoApp: HonoLikeApp = { - fetch(_request, _env, _context) { - return new Response('test'); - }, - onError() {}, - errorHandler(err: Error) { - return new Response(`Error: ${err.message}`, { status: 500 }); - }, - }; - - withSentry(env => ({ dsn: env.SENTRY_DSN }), honoApp); - - const errorHandlerResponse = honoApp.errorHandler?.(error); - - expect(handleHonoException).toHaveBeenCalledTimes(1); - expect(handleHonoException).toHaveBeenLastCalledWith(error, undefined); - 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: HonoLikeApp = { - fetch(_request, _env, _context) { - return new Response('test'); - }, - onError() {}, - errorHandler: originalErrorHandlerSpy, - }; - - withSentry(env => ({ dsn: env.SENTRY_DSN }), honoApp); - - 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'); - - const originalErrorHandler = (err: Error) => { - return new Response(`Error: ${err.message}`, { status: 500 }); - }; - - markAsInstrumented(originalErrorHandler); - - const honoApp: HonoLikeApp = { - fetch(_request, _env, _context) { - return new Response('test'); - }, - onError() {}, - errorHandler: originalErrorHandler, - }; - - withSentry(env => ({ dsn: env.SENTRY_DSN }), honoApp); - - honoApp.errorHandler?.(error); - expect(captureExceptionSpy).not.toHaveBeenCalled(); - }); - - test('does not double-wrap errorHandler when withSentry is called twice', async () => { - const honoApp: HonoLikeApp = { - fetch(_request, _env, _context) { - return new Response('test'); - }, - onError() {}, - errorHandler(err: Error) { - return new Response(`Error: ${err.message}`, { status: 500 }); - }, - }; - - withSentry(env => ({ dsn: env.SENTRY_DSN }), honoApp); - const firstErrorHandler = honoApp.errorHandler; - - withSentry(env => ({ dsn: env.SENTRY_DSN }), honoApp); - const secondErrorHandler = honoApp.errorHandler; - - expect(firstErrorHandler).toBe(secondErrorHandler); - }); - }); -}); diff --git a/packages/elysia/src/index.ts b/packages/elysia/src/index.ts index 63a80ae7e81a..d4e7c1ac926c 100644 --- a/packages/elysia/src/index.ts +++ b/packages/elysia/src/index.ts @@ -120,10 +120,6 @@ export { processSessionIntegration, hapiIntegration, setupHapiErrorHandler, - // eslint-disable-next-line typescript/no-deprecated - honoIntegration, - // eslint-disable-next-line typescript/no-deprecated - setupHonoErrorHandler, spotlightIntegration, initOpenTelemetry, spanToJSON, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index e37ed3c5b559..9b7f9506ae62 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -121,10 +121,6 @@ export { processSessionIntegration, hapiIntegration, setupHapiErrorHandler, - // eslint-disable-next-line typescript/no-deprecated - honoIntegration, - // eslint-disable-next-line typescript/no-deprecated - setupHonoErrorHandler, spotlightIntegration, initOpenTelemetry, spanToJSON, diff --git a/packages/node-core/src/utils/ensureIsWrapped.ts b/packages/node-core/src/utils/ensureIsWrapped.ts index bb8d6ca3a5e2..2de941805162 100644 --- a/packages/node-core/src/utils/ensureIsWrapped.ts +++ b/packages/node-core/src/utils/ensureIsWrapped.ts @@ -16,7 +16,7 @@ import { createMissingInstrumentationContext } from './createMissingInstrumentat */ export function ensureIsWrapped( maybeWrappedFunction: unknown, - name: 'express' | 'connect' | 'fastify' | 'hapi' | 'koa' | 'hono', + name: 'express' | 'connect' | 'fastify' | 'hapi' | 'koa', ): void { const clientOptions = getClient()?.getOptions(); if ( diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 70a52d1c5b27..102632e842b4 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -15,8 +15,6 @@ export { postgresIntegration } from './integrations/tracing/postgres'; export { postgresJsIntegration } from './integrations/tracing/postgresjs'; export { prismaIntegration } from '@sentry/server-utils'; export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/hapi'; -// eslint-disable-next-line typescript/no-deprecated -export { honoIntegration, setupHonoErrorHandler } from './integrations/tracing/hono'; export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa'; export { knexIntegration } from './integrations/tracing/knex'; export { tediousIntegration } from './integrations/tracing/tedious'; diff --git a/packages/node/src/integrations/tracing/hono/constants.ts b/packages/node/src/integrations/tracing/hono/constants.ts deleted file mode 100644 index 5814f5e950f2..000000000000 --- a/packages/node/src/integrations/tracing/hono/constants.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const AttributeNames = { - HONO_TYPE: 'hono.type', - HONO_NAME: 'hono.name', -} as const; - -export type AttributeNames = (typeof AttributeNames)[keyof typeof AttributeNames]; - -export const HonoTypes = { - MIDDLEWARE: 'middleware', - REQUEST_HANDLER: 'request_handler', -} as const; - -export type HonoTypes = (typeof HonoTypes)[keyof typeof HonoTypes]; diff --git a/packages/node/src/integrations/tracing/hono/index.ts b/packages/node/src/integrations/tracing/hono/index.ts deleted file mode 100644 index 3b97cd84bf06..000000000000 --- a/packages/node/src/integrations/tracing/hono/index.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { HTTP_REQUEST_METHOD, HTTP_ROUTE } from '@sentry/conventions/attributes'; -import type { IntegrationFn, Span } from '@sentry/core'; -import { - captureException, - debug, - defineIntegration, - getDefaultIsolationScope, - getIsolationScope, - httpRequestToRequestData, - SEMANTIC_ATTRIBUTE_SENTRY_OP, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - spanToJSON, -} from '@sentry/core'; -import { ensureIsWrapped, generateInstrumentOnce } from '@sentry/node-core'; -import { DEBUG_BUILD } from '../../../debug-build'; -import { AttributeNames } from './constants'; -import { HonoInstrumentation } from './instrumentation'; -import type { Context, MiddlewareHandler, MiddlewareHandlerInterface, Next } from './types'; - -const INTEGRATION_NAME = 'Hono' as const; - -function addHonoSpanAttributes(span: Span): void { - const attributes = spanToJSON(span).data; - const type = attributes[AttributeNames.HONO_TYPE]; - if (attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] || !type) { - return; - } - - span.setAttributes({ - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.hono', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.hono`, - }); - - const name = attributes[AttributeNames.HONO_NAME]; - if (typeof name === 'string') { - span.updateName(name); - } - - if (getIsolationScope() === getDefaultIsolationScope()) { - DEBUG_BUILD && debug.warn('Isolation scope is default isolation scope - skipping setting transactionName'); - return; - } - - const route = attributes[HTTP_ROUTE]; - const method = attributes[HTTP_REQUEST_METHOD]; - if (typeof route === 'string' && typeof method === 'string') { - getIsolationScope().setTransactionName(`${method} ${route}`); - } -} - -export const instrumentHono = generateInstrumentOnce( - INTEGRATION_NAME, - () => - new HonoInstrumentation({ - responseHook: span => { - addHonoSpanAttributes(span); - }, - }), -); - -const _honoIntegration = (() => { - return { - name: INTEGRATION_NAME, - setupOnce() { - instrumentHono(); - }, - }; -}) satisfies IntegrationFn; - -/** - * Adds Sentry tracing instrumentation for [Hono](https://hono.dev/). - * - * If you also want to capture errors, you need to call `setupHonoErrorHandler(app)` after you set up your Hono server. - * - * For more information, see the [hono documentation](https://docs.sentry.io/platforms/javascript/guides/hono/). - * - * @deprecated Use the `@sentry/hono` package instead. The `sentry()` middleware from `@sentry/hono/node` handles - * tracing and error capturing automatically without needing this integration or `setupHonoErrorHandler`. - * - * @example - * ```javascript - * const Sentry = require('@sentry/node'); - * - * Sentry.init({ - * integrations: [Sentry.honoIntegration()], - * }) - * ``` - */ -export const honoIntegration = defineIntegration(_honoIntegration); - -interface HonoHandlerOptions { - /** - * Callback method deciding whether error should be captured and sent to Sentry - * @param error Captured Hono error - */ - shouldHandleError: (context: Context) => boolean; -} - -function honoRequestHandler(): MiddlewareHandler { - return async function sentryRequestMiddleware(context: Context, next: Next): Promise { - const normalizedRequest = httpRequestToRequestData(context.req); - getIsolationScope().setSDKProcessingMetadata({ normalizedRequest }); - await next(); - }; -} - -function defaultShouldHandleError(context: Context): boolean { - const statusCode = context.res.status; - return statusCode >= 500; -} - -function honoErrorHandler(options?: Partial): MiddlewareHandler { - return async function sentryErrorMiddleware(context: Context, next: Next): Promise { - await next(); - - const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError; - if (shouldHandleError(context)) { - (context.res as { sentry?: string }).sentry = captureException(context.error, { - mechanism: { - type: 'auto.middleware.hono', - handled: false, - }, - }); - } - }; -} - -/** - * Add a Hono error handler to capture errors to Sentry. - * - * @param app The Hono instances - * @param options Configuration options for the handler - * - * @deprecated Use the `@sentry/hono` package instead. The `sentry()` middleware from `@sentry/hono/node` handles - * error capturing automatically without needing this function or `honoIntegration`. - * - * @example - * ```javascript - * const Sentry = require('@sentry/node'); - * const { Hono } = require("hono"); - * - * const app = new Hono(); - * - * Sentry.setupHonoErrorHandler(app); - * - * // Add your routes, etc. - * ``` - */ -export function setupHonoErrorHandler( - app: { use: MiddlewareHandlerInterface }, - options?: Partial, -): void { - app.use(honoRequestHandler()); - app.use(honoErrorHandler(options)); - ensureIsWrapped(app.use, 'hono'); -} diff --git a/packages/node/src/integrations/tracing/hono/instrumentation.ts b/packages/node/src/integrations/tracing/hono/instrumentation.ts deleted file mode 100644 index 9a55eaac2776..000000000000 --- a/packages/node/src/integrations/tracing/hono/instrumentation.ts +++ /dev/null @@ -1,253 +0,0 @@ -import type { Span } from '@opentelemetry/api'; -import { context, SpanStatusCode, trace } from '@opentelemetry/api'; -import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; -import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; -import { isThenable } from '@sentry/core'; -import { AttributeNames, HonoTypes } from './constants'; -import type { - Context, - Handler, - HandlerInterface, - Hono, - HonoInstance, - MiddlewareHandler, - MiddlewareHandlerInterface, - Next, - OnHandlerInterface, -} from './types'; - -const PACKAGE_NAME = '@sentry/instrumentation-hono'; -const PACKAGE_VERSION = '0.0.1'; - -export interface HonoResponseHookFunction { - (span: Span): void; -} - -export interface HonoInstrumentationConfig extends InstrumentationConfig { - /** Function for adding custom span attributes from the response */ - responseHook?: HonoResponseHookFunction; -} - -/** - * Hono instrumentation for OpenTelemetry - */ -export class HonoInstrumentation extends InstrumentationBase { - public constructor(config: HonoInstrumentationConfig = {}) { - super(PACKAGE_NAME, PACKAGE_VERSION, config); - } - - /** - * Initialize the instrumentation. - */ - public init(): InstrumentationNodeModuleDefinition[] { - return [ - new InstrumentationNodeModuleDefinition('hono', ['>=4.0.0 <5'], moduleExports => this._patch(moduleExports)), - ]; - } - - /** - * Patches the module exports to instrument Hono. - */ - private _patch(moduleExports: { Hono: Hono }): { Hono: Hono } { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const instrumentation = this; - - class WrappedHono extends moduleExports.Hono { - public constructor(...args: unknown[]) { - super(...args); - - instrumentation._wrap(this, 'get', instrumentation._patchHandler()); - instrumentation._wrap(this, 'post', instrumentation._patchHandler()); - instrumentation._wrap(this, 'put', instrumentation._patchHandler()); - instrumentation._wrap(this, 'delete', instrumentation._patchHandler()); - instrumentation._wrap(this, 'options', instrumentation._patchHandler()); - instrumentation._wrap(this, 'patch', instrumentation._patchHandler()); - instrumentation._wrap(this, 'all', instrumentation._patchHandler()); - instrumentation._wrap(this, 'on', instrumentation._patchOnHandler()); - instrumentation._wrap(this, 'use', instrumentation._patchMiddlewareHandler()); - } - } - - try { - moduleExports.Hono = WrappedHono; - } catch { - // This is a workaround for environments where direct assignment is not allowed. - return { ...moduleExports, Hono: WrappedHono }; - } - - return moduleExports; - } - - /** - * Patches the route handler to instrument it. - */ - private _patchHandler(): (original: HandlerInterface) => HandlerInterface { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const instrumentation = this; - - return function (original: HandlerInterface) { - return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { - if (typeof args[0] === 'string') { - const path = args[0]; - if (args.length === 1) { - return original.apply(this, [path]); - } - - const handlers = args.slice(1); - return original.apply(this, [ - path, - ...handlers.map(handler => instrumentation._wrapHandler(handler as Handler | MiddlewareHandler)), - ]); - } - - return original.apply( - this, - args.map(handler => instrumentation._wrapHandler(handler as Handler | MiddlewareHandler)), - ); - }; - }; - } - - /** - * Patches the 'on' handler to instrument it. - */ - private _patchOnHandler(): (original: OnHandlerInterface) => OnHandlerInterface { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const instrumentation = this; - - return function (original: OnHandlerInterface) { - return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { - const handlers = args.slice(2); - return original.apply(this, [ - ...args.slice(0, 2), - ...handlers.map(handler => instrumentation._wrapHandler(handler as Handler | MiddlewareHandler)), - ]); - }; - }; - } - - /** - * Patches the middleware handler to instrument it. - */ - private _patchMiddlewareHandler(): (original: MiddlewareHandlerInterface) => MiddlewareHandlerInterface { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const instrumentation = this; - - return function (original: MiddlewareHandlerInterface) { - return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { - if (typeof args[0] === 'string') { - const path = args[0]; - if (args.length === 1) { - return original.apply(this, [path]); - } - - const handlers = args.slice(1); - return original.apply(this, [ - path, - ...handlers.map(handler => instrumentation._wrapHandler(handler as MiddlewareHandler)), - ]); - } - - return original.apply( - this, - args.map(handler => instrumentation._wrapHandler(handler as MiddlewareHandler)), - ); - }; - }; - } - - /** - * Wraps a handler or middleware handler to apply instrumentation. - */ - private _wrapHandler(handler: Handler | MiddlewareHandler): Handler | MiddlewareHandler { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const instrumentation = this; - - return function (this: unknown, c: Context, next: Next) { - if (!instrumentation.isEnabled()) { - return handler.apply(this, [c, next]); - } - - const path = c.req.path; - const span = instrumentation.tracer.startSpan(path); - - return context.with(trace.setSpan(context.active(), span), () => { - return instrumentation._safeExecute( - () => { - const result = handler.apply(this, [c, next]); - if (isThenable(result)) { - return result.then(result => { - const type = instrumentation._determineHandlerType(result); - span.setAttributes({ - [AttributeNames.HONO_TYPE]: type, - [AttributeNames.HONO_NAME]: type === HonoTypes.REQUEST_HANDLER ? path : handler.name || 'anonymous', - }); - instrumentation.getConfig().responseHook?.(span); - return result; - }); - } else { - const type = instrumentation._determineHandlerType(result); - span.setAttributes({ - [AttributeNames.HONO_TYPE]: type, - [AttributeNames.HONO_NAME]: type === HonoTypes.REQUEST_HANDLER ? path : handler.name || 'anonymous', - }); - instrumentation.getConfig().responseHook?.(span); - return result; - } - }, - () => span.end(), - error => { - instrumentation._handleError(span, error); - span.end(); - }, - ); - }); - }; - } - - /** - * Safely executes a function and handles errors. - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private _safeExecute(execute: () => any, onSuccess: () => void, onFailure: (error: unknown) => void): () => any { - try { - const result = execute(); - - if (isThenable(result)) { - result.then( - () => onSuccess(), - (error: unknown) => onFailure(error), - ); - } else { - onSuccess(); - } - - return result; - } catch (error: unknown) { - onFailure(error); - throw error; - } - } - - /** - * Determines the handler type based on the result. - * @param result - * @private - */ - private _determineHandlerType(result: unknown): HonoTypes { - return result === undefined ? HonoTypes.MIDDLEWARE : HonoTypes.REQUEST_HANDLER; - } - - /** - * Handles errors by setting the span status and recording the exception. - */ - private _handleError(span: Span, error: unknown): void { - if (error instanceof Error) { - span.setStatus({ - code: SpanStatusCode.ERROR, - message: error.message, - }); - span.recordException(error); - } - } -} diff --git a/packages/node/src/integrations/tracing/hono/types.ts b/packages/node/src/integrations/tracing/hono/types.ts deleted file mode 100644 index 9873f80afa66..000000000000 --- a/packages/node/src/integrations/tracing/hono/types.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/request.ts#L30 -export type HonoRequest = { - path: string; - method: string; -}; - -// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/context.ts#L291 -export type Context = { - req: HonoRequest; - res: Response; - error: Error | undefined; -}; - -// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L36C1-L36C39 -export type Next = () => Promise; - -// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L73 -export type Handler = (c: Context, next: Next) => Promise | Response; - -// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L80 -export type MiddlewareHandler = (c: Context, next: Next) => Promise; - -// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L109 -export type HandlerInterface = { - (...handlers: (Handler | MiddlewareHandler)[]): HonoInstance; - (path: string, ...handlers: (Handler | MiddlewareHandler)[]): HonoInstance; -}; - -// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L1071 -export type OnHandlerInterface = { - (method: string | string[], path: string | string[], ...handlers: (Handler | MiddlewareHandler)[]): HonoInstance; -}; - -// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L679 -export type MiddlewareHandlerInterface = { - (...handlers: MiddlewareHandler[]): HonoInstance; - (path: string, ...handlers: MiddlewareHandler[]): HonoInstance; -}; - -// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/hono-base.ts#L99 -export interface HonoInstance { - get: HandlerInterface; - post: HandlerInterface; - put: HandlerInterface; - delete: HandlerInterface; - options: HandlerInterface; - patch: HandlerInterface; - all: HandlerInterface; - on: OnHandlerInterface; - use: MiddlewareHandlerInterface; -} - -export type Hono = new (...args: unknown[]) => HonoInstance; diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 0706f89dffdd..39684d37c63c 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -10,7 +10,6 @@ import { genericPoolIntegration, instrumentGenericPool } from './genericPool'; import { googleGenAIIntegration, instrumentGoogleGenAI } from './google-genai'; import { graphqlIntegration, instrumentGraphql } from './graphql'; import { hapiIntegration, instrumentHapi } from './hapi'; -import { honoIntegration, instrumentHono } from './hono'; import { instrumentKafka, kafkaIntegration } from './kafka'; import { instrumentKoa, koaIntegration } from './koa'; import { instrumentLangChain, langChainIntegration } from './langchain'; @@ -35,8 +34,6 @@ export function getAutoPerformanceIntegrations(): Integration[] { expressIntegration(), fastifyIntegration(), graphqlIntegration(), - // eslint-disable-next-line typescript/no-deprecated - honoIntegration(), mongoIntegration(), mongooseIntegration(), mysqlIntegration(), @@ -74,7 +71,6 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => instrumentExpress, instrumentFastifyV3, instrumentHapi, - instrumentHono, instrumentKafka, instrumentKoa, instrumentLruMemoizer,