11import { Context , MiddlewareHandler } from "@hono/hono" ;
22import { context , Span , SpanStatusCode , trace } from "@opentelemetry/api" ;
3+ import { getTracerProvider } from "@/lib/otel.ts" ;
34
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+ } ;
512
613export interface OtelConfig {
714 /**
@@ -29,95 +36,88 @@ export function otelTracing(config: OtelConfig = {}): MiddlewareHandler {
2936 return async ( c , next ) => {
3037 const path = c . req . path ;
3138 const method = c . req . method ;
39+ const route = c . req . routePath || path ;
3240
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+ ) ;
4349
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 ) ;
6754 }
68- }
6955
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+ } ) ;
8061 }
8162
82- // Include response body if configured
83- if ( config . includeResponseBody && c . res ?. body ) {
63+ // Include request body if configured
64+ if ( config . includeRequestBody ) {
8465 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 ) ;
9070 }
91- } catch ( e ) {
92- // Ignore if body can't be accessed
71+ } catch ( _ ) {
72+ /* swallow */
9373 }
9474 }
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- } ) ;
11075
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 ( ) ;
11479
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+ }
11884
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+ } ) ;
121121 } ;
122122}
123123
@@ -137,7 +137,7 @@ export async function createSpan<T>(
137137 attributes : Record < string , string | number | boolean > = { } ,
138138) : Promise < T > {
139139 const parentSpan = getCurrentSpan ( ) ;
140- const span = tracer . startSpan (
140+ const span = obtainTracer ( ) . startSpan (
141141 name ,
142142 undefined ,
143143 parentSpan ? trace . setSpan ( context . active ( ) , parentSpan ) : undefined ,
0 commit comments