-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(node): Add an instrumentation interface for Hono #17366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
0ddfe17
cb1a52e
21fba19
2a6cd6e
e694aae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
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); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,84 @@ | ||||||
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 } { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lowercase the hono variable so it's easier to differentiate between the type and the variable.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we are patching the class exported by hono, wouldn’t it need to be uppercase here, same as the exported class name? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah this makes sense, yes :) |
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias | ||||||
const instrumentation = this; | ||||||
|
||||||
moduleExports.Hono = class HonoWrapper 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()); | ||||||
} | ||||||
}; | ||||||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
return moduleExports; | ||||||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Hono Instrumentation Fails on Default ImportsThe Hono instrumentation only patches the named |
||||||
} | ||||||
|
||||||
/** | ||||||
* Patches the route handler to instrument it. | ||||||
*/ | ||||||
private _patchHandler(): (original: HandlerInterface) => HandlerInterface { | ||||||
return function (original: HandlerInterface) { | ||||||
return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { | ||||||
// 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) { | ||||||
return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { | ||||||
// 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) { | ||||||
return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { | ||||||
// TODO: Add OpenTelemetry tracing logic here | ||||||
return original.apply(this, args); | ||||||
}; | ||||||
}; | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<void>; | ||
|
||
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L73 | ||
export type Handler = (c: Context, next: Next) => Promise<Response> | Response; | ||
|
||
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L80 | ||
export type MiddlewareHandler = (c: Context, next: Next) => Promise<Response | void> | 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 = new (...args: unknown[]) => HonoInstance; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Hono Instrumentation Wrapping Issue
The
HonoInstrumentation
replacesmoduleExports.Hono
with a subclass that wraps instance methods in its constructor. AsInstrumentationNodeModuleDefinition
lacks anonModuleUnpatch
callback, disabling the instrumentation does not restore the originalHono
export. This causes newHono
instances to remain wrapped, resulting in non-reversible enable/disable behavior and potential wrapper stacking upon re-enablement.