1
1
import { Context , MiddlewareHandler } from "@hono/hono" ;
2
2
import { context , Span , SpanStatusCode , trace } from "@opentelemetry/api" ;
3
+ import { getTracerProvider } from "@/lib/otel.ts" ;
3
4
4
- const tracer = trace . getTracer ( "toolshed-middleware" , "1.0.0" ) ;
5
+ // Dynamically resolve the tracer so we don't capture the no-op global tracer
6
+ const obtainTracer = ( ) => {
7
+ const provider = getTracerProvider ( ) ;
8
+ return provider
9
+ ? provider . getTracer ( "toolshed-middleware" , "1.0.0" )
10
+ : trace . getTracer ( "toolshed-middleware" , "1.0.0" ) ;
11
+ } ;
5
12
6
13
export interface OtelConfig {
7
14
/**
@@ -29,95 +36,88 @@ export function otelTracing(config: OtelConfig = {}): MiddlewareHandler {
29
36
return async ( c , next ) => {
30
37
const path = c . req . path ;
31
38
const method = c . req . method ;
39
+ const route = c . req . routePath || path ;
32
40
33
- // Create and configure span
34
- const span = tracer . startSpan ( `${ method } ${ path } ` ) ;
35
- span . setAttribute ( "http.method" , method ) ;
36
- span . setAttribute ( "http.route" , path ) ;
37
- span . setAttribute ( "http.target" , path ) ;
38
- span . setAttribute ( "http.host" , c . req . header ( "host" ) || "unknown" ) ;
39
- span . setAttribute (
40
- "http.user_agent" ,
41
- c . req . header ( "user-agent" ) || "unknown" ,
42
- ) ;
41
+ await obtainTracer ( ) . startActiveSpan ( `${ method } ${ path } ` , async ( span ) => {
42
+ span . setAttribute ( "http.method" , method ) ;
43
+ span . setAttribute ( "http.route" , path + c . req . routePath ) ;
44
+ span . setAttribute ( "http.host" , c . req . header ( "host" ) || "unknown" ) ;
45
+ span . setAttribute (
46
+ "http.user_agent" ,
47
+ c . req . header ( "user-agent" ) || "unknown" ,
48
+ ) ;
43
49
44
- // Add request ID if it exists in headers
45
- const requestId = c . req . header ( "x-request-id" ) ;
46
- if ( requestId ) {
47
- span . setAttribute ( "http.request_id" , requestId ) ;
48
- }
49
-
50
- // Add custom attributes if configured
51
- if ( config . additionalAttributes ) {
52
- Object . entries ( config . additionalAttributes ) . forEach ( ( [ key , value ] ) => {
53
- span . setAttribute ( key , value ) ;
54
- } ) ;
55
- }
56
-
57
- // Include request body if configured
58
- if ( config . includeRequestBody ) {
59
- try {
60
- const bodyClone = c . req . raw . clone ( ) ;
61
- const body = await bodyClone . text ( ) ;
62
- if ( body ) {
63
- span . setAttribute ( "http.request.body" , body ) ;
64
- }
65
- } catch ( e ) {
66
- // Ignore if body can't be parsed
50
+ // Add request ID if it exists in headers
51
+ const requestId = c . req . header ( "x-request-id" ) ;
52
+ if ( requestId ) {
53
+ span . setAttribute ( "http.request_id" , requestId ) ;
67
54
}
68
- }
69
55
70
- try {
71
- // Set the span in context for nested spans
72
- await context . with ( trace . setSpan ( context . active ( ) , span ) , async ( ) => {
73
- // Need to capture response status after next() is called
74
- await next ( ) ;
75
- } ) ;
76
-
77
- // Capture status code from response if available
78
- if ( c . res ?. status ) {
79
- span . setAttribute ( "http.status_code" , c . res . status ) ;
56
+ // Add custom attributes if configured
57
+ if ( config . additionalAttributes ) {
58
+ Object . entries ( config . additionalAttributes ) . forEach ( ( [ key , value ] ) => {
59
+ span . setAttribute ( key , value ) ;
60
+ } ) ;
80
61
}
81
62
82
- // Include response body if configured
83
- if ( config . includeResponseBody && c . res ?. body ) {
63
+ // Include request body if configured
64
+ if ( config . includeRequestBody ) {
84
65
try {
85
- // Try to get the response body content
86
- const clonedResponse = c . res . clone ( ) ;
87
- const text = await clonedResponse . text ( ) ;
88
- if ( text ) {
89
- span . setAttribute ( "http.response.body" , text ) ;
66
+ const bodyClone = c . req . raw . clone ( ) ;
67
+ const body = await bodyClone . text ( ) ;
68
+ if ( body ) {
69
+ span . setAttribute ( "http.request.body" , body ) ;
90
70
}
91
- } catch ( e ) {
92
- // Ignore if body can't be accessed
71
+ } catch ( _ ) {
72
+ /* swallow */
93
73
}
94
74
}
95
- } catch ( error ) {
96
- // Handle errors
97
- span . setAttribute ( "error" , true ) ;
98
- span . setAttribute (
99
- "error.message" ,
100
- error instanceof Error ? error . message : String ( error ) ,
101
- ) ;
102
- span . setAttribute (
103
- "error.type" ,
104
- error instanceof Error ? error . name : "UnknownError" ,
105
- ) ;
106
- span . setStatus ( {
107
- code : SpanStatusCode . ERROR ,
108
- message : error instanceof Error ? error . message : String ( error ) ,
109
- } ) ;
110
75
111
- if ( error instanceof Error && error . stack ) {
112
- span . setAttribute ( "error.stack" , error . stack ) ;
113
- }
76
+ try {
77
+ // Execute the downstream handlers while this span is active
78
+ await next ( ) ;
114
79
115
- span . end ( ) ;
116
- throw error ;
117
- }
80
+ // Capture status code from response if available
81
+ if ( c . res ?. status ) {
82
+ span . setAttribute ( "http.status_code" , c . res . status ) ;
83
+ }
118
84
119
- // End the span
120
- span . end ( ) ;
85
+ // Include response body if configured
86
+ if ( config . includeResponseBody && c . res ?. body ) {
87
+ try {
88
+ const clonedResponse = c . res . clone ( ) ;
89
+ const text = await clonedResponse . text ( ) ;
90
+ if ( text ) {
91
+ span . setAttribute ( "http.response.body" , text ) ;
92
+ }
93
+ } catch ( _ ) {
94
+ /* swallow */
95
+ }
96
+ }
97
+ } catch ( error ) {
98
+ span . setAttribute ( "error" , true ) ;
99
+ span . setAttribute (
100
+ "error.message" ,
101
+ error instanceof Error ? error . message : String ( error ) ,
102
+ ) ;
103
+ span . setAttribute (
104
+ "error.type" ,
105
+ error instanceof Error ? error . name : "UnknownError" ,
106
+ ) ;
107
+ span . setStatus ( {
108
+ code : SpanStatusCode . ERROR ,
109
+ message : error instanceof Error ? error . message : String ( error ) ,
110
+ } ) ;
111
+
112
+ if ( error instanceof Error && error . stack ) {
113
+ span . setAttribute ( "error.stack" , error . stack ) ;
114
+ }
115
+
116
+ throw error ;
117
+ } finally {
118
+ span . end ( ) ;
119
+ }
120
+ } ) ;
121
121
} ;
122
122
}
123
123
@@ -137,7 +137,7 @@ export async function createSpan<T>(
137
137
attributes : Record < string , string | number | boolean > = { } ,
138
138
) : Promise < T > {
139
139
const parentSpan = getCurrentSpan ( ) ;
140
- const span = tracer . startSpan (
140
+ const span = obtainTracer ( ) . startSpan (
141
141
name ,
142
142
undefined ,
143
143
parentSpan ? trace . setSpan ( context . active ( ) , parentSpan ) : undefined ,
0 commit comments