11/* eslint-disable max-lines */
22import { Integration , Transaction } from '@sentry/types' ;
3- import { CrossPlatformRequest , extractPathForTransaction , logger } from '@sentry/utils' ;
3+ import { CrossPlatformRequest , extractPathForTransaction , isRegExp , logger } from '@sentry/utils' ;
44
55type Method =
66 | 'all'
@@ -55,7 +55,7 @@ type ExpressRouter = Router & {
5555type Layer = {
5656 match : ( path : string ) => boolean ;
5757 handle_request : ( req : PatchedRequest , res : ExpressResponse , next : ( ) => void ) => void ;
58- route ?: { path : string } ;
58+ route ?: { path : string | RegExp } ;
5959 path ?: string ;
6060} ;
6161
@@ -273,9 +273,14 @@ function instrumentRouter(appOrRouter: ExpressRouter): void {
273273 req . _reconstructedRoute = '' ;
274274 }
275275
276- // If the layer's partial route has params, the route is stored in layer.route. Otherwise, the hardcoded path
277- // (i.e. a partial route without params) is stored in layer.path
278- const partialRoute = layer . route ?. path || layer . path || '' ;
276+ // If the layer's partial route has params, the route is stored in layer.route.
277+ // Since a route might be defined with a RegExp, we convert it toString to make sure we end up with a string
278+ const lrp = layer . route ?. path ;
279+ const isRegex = isRegExp ( lrp ) ;
280+ const layerRoutePath = isRegex ? lrp ?. toString ( ) : ( lrp as string ) ;
281+
282+ // Otherwise, the hardcoded path (i.e. a partial route without params) is stored in layer.path
283+ const partialRoute = layerRoutePath || layer . path || '' ;
279284
280285 // Normalize the partial route so that it doesn't contain leading or trailing slashes
281286 // and exclude empty or '*' wildcard routes.
@@ -288,15 +293,17 @@ function instrumentRouter(appOrRouter: ExpressRouter): void {
288293 . join ( '/' ) ;
289294
290295 // If we found a valid partial URL, we append it to the reconstructed route
291- if ( finalPartialRoute . length > 0 ) {
292- req . _reconstructedRoute += `/${ finalPartialRoute } ` ;
296+ if ( finalPartialRoute && finalPartialRoute . length > 0 ) {
297+ // If the partial route is from a regex route, we append a '/' to close the regex
298+ req . _reconstructedRoute += `/${ finalPartialRoute } ${ isRegex ? '/' : '' } ` ;
293299 }
294300
295301 // Now we check if we are in the "last" part of the route. We determine this by comparing the
296302 // number of URL segments from the original URL to that of our reconstructed parameterized URL.
297303 // If we've reached our final destination, we update the transaction name.
298- const urlLength = req . originalUrl ?. split ( '/' ) . filter ( s => s . length > 0 ) . length ;
299- const routeLength = req . _reconstructedRoute . split ( '/' ) . filter ( s => s . length > 0 ) . length ;
304+ const urlLength = getNumberOfUrlSegments ( req . originalUrl || '' ) ;
305+ const routeLength = getNumberOfUrlSegments ( req . _reconstructedRoute ) ;
306+
300307 if ( urlLength === routeLength ) {
301308 const transaction = res . __sentry_transaction ;
302309 if ( transaction && transaction . metadata . source !== 'custom' ) {
@@ -311,3 +318,8 @@ function instrumentRouter(appOrRouter: ExpressRouter): void {
311318 return originalProcessParams . call ( this , layer , called , req , res , done ) ;
312319 } ;
313320}
321+
322+ function getNumberOfUrlSegments ( url : string ) : number {
323+ // split at '/' or at '\/' to split regex urls correctly
324+ return url . split ( / \\ ? \/ / ) . filter ( s => s . length > 0 ) . length ;
325+ }
0 commit comments