1- import  {  extractRequestData ,  loadModule  }  from  '@sentry/utils' ; 
1+ import  {  getCurrentHub  }  from  '@sentry/hub' ; 
2+ import  {  flush  }  from  '@sentry/node' ; 
3+ import  {  hasTracingEnabled  }  from  '@sentry/tracing' ; 
4+ import  {  Transaction  }  from  '@sentry/types' ; 
5+ import  {  extractRequestData ,  loadModule ,  logger  }  from  '@sentry/utils' ; 
6+ import  *  as  domain  from  'domain' ; 
27
38import  { 
49  createRoutes , 
@@ -35,20 +40,26 @@ function wrapExpressRequestHandler(
3540    res : ExpressResponse , 
3641    next : ExpressNextFunction , 
3742  ) : Promise < void >  { 
38-     const  request  =  extractRequestData ( req ) ; 
43+     // eslint-disable-next-line @typescript-eslint/unbound-method 
44+     res . end  =  wrapEndMethod ( res . end ) ; 
3945
40-     if   ( ! request . url   ||   ! request . method )   { 
41-        return   origRequestHandler . call ( this ,   req ,   res ,   next ) ; 
42-     } 
46+     const   local   =   domain . create ( ) ; 
47+     local . add ( req ) ; 
48+     local . add ( res ) ; 
4349
44-     const  url  =  new  URL ( request . url ) ; 
50+     local . run ( async  ( )  =>  { 
51+       const  request  =  extractRequestData ( req ) ; 
52+       const  hub  =  getCurrentHub ( ) ; 
53+       const  options  =  hub . getClient ( ) ?. getOptions ( ) ; 
4554
46-     const  transaction  =  startRequestHandlerTransaction ( url ,  request . method ,  routes ,  pkg ) ; 
55+       if  ( ! options  ||  ! hasTracingEnabled ( options )  ||  ! request . url  ||  ! request . method )  { 
56+         return  origRequestHandler . call ( this ,  req ,  res ,  next ) ; 
57+       } 
4758
48-     await   origRequestHandler . call ( this ,   req ,   res ,   next ) ; 
49- 
50-     transaction ?. setHttpStatus ( res . statusCode ) ; 
51-     transaction ?. finish ( ) ; 
59+        const   url   =   new   URL ( request . url ) ; 
60+        startRequestHandlerTransaction ( url ,   request . method ,   routes ,   hub ,   pkg ) ; 
61+        await   origRequestHandler . call ( this ,   req ,   res ,   next ) ; 
62+     } ) ; 
5263  } ; 
5364} 
5465
@@ -57,11 +68,73 @@ function wrapExpressRequestHandler(
5768 */ 
5869export  function  wrapExpressCreateRequestHandler ( 
5970  origCreateRequestHandler : ExpressCreateRequestHandler , 
71+   // eslint-disable-next-line @typescript-eslint/no-explicit-any 
6072) : ( options : any )  =>  ExpressRequestHandler  { 
73+   // eslint-disable-next-line @typescript-eslint/no-explicit-any 
6174  return  function  ( this : unknown ,  options : any ) : ExpressRequestHandler  { 
6275    const  newBuild  =  instrumentBuild ( ( options  as  ExpressCreateRequestHandlerOptions ) . build ) ; 
6376    const  requestHandler  =  origCreateRequestHandler . call ( this ,  {  ...options ,  build : newBuild  } ) ; 
6477
6578    return  wrapExpressRequestHandler ( requestHandler ,  newBuild ) ; 
6679  } ; 
6780} 
81+ 
82+ export  type  AugmentedExpressResponse  =  ExpressResponse  &  { 
83+   __sentryTransaction ?: Transaction ; 
84+ } ; 
85+ 
86+ type  ResponseEndMethod  =  AugmentedExpressResponse [ 'end' ] ; 
87+ type  WrappedResponseEndMethod  =  AugmentedExpressResponse [ 'end' ] ; 
88+ 
89+ /** 
90+  * Wrap `res.end()` so that it closes the transaction and flushes events before letting the request finish. 
91+  * 
92+  * Note: This wraps a sync method with an async method. While in general that's not a great idea in terms of keeping 
93+  * things in the right order, in this case it's safe, because the native `.end()` actually *is* async, and its run 
94+  * actually *is* awaited, just manually so (which reflects the fact that the core of the request/response code in Node 
95+  * by far predates the introduction of `async`/`await`). When `.end()` is done, it emits the `prefinish` event, and 
96+  * only once that fires does request processing continue. See 
97+  * https://github.com/nodejs/node/commit/7c9b607048f13741173d397795bac37707405ba7. 
98+  * 
99+  * @param  origEnd The original `res.end()` method 
100+  * @returns  The wrapped version 
101+  */ 
102+ function  wrapEndMethod ( origEnd : ResponseEndMethod ) : WrappedResponseEndMethod  { 
103+   return  async  function  newEnd ( this : AugmentedExpressResponse ,  ...args : unknown [ ] )  { 
104+     await  finishSentryProcessing ( this ) ; 
105+ 
106+     return  origEnd . call ( this ,  ...args ) ; 
107+   } ; 
108+ } 
109+ 
110+ /** 
111+  * Close the open transaction (if any) and flush events to Sentry. 
112+  * 
113+  * @param  res The outgoing response for this request, on which the transaction is stored 
114+  */ 
115+ async  function  finishSentryProcessing ( res : AugmentedExpressResponse ) : Promise < void >  { 
116+   const  {  __sentryTransaction : transaction  }  =  res ; 
117+ 
118+   if  ( transaction )  { 
119+     transaction . setHttpStatus ( res . statusCode ) ; 
120+ 
121+     // Push `transaction.finish` to the next event loop so open spans have a better chance of finishing before the 
122+     // transaction closes, and make sure to wait until that's done before flushing events 
123+     await  new  Promise ( resolve  =>  { 
124+       setImmediate ( ( )  =>  { 
125+         transaction . finish ( ) ; 
126+         resolve ( ) ; 
127+       } ) ; 
128+     } ) ; 
129+   } 
130+ 
131+   // Flush the event queue to ensure that events get sent to Sentry before the response is finished and the lambda 
132+   // ends. If there was an error, rethrow it so that the normal exception-handling mechanisms can apply. 
133+   try  { 
134+     __DEBUG_BUILD__  &&  logger . log ( 'Flushing events...' ) ; 
135+     await  flush ( 2000 ) ; 
136+     __DEBUG_BUILD__  &&  logger . log ( 'Done flushing events' ) ; 
137+   }  catch  ( e )  { 
138+     __DEBUG_BUILD__  &&  logger . log ( 'Error while flushing events:\n' ,  e ) ; 
139+   } 
140+ } 
0 commit comments