1+ import type { Span } from '@opentelemetry/api' ;
2+ import { context , SpanStatusCode , trace } from '@opentelemetry/api' ;
13import { InstrumentationBase , InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation' ;
2- import type { HandlerInterface , Hono , HonoInstance , MiddlewareHandlerInterface , OnHandlerInterface } from './types' ;
4+ import { AttributeNames , HonoTypes } from './constants' ;
5+ import type {
6+ Context ,
7+ Handler ,
8+ HandlerInterface ,
9+ Hono ,
10+ HonoInstance ,
11+ MiddlewareHandler ,
12+ MiddlewareHandlerInterface ,
13+ Next ,
14+ OnHandlerInterface ,
15+ } from './types' ;
316
417const PACKAGE_NAME = '@sentry/instrumentation-hono' ;
518const PACKAGE_VERSION = '0.0.1' ;
@@ -50,10 +63,38 @@ export class HonoInstrumentation extends InstrumentationBase {
5063 * Patches the route handler to instrument it.
5164 */
5265 private _patchHandler ( ) : ( original : HandlerInterface ) => HandlerInterface {
66+ // eslint-disable-next-line @typescript-eslint/no-this-alias
67+ const instrumentation = this ;
68+
5369 return function ( original : HandlerInterface ) {
5470 return function wrappedHandler ( this : HonoInstance , ...args : unknown [ ] ) {
55- // TODO: Add OpenTelemetry tracing logic here
56- return original . apply ( this , args ) ;
71+ if ( typeof args [ 0 ] === 'string' ) {
72+ const path = args [ 0 ] ;
73+ if ( args . length === 1 ) {
74+ return original . apply ( this , [ path ] ) ;
75+ }
76+
77+ const handlers = args . slice ( 1 ) ;
78+ return original . apply ( this , [
79+ path ,
80+ ...handlers . map ( ( handler , index ) =>
81+ instrumentation . _wrapHandler (
82+ index + 1 === handlers . length ? HonoTypes . REQUEST_HANDLER : HonoTypes . MIDDLEWARE ,
83+ handler as Handler | MiddlewareHandler ,
84+ ) ,
85+ ) ,
86+ ] ) ;
87+ }
88+
89+ return original . apply (
90+ this ,
91+ args . map ( ( handler , index ) =>
92+ instrumentation . _wrapHandler (
93+ index + 1 === args . length ? HonoTypes . REQUEST_HANDLER : HonoTypes . MIDDLEWARE ,
94+ handler as Handler | MiddlewareHandler ,
95+ ) ,
96+ ) ,
97+ ) ;
5798 } ;
5899 } ;
59100 }
@@ -62,10 +103,21 @@ export class HonoInstrumentation extends InstrumentationBase {
62103 * Patches the 'on' handler to instrument it.
63104 */
64105 private _patchOnHandler ( ) : ( original : OnHandlerInterface ) => OnHandlerInterface {
106+ // eslint-disable-next-line @typescript-eslint/no-this-alias
107+ const instrumentation = this ;
108+
65109 return function ( original : OnHandlerInterface ) {
66110 return function wrappedHandler ( this : HonoInstance , ...args : unknown [ ] ) {
67- // TODO: Add OpenTelemetry tracing logic here
68- return original . apply ( this , args ) ;
111+ const handlers = args . slice ( 2 ) ;
112+ return original . apply ( this , [
113+ ...args . slice ( 0 , 2 ) ,
114+ ...handlers . map ( ( handler , index ) =>
115+ instrumentation . _wrapHandler (
116+ index + 1 === handlers . length ? HonoTypes . REQUEST_HANDLER : HonoTypes . MIDDLEWARE ,
117+ handler as Handler | MiddlewareHandler ,
118+ ) ,
119+ ) ,
120+ ] ) ;
69121 } ;
70122 } ;
71123 }
@@ -74,11 +126,107 @@ export class HonoInstrumentation extends InstrumentationBase {
74126 * Patches the middleware handler to instrument it.
75127 */
76128 private _patchMiddlewareHandler ( ) : ( original : MiddlewareHandlerInterface ) => MiddlewareHandlerInterface {
129+ // eslint-disable-next-line @typescript-eslint/no-this-alias
130+ const instrumentation = this ;
131+
77132 return function ( original : MiddlewareHandlerInterface ) {
78133 return function wrappedHandler ( this : HonoInstance , ...args : unknown [ ] ) {
79- // TODO: Add OpenTelemetry tracing logic here
80- return original . apply ( this , args ) ;
134+ if ( typeof args [ 0 ] === 'string' ) {
135+ const path = args [ 0 ] ;
136+ if ( args . length === 1 ) {
137+ return original . apply ( this , [ path ] ) ;
138+ }
139+
140+ const handlers = args . slice ( 1 ) ;
141+ return original . apply ( this , [
142+ path ,
143+ ...handlers . map ( handler =>
144+ instrumentation . _wrapHandler ( HonoTypes . MIDDLEWARE , handler as MiddlewareHandler ) ,
145+ ) ,
146+ ] ) ;
147+ }
148+
149+ return original . apply (
150+ this ,
151+ args . map ( handler => instrumentation . _wrapHandler ( HonoTypes . MIDDLEWARE , handler as MiddlewareHandler ) ) ,
152+ ) ;
81153 } ;
82154 } ;
83155 }
156+
157+ /**
158+ * Wraps a handler or middleware handler to apply instrumentation.
159+ */
160+ private _wrapHandler ( type : HonoTypes , handler : Handler | MiddlewareHandler ) : Handler | MiddlewareHandler {
161+ // eslint-disable-next-line @typescript-eslint/no-this-alias
162+ const instrumentation = this ;
163+
164+ return function ( this : unknown , c : Context , next : Next ) {
165+ if ( ! instrumentation . isEnabled ( ) ) {
166+ return handler . apply ( this , [ c , next ] ) ;
167+ }
168+
169+ const path = c . req . path ;
170+ const spanName = `${ type . replace ( '_' , ' ' ) } - ${ path } ` ;
171+ const span = instrumentation . tracer . startSpan ( spanName , {
172+ attributes : {
173+ [ AttributeNames . HONO_TYPE ] : type ,
174+ [ AttributeNames . HONO_NAME ] : type === 'request_handler' ? path : handler . name || 'anonymous' ,
175+ } ,
176+ } ) ;
177+
178+ return context . with ( trace . setSpan ( context . active ( ) , span ) , ( ) => {
179+ return instrumentation . _safeExecute (
180+ ( ) => handler . apply ( this , [ c , next ] ) ,
181+ ( ) => span . end ( ) ,
182+ error => {
183+ instrumentation . _handleError ( span , error ) ;
184+ span . end ( ) ;
185+ } ,
186+ ) ;
187+ } ) ;
188+ } ;
189+ }
190+
191+ /**
192+ * Safely executes a function and handles errors.
193+ */
194+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
195+ private _safeExecute ( execute : ( ) => any , onSuccess : ( ) => void , onFailure : ( error : unknown ) => void ) : ( ) => any {
196+ try {
197+ const result = execute ( ) ;
198+
199+ if (
200+ result &&
201+ typeof result === 'object' &&
202+ typeof Object . getOwnPropertyDescriptor ( result , 'then' ) ?. value === 'function'
203+ ) {
204+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
205+ result . then (
206+ ( ) => onSuccess ( ) ,
207+ ( error : unknown ) => onFailure ( error ) ,
208+ ) ;
209+ } else {
210+ onSuccess ( ) ;
211+ }
212+
213+ return result ;
214+ } catch ( error : unknown ) {
215+ onFailure ( error ) ;
216+ throw error ;
217+ }
218+ }
219+
220+ /**
221+ * Handles errors by setting the span status and recording the exception.
222+ */
223+ private _handleError ( span : Span , error : unknown ) : void {
224+ if ( error instanceof Error ) {
225+ span . setStatus ( {
226+ code : SpanStatusCode . ERROR ,
227+ message : error . message ,
228+ } ) ;
229+ span . recordException ( error ) ;
230+ }
231+ }
84232}
0 commit comments