1
+ /* eslint-disable max-lines */
1
2
// Inspired from Donnie McNeal's solution:
2
3
// https://gist.github.com/wontondon/e8c4bdf2888875e4c755712e99279536
3
4
4
- import { WINDOW } from '@sentry/browser' ;
5
- import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core' ;
6
- import type { Transaction , TransactionContext , TransactionSource } from '@sentry/types' ;
5
+ import {
6
+ WINDOW ,
7
+ browserTracingIntegration ,
8
+ startBrowserTracingNavigationSpan ,
9
+ startBrowserTracingPageLoadSpan ,
10
+ } from '@sentry/browser' ;
11
+ import {
12
+ SEMANTIC_ATTRIBUTE_SENTRY_OP ,
13
+ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
14
+ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
15
+ getActiveSpan ,
16
+ getRootSpan ,
17
+ spanToJSON ,
18
+ } from '@sentry/core' ;
19
+ import type {
20
+ Integration ,
21
+ Span ,
22
+ StartSpanOptions ,
23
+ Transaction ,
24
+ TransactionContext ,
25
+ TransactionSource ,
26
+ } from '@sentry/types' ;
7
27
import { getNumberOfUrlSegments , logger } from '@sentry/utils' ;
8
28
import hoistNonReactStatics from 'hoist-non-react-statics' ;
9
29
import * as React from 'react' ;
@@ -37,10 +57,77 @@ let _customStartTransaction: (context: TransactionContext) => Transaction | unde
37
57
let _startTransactionOnLocationChange : boolean ;
38
58
let _stripBasename : boolean = false ;
39
59
40
- const SENTRY_TAGS = {
41
- 'routing.instrumentation' : 'react-router-v6' ,
42
- } ;
60
+ interface ReactRouterOptions {
61
+ useEffect : UseEffect ;
62
+ useLocation : UseLocation ;
63
+ useNavigationType : UseNavigationType ;
64
+ createRoutesFromChildren : CreateRoutesFromChildren ;
65
+ matchRoutes : MatchRoutes ;
66
+ stripBasename ?: boolean ;
67
+ }
68
+
69
+ /**
70
+ * A browser tracing integration that uses React Router v3 to instrument navigations.
71
+ * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options.
72
+ */
73
+ export function browserTracingReactRouterV6Integration (
74
+ options : Parameters < typeof browserTracingIntegration > [ 0 ] & ReactRouterOptions ,
75
+ ) : Integration {
76
+ const integration = browserTracingIntegration ( {
77
+ ...options ,
78
+ instrumentPageLoad : false ,
79
+ instrumentNavigation : false ,
80
+ } ) ;
81
+
82
+ const {
83
+ useEffect,
84
+ useLocation,
85
+ useNavigationType,
86
+ createRoutesFromChildren,
87
+ matchRoutes,
88
+ stripBasename,
89
+ instrumentPageLoad = true ,
90
+ instrumentNavigation = true ,
91
+ } = options ;
92
+
93
+ return {
94
+ ...integration ,
95
+ afterAllSetup ( client ) {
96
+ integration . afterAllSetup ( client ) ;
97
+
98
+ const startNavigationCallback = ( startSpanOptions : StartSpanOptions ) : undefined => {
99
+ startBrowserTracingNavigationSpan ( client , startSpanOptions ) ;
100
+ return undefined ;
101
+ } ;
102
+
103
+ const initPathName = WINDOW && WINDOW . location && WINDOW . location . pathname ;
104
+ if ( instrumentPageLoad && initPathName ) {
105
+ startBrowserTracingPageLoadSpan ( client , {
106
+ name : initPathName ,
107
+ attributes : {
108
+ [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'url' ,
109
+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'pageload' ,
110
+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.pageload.react.reactrouter_v6' ,
111
+ } ,
112
+ } ) ;
113
+ }
43
114
115
+ _useEffect = useEffect ;
116
+ _useLocation = useLocation ;
117
+ _useNavigationType = useNavigationType ;
118
+ _matchRoutes = matchRoutes ;
119
+ _createRoutesFromChildren = createRoutesFromChildren ;
120
+ _stripBasename = stripBasename || false ;
121
+
122
+ _customStartTransaction = startNavigationCallback ;
123
+ _startTransactionOnLocationChange = instrumentNavigation ;
124
+ } ,
125
+ } ;
126
+ }
127
+
128
+ /**
129
+ * @deprecated Use `browserTracingReactRouterV6Integration()` instead.
130
+ */
44
131
export function reactRouterV6Instrumentation (
45
132
useEffect : UseEffect ,
46
133
useLocation : UseLocation ,
@@ -58,11 +145,10 @@ export function reactRouterV6Instrumentation(
58
145
if ( startTransactionOnPageLoad && initPathName ) {
59
146
activeTransaction = customStartTransaction ( {
60
147
name : initPathName ,
61
- op : 'pageload' ,
62
- origin : 'auto.pageload.react.reactrouterv6' ,
63
- tags : SENTRY_TAGS ,
64
- metadata : {
65
- source : 'url' ,
148
+ attributes : {
149
+ [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'url' ,
150
+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'pageload' ,
151
+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.pageload.react.reactrouter_v6' ,
66
152
} ,
67
153
} ) ;
68
154
}
@@ -155,6 +241,7 @@ function getNormalizedName(
155
241
}
156
242
157
243
function updatePageloadTransaction (
244
+ activeRootSpan : Span | undefined ,
158
245
location : Location ,
159
246
routes : RouteObject [ ] ,
160
247
matches ?: AgnosticDataRouteMatch ,
@@ -164,10 +251,10 @@ function updatePageloadTransaction(
164
251
? matches
165
252
: ( _matchRoutes ( routes , location , basename ) as unknown as RouteMatch [ ] ) ;
166
253
167
- if ( activeTransaction && branches ) {
254
+ if ( activeRootSpan && branches ) {
168
255
const [ name , source ] = getNormalizedName ( routes , location , branches , basename ) ;
169
- activeTransaction . updateName ( name ) ;
170
- activeTransaction . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_SOURCE , source ) ;
256
+ activeRootSpan . updateName ( name ) ;
257
+ activeRootSpan . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_SOURCE , source ) ;
171
258
}
172
259
}
173
260
@@ -188,11 +275,10 @@ function handleNavigation(
188
275
const [ name , source ] = getNormalizedName ( routes , location , branches , basename ) ;
189
276
activeTransaction = _customStartTransaction ( {
190
277
name,
191
- op : 'navigation' ,
192
- origin : 'auto.navigation.react.reactrouterv6' ,
193
- tags : SENTRY_TAGS ,
194
- metadata : {
195
- source,
278
+ attributes : {
279
+ [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : source ,
280
+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'navigation' ,
281
+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.navigation.react.reactrouter_v6' ,
196
282
} ,
197
283
} ) ;
198
284
}
@@ -227,7 +313,7 @@ export function withSentryReactRouterV6Routing<P extends Record<string, any>, R
227
313
const routes = _createRoutesFromChildren ( props . children ) as RouteObject [ ] ;
228
314
229
315
if ( isMountRenderPass ) {
230
- updatePageloadTransaction ( location , routes ) ;
316
+ updatePageloadTransaction ( getActiveRootSpan ( ) , location , routes ) ;
231
317
isMountRenderPass = false ;
232
318
} else {
233
319
handleNavigation ( location , routes , navigationType ) ;
@@ -285,7 +371,7 @@ export function wrapUseRoutes(origUseRoutes: UseRoutes): UseRoutes {
285
371
typeof stableLocationParam === 'string' ? { pathname : stableLocationParam } : stableLocationParam ;
286
372
287
373
if ( isMountRenderPass ) {
288
- updatePageloadTransaction ( normalizedLocation , routes ) ;
374
+ updatePageloadTransaction ( getActiveRootSpan ( ) , normalizedLocation , routes ) ;
289
375
isMountRenderPass = false ;
290
376
} else {
291
377
handleNavigation ( normalizedLocation , routes , navigationType ) ;
@@ -312,25 +398,41 @@ export function wrapCreateBrowserRouter<
312
398
const router = createRouterFunction ( routes , opts ) ;
313
399
const basename = opts && opts . basename ;
314
400
401
+ const activeRootSpan = getActiveRootSpan ( ) ;
402
+
315
403
// The initial load ends when `createBrowserRouter` is called.
316
404
// This is the earliest convenient time to update the transaction name.
317
405
// Callbacks to `router.subscribe` are not called for the initial load.
318
- if ( router . state . historyAction === 'POP' && activeTransaction ) {
319
- updatePageloadTransaction ( router . state . location , routes , undefined , basename ) ;
406
+ if ( router . state . historyAction === 'POP' && activeRootSpan ) {
407
+ updatePageloadTransaction ( activeRootSpan , router . state . location , routes , undefined , basename ) ;
320
408
}
321
409
322
410
router . subscribe ( ( state : RouterState ) => {
323
411
const location = state . location ;
324
-
325
- if (
326
- _startTransactionOnLocationChange &&
327
- ( state . historyAction === 'PUSH' || state . historyAction === 'POP' ) &&
328
- activeTransaction
329
- ) {
412
+ if ( _startTransactionOnLocationChange && ( state . historyAction === 'PUSH' || state . historyAction === 'POP' ) ) {
330
413
handleNavigation ( location , routes , state . historyAction , undefined , basename ) ;
331
414
}
332
415
} ) ;
333
416
334
417
return router ;
335
418
} ;
336
419
}
420
+
421
+ function getActiveRootSpan ( ) : Span | undefined {
422
+ // Legacy behavior for "old" react router instrumentation
423
+ if ( activeTransaction ) {
424
+ return activeTransaction ;
425
+ }
426
+
427
+ const span = getActiveSpan ( ) ;
428
+ const rootSpan = span ? getRootSpan ( span ) : undefined ;
429
+
430
+ if ( ! rootSpan ) {
431
+ return undefined ;
432
+ }
433
+
434
+ const op = spanToJSON ( rootSpan ) . op ;
435
+
436
+ // Only use this root span if it is a pageload or navigation span
437
+ return op === 'navigation' || op === 'pageload' ? rootSpan : undefined ;
438
+ }
0 commit comments