1
+ import type { Span } from '@opentelemetry/api' ;
2
+ import { context , SpanStatusCode , trace } from '@opentelemetry/api' ;
1
3
import { 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' ;
3
16
4
17
const PACKAGE_NAME = '@sentry/instrumentation-hono' ;
5
18
const PACKAGE_VERSION = '0.0.1' ;
@@ -50,10 +63,38 @@ export class HonoInstrumentation extends InstrumentationBase {
50
63
* Patches the route handler to instrument it.
51
64
*/
52
65
private _patchHandler ( ) : ( original : HandlerInterface ) => HandlerInterface {
66
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
67
+ const instrumentation = this ;
68
+
53
69
return function ( original : HandlerInterface ) {
54
70
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
+ ) ;
57
98
} ;
58
99
} ;
59
100
}
@@ -62,10 +103,21 @@ export class HonoInstrumentation extends InstrumentationBase {
62
103
* Patches the 'on' handler to instrument it.
63
104
*/
64
105
private _patchOnHandler ( ) : ( original : OnHandlerInterface ) => OnHandlerInterface {
106
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
107
+ const instrumentation = this ;
108
+
65
109
return function ( original : OnHandlerInterface ) {
66
110
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
+ ] ) ;
69
121
} ;
70
122
} ;
71
123
}
@@ -74,11 +126,106 @@ export class HonoInstrumentation extends InstrumentationBase {
74
126
* Patches the middleware handler to instrument it.
75
127
*/
76
128
private _patchMiddlewareHandler ( ) : ( original : MiddlewareHandlerInterface ) => MiddlewareHandlerInterface {
129
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
130
+ const instrumentation = this ;
131
+
77
132
return function ( original : MiddlewareHandlerInterface ) {
78
133
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
+ ) ;
81
153
} ;
82
154
} ;
83
155
}
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
+ }
210
+
211
+ onSuccess ( ) ;
212
+ return result ;
213
+ } catch ( error : unknown ) {
214
+ onFailure ( error ) ;
215
+ throw error ;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Handles errors by setting the span status and recording the exception.
221
+ */
222
+ private _handleError ( span : Span , error : unknown ) : void {
223
+ if ( error instanceof Error ) {
224
+ span . setStatus ( {
225
+ code : SpanStatusCode . ERROR ,
226
+ message : error . message ,
227
+ } ) ;
228
+ span . recordException ( error ) ;
229
+ }
230
+ }
84
231
}
0 commit comments