Skip to content

Commit 3bf4a30

Browse files
authored
feat(node): Add an instrumentation interface for Hono (#17366)
This PR introduces the scaffolding for Sentry’s tracing integration with [Hono](https://hono.dev/) by adding interface-only implementations and wiring needed to verify the overall approach before filling in the tracing logic. ## Summary - Adds a new Hono Instrumentation (OpenTelemetry-based) which patches Hono route and middleware APIs. - Provides a honoIntegration exported via defineIntegration, plus an instrumentHono guard using generateInstrumentOnce. - Introduces minimal, vendored Hono types required for patching, and enums for consistent attribute naming. ## Intent & scope - Implemented interfaces only to validate the design direction. The goal is to confirm the wrapping points, attribute schema, and initialization flow before we add any span creation, context propagation, or attribute setting. - If this approach looks good, the next step is to ship a patch that implements the route handler tracing. That follow-up will include span start/finish, setting hono.type/hono.name, request path/method extraction, and trace context propagation. - No tests added in this PR because it only introduces the interface and structure. Tests will land together with the first functional instrumentation patch. ## Rationale There is an existing Hono OTel package ([@hono/otel](https://www.npmjs.com/package/@hono/otel)), but it currently lacks several features we need for a robust Sentry integration—especially middleware instrumentation and Sentry-specific integration points (e.g., seamless correlation with Sentry transactions/spans and future Sentry error handler wiring). Given these gaps, we’re proceeding with an in-repo implementation tailored for Sentry’s needs. ## Related Issue #15260
1 parent 90c10a9 commit 3bf4a30

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { IntegrationFn } from '@sentry/core';
2+
import { defineIntegration } from '@sentry/core';
3+
import { generateInstrumentOnce } from '@sentry/node-core';
4+
import { HonoInstrumentation } from './instrumentation';
5+
6+
const INTEGRATION_NAME = 'Hono';
7+
8+
export const instrumentHono = generateInstrumentOnce(INTEGRATION_NAME, () => new HonoInstrumentation());
9+
10+
const _honoIntegration = (() => {
11+
return {
12+
name: INTEGRATION_NAME,
13+
setupOnce() {
14+
instrumentHono();
15+
},
16+
};
17+
}) satisfies IntegrationFn;
18+
19+
/**
20+
* Adds Sentry tracing instrumentation for [Hono](https://hono.dev/).
21+
*
22+
* If you also want to capture errors, you need to call `setupHonoErrorHandler(app)` after you set up your Hono server.
23+
*
24+
* For more information, see the [hono documentation](https://docs.sentry.io/platforms/javascript/guides/hono/).
25+
*
26+
* @example
27+
* ```javascript
28+
* const Sentry = require('@sentry/node');
29+
*
30+
* Sentry.init({
31+
* integrations: [Sentry.honoIntegration()],
32+
* })
33+
* ```
34+
*/
35+
export const honoIntegration = defineIntegration(_honoIntegration);
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation';
2+
import type { HandlerInterface, Hono, HonoInstance, MiddlewareHandlerInterface, OnHandlerInterface } from './types';
3+
4+
const PACKAGE_NAME = '@sentry/instrumentation-hono';
5+
const PACKAGE_VERSION = '0.0.1';
6+
7+
/**
8+
* Hono instrumentation for OpenTelemetry
9+
*/
10+
export class HonoInstrumentation extends InstrumentationBase {
11+
public constructor() {
12+
super(PACKAGE_NAME, PACKAGE_VERSION, {});
13+
}
14+
15+
/**
16+
* Initialize the instrumentation.
17+
*/
18+
public init(): InstrumentationNodeModuleDefinition[] {
19+
return [
20+
new InstrumentationNodeModuleDefinition('hono', ['>=4.0.0 <5'], moduleExports => this._patch(moduleExports)),
21+
];
22+
}
23+
24+
/**
25+
* Patches the module exports to instrument Hono.
26+
*/
27+
private _patch(moduleExports: { Hono: Hono }): { Hono: Hono } {
28+
// eslint-disable-next-line @typescript-eslint/no-this-alias
29+
const instrumentation = this;
30+
31+
moduleExports.Hono = class HonoWrapper extends moduleExports.Hono {
32+
public constructor(...args: unknown[]) {
33+
super(...args);
34+
35+
instrumentation._wrap(this, 'get', instrumentation._patchHandler());
36+
instrumentation._wrap(this, 'post', instrumentation._patchHandler());
37+
instrumentation._wrap(this, 'put', instrumentation._patchHandler());
38+
instrumentation._wrap(this, 'delete', instrumentation._patchHandler());
39+
instrumentation._wrap(this, 'options', instrumentation._patchHandler());
40+
instrumentation._wrap(this, 'patch', instrumentation._patchHandler());
41+
instrumentation._wrap(this, 'all', instrumentation._patchHandler());
42+
instrumentation._wrap(this, 'on', instrumentation._patchOnHandler());
43+
instrumentation._wrap(this, 'use', instrumentation._patchMiddlewareHandler());
44+
}
45+
};
46+
return moduleExports;
47+
}
48+
49+
/**
50+
* Patches the route handler to instrument it.
51+
*/
52+
private _patchHandler(): (original: HandlerInterface) => HandlerInterface {
53+
return function (original: HandlerInterface) {
54+
return function wrappedHandler(this: HonoInstance, ...args: unknown[]) {
55+
// TODO: Add OpenTelemetry tracing logic here
56+
return original.apply(this, args);
57+
};
58+
};
59+
}
60+
61+
/**
62+
* Patches the 'on' handler to instrument it.
63+
*/
64+
private _patchOnHandler(): (original: OnHandlerInterface) => OnHandlerInterface {
65+
return function (original: OnHandlerInterface) {
66+
return function wrappedHandler(this: HonoInstance, ...args: unknown[]) {
67+
// TODO: Add OpenTelemetry tracing logic here
68+
return original.apply(this, args);
69+
};
70+
};
71+
}
72+
73+
/**
74+
* Patches the middleware handler to instrument it.
75+
*/
76+
private _patchMiddlewareHandler(): (original: MiddlewareHandlerInterface) => MiddlewareHandlerInterface {
77+
return function (original: MiddlewareHandlerInterface) {
78+
return function wrappedHandler(this: HonoInstance, ...args: unknown[]) {
79+
// TODO: Add OpenTelemetry tracing logic here
80+
return original.apply(this, args);
81+
};
82+
};
83+
}
84+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/request.ts#L30
2+
export type HonoRequest = {
3+
path: string;
4+
};
5+
6+
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/context.ts#L291
7+
export type Context = {
8+
req: HonoRequest;
9+
};
10+
11+
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L36C1-L36C39
12+
export type Next = () => Promise<void>;
13+
14+
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L73
15+
export type Handler = (c: Context, next: Next) => Promise<Response> | Response;
16+
17+
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L80
18+
export type MiddlewareHandler = (c: Context, next: Next) => Promise<Response | void> | Response | void;
19+
20+
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L109
21+
export type HandlerInterface = {
22+
(...handlers: (Handler | MiddlewareHandler)[]): HonoInstance;
23+
(path: string, ...handlers: (Handler | MiddlewareHandler)[]): HonoInstance;
24+
};
25+
26+
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L1071
27+
export type OnHandlerInterface = {
28+
(method: string | string[], path: string | string[], ...handlers: (Handler | MiddlewareHandler)[]): HonoInstance;
29+
};
30+
31+
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L679
32+
export type MiddlewareHandlerInterface = {
33+
(...handlers: MiddlewareHandler[]): HonoInstance;
34+
(path: string, ...handlers: MiddlewareHandler[]): HonoInstance;
35+
};
36+
37+
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/hono-base.ts#L99
38+
export interface HonoInstance {
39+
get: HandlerInterface;
40+
post: HandlerInterface;
41+
put: HandlerInterface;
42+
delete: HandlerInterface;
43+
options: HandlerInterface;
44+
patch: HandlerInterface;
45+
all: HandlerInterface;
46+
on: OnHandlerInterface;
47+
use: MiddlewareHandlerInterface;
48+
}
49+
50+
export type Hono = new (...args: unknown[]) => HonoInstance;

0 commit comments

Comments
 (0)