1
1
/* eslint-disable max-lines */
2
2
import { Integration , Transaction } from '@sentry/types' ;
3
- import { CrossPlatformRequest , extractPathForTransaction , logger } from '@sentry/utils' ;
3
+ import { CrossPlatformRequest , extractPathForTransaction , isRegExp , logger } from '@sentry/utils' ;
4
4
5
5
type Method =
6
6
| 'all'
@@ -55,7 +55,7 @@ type ExpressRouter = Router & {
55
55
type Layer = {
56
56
match : ( path : string ) => boolean ;
57
57
handle_request : ( req : PatchedRequest , res : ExpressResponse , next : ( ) => void ) => void ;
58
- route ?: { path : string } ;
58
+ route ?: { path : string | RegExp } ;
59
59
path ?: string ;
60
60
} ;
61
61
@@ -273,9 +273,14 @@ function instrumentRouter(appOrRouter: ExpressRouter): void {
273
273
req . _reconstructedRoute = '' ;
274
274
}
275
275
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 || '' ;
279
284
280
285
// Normalize the partial route so that it doesn't contain leading or trailing slashes
281
286
// and exclude empty or '*' wildcard routes.
@@ -288,15 +293,17 @@ function instrumentRouter(appOrRouter: ExpressRouter): void {
288
293
. join ( '/' ) ;
289
294
290
295
// 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 ? '/' : '' } ` ;
293
299
}
294
300
295
301
// Now we check if we are in the "last" part of the route. We determine this by comparing the
296
302
// number of URL segments from the original URL to that of our reconstructed parameterized URL.
297
303
// 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
+
300
307
if ( urlLength === routeLength ) {
301
308
const transaction = res . __sentry_transaction ;
302
309
if ( transaction && transaction . metadata . source !== 'custom' ) {
@@ -311,3 +318,8 @@ function instrumentRouter(appOrRouter: ExpressRouter): void {
311
318
return originalProcessParams . call ( this , layer , called , req , res , done ) ;
312
319
} ;
313
320
}
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