@@ -11,60 +11,154 @@ import {
1111 SPAN_STATUS_OK ,
1212 startSpan ,
1313} from '@sentry/core' ;
14- import type { EventHandler , EventHandlerRequest , H3Event } from 'h3' ;
14+ import type {
15+ _ResponseMiddleware as ResponseMiddleware ,
16+ EventHandler ,
17+ EventHandlerObject ,
18+ EventHandlerRequest ,
19+ EventHandlerResponse ,
20+ H3Event ,
21+ } from 'h3' ;
1522
1623/**
1724 * Wraps a middleware handler with Sentry instrumentation.
1825 *
1926 * @param handler The middleware handler.
2027 * @param fileName The name of the middleware file.
2128 */
22- export function wrapMiddlewareHandler ( handler : EventHandler , fileName : string ) {
23- return async ( event : H3Event < EventHandlerRequest > ) => {
24- debug . log ( `Sentry middleware: ${ fileName } handling ${ event . path } ` ) ;
25-
26- const attributes = getSpanAttributes ( event , fileName ) ;
27-
28- return startSpan (
29- {
30- name : fileName ,
31- attributes,
32- } ,
33- async span => {
34- try {
35- const result = await handler ( event ) ;
36- span . setStatus ( { code : SPAN_STATUS_OK } ) ;
37-
38- return result ;
39- } catch ( error ) {
40- captureException ( error , {
41- mechanism : {
42- handled : false ,
43- type : attributes [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] ,
44- } ,
45- } ) ;
46-
47- // Re-throw the error to be handled by the caller
48- throw error ;
49- } finally {
50- await flushIfServerless ( ) ;
51- }
52- } ,
29+ export function wrapMiddlewareHandler < THandler extends EventHandler | EventHandlerObject > (
30+ handler : THandler ,
31+ fileName : string ,
32+ ) : THandler {
33+ if ( ! isEventHandlerObject ( handler ) ) {
34+ return wrapEventHandler ( handler , fileName ) as THandler ;
35+ }
36+
37+ const handlerObj = {
38+ ...handler ,
39+ handler : wrapEventHandler ( handler . handler , fileName ) ,
40+ } ;
41+
42+ if ( handlerObj . onRequest ) {
43+ handlerObj . onRequest = normalizeHandlers ( handlerObj . onRequest , ( h , index ) =>
44+ wrapEventHandler ( h , fileName , 'onRequest' , index ) ,
5345 ) ;
46+ }
47+
48+ if ( handlerObj . onBeforeResponse ) {
49+ handlerObj . onBeforeResponse = normalizeHandlers ( handlerObj . onBeforeResponse , ( h , index ) =>
50+ wrapResponseHandler ( h , fileName , index ) ,
51+ ) ;
52+ }
53+
54+ return handlerObj ;
55+ }
56+
57+ /**
58+ * Wraps a callable event handler with Sentry instrumentation.
59+ *
60+ * @param handler The event handler.
61+ * @param handlerName The name of the event handler to be used for the span name and logging.
62+ */
63+ function wrapEventHandler (
64+ handler : EventHandler ,
65+ middlewareName : string ,
66+ hookName ?: 'onRequest' ,
67+ index ?: number ,
68+ ) : EventHandler {
69+ return async ( event : H3Event < EventHandlerRequest > ) => {
70+ debug . log ( `Sentry middleware: ${ middlewareName } ${ hookName ? `.${ hookName } ` : '' } handling ${ event . path } ` ) ;
71+
72+ const attributes = getSpanAttributes ( event , middlewareName , hookName , index ) ;
73+
74+ return withSpan ( ( ) => handler ( event ) , attributes , middlewareName , hookName ) ;
75+ } ;
76+ }
77+
78+ /**
79+ * Wraps a middleware response handler with Sentry instrumentation.
80+ */
81+ function wrapResponseHandler ( handler : ResponseMiddleware , middlewareName : string , index ?: number ) : ResponseMiddleware {
82+ return async ( event : H3Event < EventHandlerRequest > , response : EventHandlerResponse ) => {
83+ debug . log ( `Sentry middleware: ${ middlewareName } .onBeforeResponse handling ${ event . path } ` ) ;
84+
85+ const attributes = getSpanAttributes ( event , middlewareName , 'onBeforeResponse' , index ) ;
86+
87+ return withSpan ( ( ) => handler ( event , response ) , attributes , middlewareName , 'onBeforeResponse' ) ;
5488 } ;
5589}
5690
91+ /**
92+ * Wraps a middleware or event handler execution with a span.
93+ */
94+ function withSpan < TResult > (
95+ handler : ( ) => TResult | Promise < TResult > ,
96+ attributes : SpanAttributes ,
97+ middlewareName : string ,
98+ hookName ?: 'handler' | 'onRequest' | 'onBeforeResponse' ,
99+ ) : Promise < TResult > {
100+ const spanName = hookName && hookName !== 'handler' ? `${ middlewareName } .${ hookName } ` : middlewareName ;
101+
102+ return startSpan (
103+ {
104+ name : spanName ,
105+ attributes,
106+ } ,
107+ async span => {
108+ try {
109+ const result = await handler ( ) ;
110+ span . setStatus ( { code : SPAN_STATUS_OK } ) ;
111+
112+ return result ;
113+ } catch ( error ) {
114+ captureException ( error , {
115+ mechanism : {
116+ handled : false ,
117+ type : attributes [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] ,
118+ } ,
119+ } ) ;
120+
121+ // Re-throw the error to be handled by the caller
122+ throw error ;
123+ } finally {
124+ await flushIfServerless ( ) ;
125+ }
126+ } ,
127+ ) ;
128+ }
129+
130+ /**
131+ * Takes a list of handlers and wraps them with the normalizer function.
132+ */
133+ function normalizeHandlers < T extends EventHandler | ResponseMiddleware > (
134+ handlers : T | T [ ] ,
135+ normalizer : ( h : T , index ?: number ) => T ,
136+ ) : T | T [ ] {
137+ return Array . isArray ( handlers ) ? handlers . map ( ( handler , index ) => normalizer ( handler , index ) ) : normalizer ( handlers ) ;
138+ }
139+
57140/**
58141 * Gets the span attributes for the middleware handler based on the event.
59142 */
60- function getSpanAttributes ( event : H3Event < EventHandlerRequest > , fileName : string ) : SpanAttributes {
143+ function getSpanAttributes (
144+ event : H3Event < EventHandlerRequest > ,
145+ middlewareName : string ,
146+ hookName ?: 'handler' | 'onRequest' | 'onBeforeResponse' ,
147+ index ?: number ,
148+ ) : SpanAttributes {
61149 const attributes : SpanAttributes = {
62150 [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'middleware.nuxt' ,
63151 [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'custom' ,
64- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.http.nuxt' ,
65- 'nuxt.middleware.name' : fileName ,
152+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.middleware.nuxt' ,
153+ 'nuxt.middleware.name' : middlewareName ,
154+ 'nuxt.middleware.hook.name' : hookName ?? 'handler' ,
66155 } ;
67156
157+ // Add index for array handlers
158+ if ( typeof index === 'number' ) {
159+ attributes [ 'nuxt.middleware.hook.index' ] = index ;
160+ }
161+
68162 // Add HTTP method
69163 if ( event . method ) {
70164 attributes [ 'http.request.method' ] = event . method ;
@@ -88,3 +182,10 @@ function getSpanAttributes(event: H3Event<EventHandlerRequest>, fileName: string
88182
89183 return attributes ;
90184}
185+
186+ /**
187+ * Checks if the handler is an event handler, util for type narrowing.
188+ */
189+ function isEventHandlerObject ( handler : EventHandler | EventHandlerObject ) : handler is EventHandlerObject {
190+ return typeof handler !== 'function' ;
191+ }
0 commit comments