1+ /* eslint-disable max-lines */
12// Inspired from Donnie McNeal's solution:
23// https://gist.github.com/wontondon/e8c4bdf2888875e4c755712e99279536
34
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' ;
727import { getNumberOfUrlSegments , logger } from '@sentry/utils' ;
828import hoistNonReactStatics from 'hoist-non-react-statics' ;
929import * as React from 'react' ;
@@ -37,10 +57,77 @@ let _customStartTransaction: (context: TransactionContext) => Transaction | unde
3757let _startTransactionOnLocationChange : boolean ;
3858let _stripBasename : boolean = false ;
3959
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+ }
43114
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+ */
44131export function reactRouterV6Instrumentation (
45132 useEffect : UseEffect ,
46133 useLocation : UseLocation ,
@@ -58,11 +145,10 @@ export function reactRouterV6Instrumentation(
58145 if ( startTransactionOnPageLoad && initPathName ) {
59146 activeTransaction = customStartTransaction ( {
60147 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' ,
66152 } ,
67153 } ) ;
68154 }
@@ -155,6 +241,7 @@ function getNormalizedName(
155241}
156242
157243function updatePageloadTransaction (
244+ activeRootSpan : Span | undefined ,
158245 location : Location ,
159246 routes : RouteObject [ ] ,
160247 matches ?: AgnosticDataRouteMatch ,
@@ -164,10 +251,10 @@ function updatePageloadTransaction(
164251 ? matches
165252 : ( _matchRoutes ( routes , location , basename ) as unknown as RouteMatch [ ] ) ;
166253
167- if ( activeTransaction && branches ) {
254+ if ( activeRootSpan && branches ) {
168255 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 ) ;
171258 }
172259}
173260
@@ -188,11 +275,10 @@ function handleNavigation(
188275 const [ name , source ] = getNormalizedName ( routes , location , branches , basename ) ;
189276 activeTransaction = _customStartTransaction ( {
190277 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' ,
196282 } ,
197283 } ) ;
198284 }
@@ -227,7 +313,7 @@ export function withSentryReactRouterV6Routing<P extends Record<string, any>, R
227313 const routes = _createRoutesFromChildren ( props . children ) as RouteObject [ ] ;
228314
229315 if ( isMountRenderPass ) {
230- updatePageloadTransaction ( location , routes ) ;
316+ updatePageloadTransaction ( getActiveRootSpan ( ) , location , routes ) ;
231317 isMountRenderPass = false ;
232318 } else {
233319 handleNavigation ( location , routes , navigationType ) ;
@@ -285,7 +371,7 @@ export function wrapUseRoutes(origUseRoutes: UseRoutes): UseRoutes {
285371 typeof stableLocationParam === 'string' ? { pathname : stableLocationParam } : stableLocationParam ;
286372
287373 if ( isMountRenderPass ) {
288- updatePageloadTransaction ( normalizedLocation , routes ) ;
374+ updatePageloadTransaction ( getActiveRootSpan ( ) , normalizedLocation , routes ) ;
289375 isMountRenderPass = false ;
290376 } else {
291377 handleNavigation ( normalizedLocation , routes , navigationType ) ;
@@ -312,25 +398,41 @@ export function wrapCreateBrowserRouter<
312398 const router = createRouterFunction ( routes , opts ) ;
313399 const basename = opts && opts . basename ;
314400
401+ const activeRootSpan = getActiveRootSpan ( ) ;
402+
315403 // The initial load ends when `createBrowserRouter` is called.
316404 // This is the earliest convenient time to update the transaction name.
317405 // 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 ) ;
320408 }
321409
322410 router . subscribe ( ( state : RouterState ) => {
323411 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' ) ) {
330413 handleNavigation ( location , routes , state . historyAction , undefined , basename ) ;
331414 }
332415 } ) ;
333416
334417 return router ;
335418 } ;
336419}
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