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..8876d26b829e --- /dev/null +++ b/packages/node/src/integrations/tracing/hono/index.ts @@ -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); 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..81e062560051 --- /dev/null +++ b/packages/node/src/integrations/tracing/hono/instrumentation.ts @@ -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 } { + // 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()); + } + }; + return moduleExports; + } + + /** + * 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); + }; + }; + } +} 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..3d7e057859f1 --- /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 = new (...args: unknown[]) => HonoInstance;