From 0ddfe176f2e6720162e0abdff00645d8b070a344 Mon Sep 17 00:00:00 2001 From: Karibash Date: Tue, 5 Aug 2025 21:47:08 +0900 Subject: [PATCH 1/5] feat(node): Add an instrumentation interface for Hono --- .../src/integrations/tracing/hono/enums.ts | 14 +++ .../src/integrations/tracing/hono/index.ts | 38 ++++++++ .../tracing/hono/instrumentation.ts | 94 +++++++++++++++++++ .../src/integrations/tracing/hono/types.ts | 50 ++++++++++ 4 files changed, 196 insertions(+) create mode 100644 packages/node/src/integrations/tracing/hono/enums.ts create mode 100644 packages/node/src/integrations/tracing/hono/index.ts create mode 100644 packages/node/src/integrations/tracing/hono/instrumentation.ts create mode 100644 packages/node/src/integrations/tracing/hono/types.ts diff --git a/packages/node/src/integrations/tracing/hono/enums.ts b/packages/node/src/integrations/tracing/hono/enums.ts new file mode 100644 index 000000000000..80667819e7de --- /dev/null +++ b/packages/node/src/integrations/tracing/hono/enums.ts @@ -0,0 +1,14 @@ +export enum AttributeNames { + HONO_TYPE = 'hono.type', + HONO_NAME = 'hono.name', +} + +export enum HonoTypes { + MIDDLEWARE = 'middleware', + REQUEST_HANDLER = 'request_handler', +} + +export enum HonoNames { + MIDDLEWARE = 'middleware', + REQUEST_HANDLER = 'request handler', +} diff --git a/packages/node/src/integrations/tracing/hono/index.ts b/packages/node/src/integrations/tracing/hono/index.ts new file mode 100644 index 000000000000..cf6a80ce4bd6 --- /dev/null +++ b/packages/node/src/integrations/tracing/hono/index.ts @@ -0,0 +1,38 @@ +import type { IntegrationFn } from '@sentry/core'; +import { defineIntegration } from '@sentry/core'; +import { generateInstrumentOnce } from '@sentry/node-core'; +import { HonoInstrumentation } from './instrumentation'; + +const INTEGRATION_NAME = 'Hono'; + +export const instrumentHono = generateInstrumentOnce( + INTEGRATION_NAME, + () => new HonoInstrumentation(), +); + +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/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.honoIntegration()], + * }) + * ``` + */ +export const honoIntegration = defineIntegration(_honoIntegration); diff --git a/packages/node/src/integrations/tracing/hono/instrumentation.ts b/packages/node/src/integrations/tracing/hono/instrumentation.ts new file mode 100644 index 000000000000..3a56d5c19974 --- /dev/null +++ b/packages/node/src/integrations/tracing/hono/instrumentation.ts @@ -0,0 +1,94 @@ +import { InstrumentationBase,InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; +import type { HandlerInterface, Hono, HonoInstance, MiddlewareHandlerInterface, OnHandlerInterface } from './types'; + +const PACKAGE_NAME = '@sentry/instrumentation-hono'; +const PACKAGE_VERSION = '0.0.1'; + +/** + * Hono instrumentation for OpenTelemetry + */ +export class HonoInstrumentation extends InstrumentationBase { + public constructor() { + super(PACKAGE_NAME, PACKAGE_VERSION, {}); + } + + /** + * 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; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + function Hono(this: HonoInstance, ...args: any): HonoInstance { + const app: HonoInstance = moduleExports.Hono.apply(this, args); + + instrumentation._wrap(app, 'get', instrumentation._patchHandler()); + instrumentation._wrap(app, 'post', instrumentation._patchHandler()); + instrumentation._wrap(app, 'put', instrumentation._patchHandler()); + instrumentation._wrap(app, 'delete', instrumentation._patchHandler()); + instrumentation._wrap(app, 'options', instrumentation._patchHandler()); + instrumentation._wrap(app, 'patch', instrumentation._patchHandler()); + instrumentation._wrap(app, 'all', instrumentation._patchHandler()); + instrumentation._wrap(app, 'on', instrumentation._patchOnHandler()); + instrumentation._wrap(app, 'use', instrumentation._patchMiddlewareHandler()); + + return app; + } + + moduleExports.Hono = Hono; + return moduleExports; + } + + /** + * Patches the route handler to instrument it. + */ + private _patchHandler(): (original: HandlerInterface) => HandlerInterface { + return function(original: HandlerInterface) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function wrappedHandler(this: HonoInstance, ...args: any) { + // TODO: Add OpenTelemetry tracing logic here + return original.apply(this, args); + }; + }; + } + + /** + * Patches the 'on' handler to instrument it. + */ + private _patchOnHandler(): (original: OnHandlerInterface) => OnHandlerInterface { + return function(original: OnHandlerInterface) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function wrappedHandler(this: HonoInstance, ...args: any) { + // TODO: Add OpenTelemetry tracing logic here + return original.apply(this, args); + }; + }; + } + + /** + * Patches the middleware handler to instrument it. + */ + private _patchMiddlewareHandler(): (original: MiddlewareHandlerInterface) => MiddlewareHandlerInterface { + return function(original: MiddlewareHandlerInterface) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function wrappedHandler(this: HonoInstance, ...args: any) { + // TODO: Add OpenTelemetry tracing logic here + return original.apply(this, args); + }; + }; + } +} diff --git a/packages/node/src/integrations/tracing/hono/types.ts b/packages/node/src/integrations/tracing/hono/types.ts new file mode 100644 index 000000000000..7e8e25642aa3 --- /dev/null +++ b/packages/node/src/integrations/tracing/hono/types.ts @@ -0,0 +1,50 @@ +// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/request.ts#L30 +export type HonoRequest = { + path: string; +}; + +// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/context.ts#L291 +export type Context = { + req: HonoRequest; +}; + +// 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 | Response | void; + +// 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 = () => HonoInstance; From cb1a52ee61dfd43a83517c1e74b33dae1f3032f8 Mon Sep 17 00:00:00 2001 From: Karibash Date: Mon, 11 Aug 2025 12:53:56 +0900 Subject: [PATCH 2/5] bugfix(node): Change Hono instrumentation to wrap via class extension instead of function replacement --- .../tracing/hono/instrumentation.ts | 32 +++++++++---------- .../src/integrations/tracing/hono/types.ts | 3 +- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/packages/node/src/integrations/tracing/hono/instrumentation.ts b/packages/node/src/integrations/tracing/hono/instrumentation.ts index 3a56d5c19974..bf7b0a4e8f57 100644 --- a/packages/node/src/integrations/tracing/hono/instrumentation.ts +++ b/packages/node/src/integrations/tracing/hono/instrumentation.ts @@ -32,24 +32,22 @@ export class HonoInstrumentation extends InstrumentationBase { // eslint-disable-next-line @typescript-eslint/no-this-alias const instrumentation = this; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function Hono(this: HonoInstance, ...args: any): HonoInstance { - const app: HonoInstance = moduleExports.Hono.apply(this, args); - - instrumentation._wrap(app, 'get', instrumentation._patchHandler()); - instrumentation._wrap(app, 'post', instrumentation._patchHandler()); - instrumentation._wrap(app, 'put', instrumentation._patchHandler()); - instrumentation._wrap(app, 'delete', instrumentation._patchHandler()); - instrumentation._wrap(app, 'options', instrumentation._patchHandler()); - instrumentation._wrap(app, 'patch', instrumentation._patchHandler()); - instrumentation._wrap(app, 'all', instrumentation._patchHandler()); - instrumentation._wrap(app, 'on', instrumentation._patchOnHandler()); - instrumentation._wrap(app, 'use', instrumentation._patchMiddlewareHandler()); - - return app; - } + moduleExports.Hono = class HonoWrapper extends moduleExports.Hono { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public constructor(...args: any[]) { + super(...args); - moduleExports.Hono = Hono; + 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()); + } + }; return moduleExports; } diff --git a/packages/node/src/integrations/tracing/hono/types.ts b/packages/node/src/integrations/tracing/hono/types.ts index 7e8e25642aa3..752b8e4c1ee6 100644 --- a/packages/node/src/integrations/tracing/hono/types.ts +++ b/packages/node/src/integrations/tracing/hono/types.ts @@ -47,4 +47,5 @@ export interface HonoInstance { use: MiddlewareHandlerInterface; } -export type Hono = () => HonoInstance; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type Hono = new (...args: any[]) => HonoInstance; From 21fba1958e529dd6c2fb23587e56ff434ad3293f Mon Sep 17 00:00:00 2001 From: Karibash Date: Mon, 11 Aug 2025 17:26:43 +0900 Subject: [PATCH 3/5] refactor(node): Use unknown instead of any --- .../src/integrations/tracing/hono/instrumentation.ts | 12 ++++-------- packages/node/src/integrations/tracing/hono/types.ts | 3 +-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/node/src/integrations/tracing/hono/instrumentation.ts b/packages/node/src/integrations/tracing/hono/instrumentation.ts index bf7b0a4e8f57..efe82812b46b 100644 --- a/packages/node/src/integrations/tracing/hono/instrumentation.ts +++ b/packages/node/src/integrations/tracing/hono/instrumentation.ts @@ -33,8 +33,7 @@ export class HonoInstrumentation extends InstrumentationBase { const instrumentation = this; moduleExports.Hono = class HonoWrapper extends moduleExports.Hono { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public constructor(...args: any[]) { + public constructor(...args: unknown[]) { super(...args); instrumentation._wrap(this, 'get', instrumentation._patchHandler()); @@ -56,8 +55,7 @@ export class HonoInstrumentation extends InstrumentationBase { */ private _patchHandler(): (original: HandlerInterface) => HandlerInterface { return function(original: HandlerInterface) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return function wrappedHandler(this: HonoInstance, ...args: any) { + return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { // TODO: Add OpenTelemetry tracing logic here return original.apply(this, args); }; @@ -69,8 +67,7 @@ export class HonoInstrumentation extends InstrumentationBase { */ private _patchOnHandler(): (original: OnHandlerInterface) => OnHandlerInterface { return function(original: OnHandlerInterface) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return function wrappedHandler(this: HonoInstance, ...args: any) { + return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { // TODO: Add OpenTelemetry tracing logic here return original.apply(this, args); }; @@ -82,8 +79,7 @@ export class HonoInstrumentation extends InstrumentationBase { */ private _patchMiddlewareHandler(): (original: MiddlewareHandlerInterface) => MiddlewareHandlerInterface { return function(original: MiddlewareHandlerInterface) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return function wrappedHandler(this: HonoInstance, ...args: any) { + return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { // TODO: Add OpenTelemetry tracing logic here return original.apply(this, args); }; diff --git a/packages/node/src/integrations/tracing/hono/types.ts b/packages/node/src/integrations/tracing/hono/types.ts index 752b8e4c1ee6..99dfc72ad777 100644 --- a/packages/node/src/integrations/tracing/hono/types.ts +++ b/packages/node/src/integrations/tracing/hono/types.ts @@ -47,5 +47,4 @@ export interface HonoInstance { use: MiddlewareHandlerInterface; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Hono = new (...args: any[]) => HonoInstance; +export type Hono = new (...args: unknown[]) => HonoInstance; From 2a6cd6e8795a942fc2aeff919ce2d851dc8dbb1b Mon Sep 17 00:00:00 2001 From: Karibash Date: Mon, 11 Aug 2025 21:56:53 +0900 Subject: [PATCH 4/5] refactor(node): Remove enums --- .../node/src/integrations/tracing/hono/enums.ts | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 packages/node/src/integrations/tracing/hono/enums.ts diff --git a/packages/node/src/integrations/tracing/hono/enums.ts b/packages/node/src/integrations/tracing/hono/enums.ts deleted file mode 100644 index 80667819e7de..000000000000 --- a/packages/node/src/integrations/tracing/hono/enums.ts +++ /dev/null @@ -1,14 +0,0 @@ -export enum AttributeNames { - HONO_TYPE = 'hono.type', - HONO_NAME = 'hono.name', -} - -export enum HonoTypes { - MIDDLEWARE = 'middleware', - REQUEST_HANDLER = 'request_handler', -} - -export enum HonoNames { - MIDDLEWARE = 'middleware', - REQUEST_HANDLER = 'request handler', -} From e694aae0f60f84489d1db674969204574b1576ce Mon Sep 17 00:00:00 2001 From: Karibash Date: Mon, 11 Aug 2025 22:03:34 +0900 Subject: [PATCH 5/5] chore(node): Format code with Prettier --- .../node/src/integrations/tracing/hono/index.ts | 5 +---- .../integrations/tracing/hono/instrumentation.ts | 14 +++++--------- .../node/src/integrations/tracing/hono/types.ts | 4 ++-- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/node/src/integrations/tracing/hono/index.ts b/packages/node/src/integrations/tracing/hono/index.ts index cf6a80ce4bd6..8876d26b829e 100644 --- a/packages/node/src/integrations/tracing/hono/index.ts +++ b/packages/node/src/integrations/tracing/hono/index.ts @@ -5,10 +5,7 @@ import { HonoInstrumentation } from './instrumentation'; const INTEGRATION_NAME = 'Hono'; -export const instrumentHono = generateInstrumentOnce( - INTEGRATION_NAME, - () => new HonoInstrumentation(), -); +export const instrumentHono = generateInstrumentOnce(INTEGRATION_NAME, () => new HonoInstrumentation()); const _honoIntegration = (() => { return { diff --git a/packages/node/src/integrations/tracing/hono/instrumentation.ts b/packages/node/src/integrations/tracing/hono/instrumentation.ts index efe82812b46b..81e062560051 100644 --- a/packages/node/src/integrations/tracing/hono/instrumentation.ts +++ b/packages/node/src/integrations/tracing/hono/instrumentation.ts @@ -1,4 +1,4 @@ -import { InstrumentationBase,InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; +import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; import type { HandlerInterface, Hono, HonoInstance, MiddlewareHandlerInterface, OnHandlerInterface } from './types'; const PACKAGE_NAME = '@sentry/instrumentation-hono'; @@ -17,11 +17,7 @@ export class HonoInstrumentation extends InstrumentationBase { */ public init(): InstrumentationNodeModuleDefinition[] { return [ - new InstrumentationNodeModuleDefinition( - 'hono', - ['>=4.0.0 <5'], - moduleExports => this._patch(moduleExports), - ), + new InstrumentationNodeModuleDefinition('hono', ['>=4.0.0 <5'], moduleExports => this._patch(moduleExports)), ]; } @@ -54,7 +50,7 @@ export class HonoInstrumentation extends InstrumentationBase { * Patches the route handler to instrument it. */ private _patchHandler(): (original: HandlerInterface) => HandlerInterface { - return function(original: HandlerInterface) { + return function (original: HandlerInterface) { return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { // TODO: Add OpenTelemetry tracing logic here return original.apply(this, args); @@ -66,7 +62,7 @@ export class HonoInstrumentation extends InstrumentationBase { * Patches the 'on' handler to instrument it. */ private _patchOnHandler(): (original: OnHandlerInterface) => OnHandlerInterface { - return function(original: OnHandlerInterface) { + return function (original: OnHandlerInterface) { return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { // TODO: Add OpenTelemetry tracing logic here return original.apply(this, args); @@ -78,7 +74,7 @@ export class HonoInstrumentation extends InstrumentationBase { * Patches the middleware handler to instrument it. */ private _patchMiddlewareHandler(): (original: MiddlewareHandlerInterface) => MiddlewareHandlerInterface { - return function(original: MiddlewareHandlerInterface) { + return function (original: MiddlewareHandlerInterface) { return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { // TODO: Add OpenTelemetry tracing logic here return original.apply(this, args); diff --git a/packages/node/src/integrations/tracing/hono/types.ts b/packages/node/src/integrations/tracing/hono/types.ts index 99dfc72ad777..3d7e057859f1 100644 --- a/packages/node/src/integrations/tracing/hono/types.ts +++ b/packages/node/src/integrations/tracing/hono/types.ts @@ -40,8 +40,8 @@ export interface HonoInstance { post: HandlerInterface; put: HandlerInterface; delete: HandlerInterface; - options:HandlerInterface; - patch:HandlerInterface; + options: HandlerInterface; + patch: HandlerInterface; all: HandlerInterface; on: OnHandlerInterface; use: MiddlewareHandlerInterface;