diff --git a/packages/react/src/reactrouter.tsx b/packages/react/src/reactrouter.tsx index ff02556febef..e1a97443d756 100644 --- a/packages/react/src/reactrouter.tsx +++ b/packages/react/src/reactrouter.tsx @@ -12,11 +12,11 @@ import { getRootSpan, spanToJSON, } from '@sentry/core'; -import type { Integration, Span, StartSpanOptions, TransactionSource } from '@sentry/types'; +import type { Client, Integration, Span, TransactionSource } from '@sentry/types'; import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; -import type { Action, Location, ReactRouterInstrumentation } from './types'; +import type { Action, Location } from './types'; // We need to disable eslint no-explict-any because any is required for the // react-router typings. @@ -63,21 +63,15 @@ export function reactRouterV4BrowserTracingIntegration( afterAllSetup(client) { integration.afterAllSetup(client); - const startPageloadCallback = (startSpanOptions: StartSpanOptions): undefined => { - startBrowserTracingPageLoadSpan(client, startSpanOptions); - return undefined; - }; - - const startNavigationCallback = (startSpanOptions: StartSpanOptions): undefined => { - startBrowserTracingNavigationSpan(client, startSpanOptions); - return undefined; - }; - - const instrumentation = createReactRouterInstrumentation(history, 'reactrouter_v4', routes, matchPath); - - // Now instrument page load & navigation with correct settings - instrumentation(startPageloadCallback, instrumentPageLoad, false); - instrumentation(startNavigationCallback, false, instrumentNavigation); + instrumentReactRouter( + client, + instrumentPageLoad, + instrumentNavigation, + history, + 'reactrouter_v4', + routes, + matchPath, + ); }, }; } @@ -95,38 +89,35 @@ export function reactRouterV5BrowserTracingIntegration( instrumentNavigation: false, }); - const { history, routes, matchPath } = options; + const { history, routes, matchPath, instrumentPageLoad = true, instrumentNavigation = true } = options; return { ...integration, afterAllSetup(client) { integration.afterAllSetup(client); - const startPageloadCallback = (startSpanOptions: StartSpanOptions): undefined => { - startBrowserTracingPageLoadSpan(client, startSpanOptions); - return undefined; - }; - - const startNavigationCallback = (startSpanOptions: StartSpanOptions): undefined => { - startBrowserTracingNavigationSpan(client, startSpanOptions); - return undefined; - }; - - const instrumentation = createReactRouterInstrumentation(history, 'reactrouter_v5', routes, matchPath); - - // Now instrument page load & navigation with correct settings - instrumentation(startPageloadCallback, options.instrumentPageLoad, false); - instrumentation(startNavigationCallback, false, options.instrumentNavigation); + instrumentReactRouter( + client, + instrumentPageLoad, + instrumentNavigation, + history, + 'reactrouter_v5', + routes, + matchPath, + ); }, }; } -function createReactRouterInstrumentation( +function instrumentReactRouter( + client: Client, + instrumentPageLoad: boolean, + instrumentNavigation: boolean, history: RouterHistory, instrumentationName: string, allRoutes: RouteConfig[] = [], matchPath?: MatchPath, -): ReactRouterInstrumentation { +): void { function getInitPathName(): string | undefined { if (history && history.location) { return history.location.pathname; @@ -161,12 +152,11 @@ function createReactRouterInstrumentation( return [pathname, 'url']; } - return (customStartTransaction, startTransactionOnPageLoad = true, startTransactionOnLocationChange = true): void => { + if (instrumentPageLoad) { const initPathName = getInitPathName(); - - if (startTransactionOnPageLoad && initPathName) { + if (initPathName) { const [name, source] = normalizeTransactionName(initPathName); - customStartTransaction({ + startBrowserTracingPageLoadSpan(client, { name, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', @@ -175,23 +165,23 @@ function createReactRouterInstrumentation( }, }); } + } - if (startTransactionOnLocationChange && history.listen) { - history.listen((location, action) => { - if (action && (action === 'PUSH' || action === 'POP')) { - const [name, source] = normalizeTransactionName(location.pathname); - customStartTransaction({ - name, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.${instrumentationName}`, - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, - }, - }); - } - }); - } - }; + if (instrumentNavigation && history.listen) { + history.listen((location, action) => { + if (action && (action === 'PUSH' || action === 'POP')) { + const [name, source] = normalizeTransactionName(location.pathname); + startBrowserTracingNavigationSpan(client, { + name, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.${instrumentationName}`, + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, + }, + }); + } + }); + } } /** diff --git a/packages/react/src/reactrouterv3.ts b/packages/react/src/reactrouterv3.ts index 459589d91b93..4c5c084db791 100644 --- a/packages/react/src/reactrouterv3.ts +++ b/packages/react/src/reactrouterv3.ts @@ -9,16 +9,9 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, } from '@sentry/core'; -import type { - Integration, - SpanAttributes, - StartSpanOptions, - Transaction, - TransactionContext, - TransactionSource, -} from '@sentry/types'; +import type { Integration, TransactionSource } from '@sentry/types'; -import type { Location, ReactRouterInstrumentation } from './types'; +import type { Location } from './types'; // Many of the types below had to be mocked out to prevent typescript issues // these types are required for correct functionality. @@ -64,85 +57,46 @@ export function reactRouterV3BrowserTracingIntegration( afterAllSetup(client) { integration.afterAllSetup(client); - const startPageloadCallback = (startSpanOptions: StartSpanOptions): undefined => { - startBrowserTracingPageLoadSpan(client, startSpanOptions); - return undefined; - }; - - const startNavigationCallback = (startSpanOptions: StartSpanOptions): undefined => { - startBrowserTracingNavigationSpan(client, startSpanOptions); - return undefined; - }; - - const instrumentation = reactRouterV3Instrumentation(history, routes, match); - - // Now instrument page load & navigation with correct settings - instrumentation(startPageloadCallback, instrumentPageLoad, false); - instrumentation(startNavigationCallback, false, instrumentNavigation); - }, - }; -} - -/** - * Creates routing instrumentation for React Router v3 - * Works for React Router >= 3.2.0 and < 4.0.0 - * - * @param history object from the `history` library - * @param routes a list of all routes, should be - * @param match `Router.match` utility - */ -function reactRouterV3Instrumentation(history: HistoryV3, routes: Route[], match: Match): ReactRouterInstrumentation { - return ( - startTransaction: (context: TransactionContext) => Transaction | undefined, - startTransactionOnPageLoad: boolean = true, - startTransactionOnLocationChange: boolean = true, - ) => { - let activeTransaction: Transaction | undefined; - let prevName: string | undefined; - - // Have to use window.location because history.location might not be defined. - if (startTransactionOnPageLoad && WINDOW && WINDOW.location) { - normalizeTransactionName( - routes, - WINDOW.location as unknown as Location, - match, - (localName: string, source: ReactRouterV3TransactionSource = 'url') => { - prevName = localName; - activeTransaction = startTransaction({ - name: prevName, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v3', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, - }, - }); - }, - ); - } + if (instrumentPageLoad && WINDOW && WINDOW.location) { + normalizeTransactionName( + routes, + WINDOW.location as unknown as Location, + match, + (localName: string, source: ReactRouterV3TransactionSource = 'url') => { + startBrowserTracingPageLoadSpan(client, { + name: localName, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v3', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, + }, + }); + }, + ); + } - if (startTransactionOnLocationChange && history.listen) { - history.listen(location => { - if (location.action === 'PUSH' || location.action === 'POP') { - if (activeTransaction) { - activeTransaction.end(); + if (instrumentNavigation && history.listen) { + history.listen(location => { + if (location.action === 'PUSH' || location.action === 'POP') { + normalizeTransactionName( + routes, + location, + match, + (localName: string, source: TransactionSource = 'url') => { + startBrowserTracingNavigationSpan(client, { + name: localName, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v3', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, + }, + }); + }, + ); } - normalizeTransactionName(routes, location, match, (localName: string, source: TransactionSource = 'url') => { - prevName = localName; - - const attributes: SpanAttributes = { - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v3', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, - }; - - activeTransaction = startTransaction({ - name: prevName, - attributes, - }); - }); - } - }); - } + }); + } + }, }; } diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx index 73196bcfcc2a..cdf798cb84c7 100644 --- a/packages/react/src/reactrouterv6.tsx +++ b/packages/react/src/reactrouterv6.tsx @@ -1,4 +1,3 @@ -/* eslint-disable max-lines */ // Inspired from Donnie McNeal's solution: // https://gist.github.com/wontondon/e8c4bdf2888875e4c755712e99279536 @@ -13,17 +12,11 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getActiveSpan, + getClient, getRootSpan, spanToJSON, } from '@sentry/core'; -import type { - Integration, - Span, - StartSpanOptions, - Transaction, - TransactionContext, - TransactionSource, -} from '@sentry/types'; +import type { Client, Integration, Span, TransactionSource } from '@sentry/types'; import { getNumberOfUrlSegments, logger } from '@sentry/utils'; import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; @@ -46,17 +39,15 @@ import type { UseRoutes, } from './types'; -let activeTransaction: Transaction | undefined; - let _useEffect: UseEffect; let _useLocation: UseLocation; let _useNavigationType: UseNavigationType; let _createRoutesFromChildren: CreateRoutesFromChildren; let _matchRoutes: MatchRoutes; -let _customStartTransaction: (context: TransactionContext) => Transaction | undefined; -let _startTransactionOnLocationChange: boolean; let _stripBasename: boolean = false; +const CLIENTS_WITH_INSTRUMENT_NAVIGATION: Client[] = []; + interface ReactRouterOptions { useEffect: UseEffect; useLocation: UseLocation; @@ -95,11 +86,6 @@ export function reactRouterV6BrowserTracingIntegration( afterAllSetup(client) { integration.afterAllSetup(client); - const startNavigationCallback = (startSpanOptions: StartSpanOptions): undefined => { - startBrowserTracingNavigationSpan(client, startSpanOptions); - return undefined; - }; - const initPathName = WINDOW && WINDOW.location && WINDOW.location.pathname; if (instrumentPageLoad && initPathName) { startBrowserTracingPageLoadSpan(client, { @@ -119,52 +105,13 @@ export function reactRouterV6BrowserTracingIntegration( _createRoutesFromChildren = createRoutesFromChildren; _stripBasename = stripBasename || false; - _customStartTransaction = startNavigationCallback; - _startTransactionOnLocationChange = instrumentNavigation; + if (instrumentNavigation) { + CLIENTS_WITH_INSTRUMENT_NAVIGATION.push(client); + } }, }; } -/** - * @deprecated Use `reactRouterV6BrowserTracingIntegration()` instead. - */ -export function reactRouterV6Instrumentation( - useEffect: UseEffect, - useLocation: UseLocation, - useNavigationType: UseNavigationType, - createRoutesFromChildren: CreateRoutesFromChildren, - matchRoutes: MatchRoutes, - stripBasename?: boolean, -) { - return ( - customStartTransaction: (context: TransactionContext) => Transaction | undefined, - startTransactionOnPageLoad = true, - startTransactionOnLocationChange = true, - ): void => { - const initPathName = WINDOW && WINDOW.location && WINDOW.location.pathname; - if (startTransactionOnPageLoad && initPathName) { - activeTransaction = customStartTransaction({ - name: initPathName, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', - }, - }); - } - - _useEffect = useEffect; - _useLocation = useLocation; - _useNavigationType = useNavigationType; - _matchRoutes = matchRoutes; - _createRoutesFromChildren = createRoutesFromChildren; - _stripBasename = stripBasename || false; - - _customStartTransaction = customStartTransaction; - _startTransactionOnLocationChange = startTransactionOnLocationChange; - }; -} - /** * Strip the basename from a pathname if exists. * @@ -267,13 +214,15 @@ function handleNavigation( ): void { const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename); - if (_startTransactionOnLocationChange && (navigationType === 'PUSH' || navigationType === 'POP') && branches) { - if (activeTransaction) { - activeTransaction.end(); - } + const client = getClient(); + if (!client || !CLIENTS_WITH_INSTRUMENT_NAVIGATION.includes(client)) { + return; + } + if ((navigationType === 'PUSH' || navigationType === 'POP') && branches) { const [name, source] = getNormalizedName(routes, location, branches, basename); - activeTransaction = _customStartTransaction({ + + startBrowserTracingNavigationSpan(client, { name, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, @@ -286,18 +235,11 @@ function handleNavigation( // eslint-disable-next-line @typescript-eslint/no-explicit-any export function withSentryReactRouterV6Routing
, R extends React.FC
>(Routes: R): R { - if ( - !_useEffect || - !_useLocation || - !_useNavigationType || - !_createRoutesFromChildren || - !_matchRoutes || - !_customStartTransaction - ) { + if (!_useEffect || !_useLocation || !_useNavigationType || !_createRoutesFromChildren || !_matchRoutes) { DEBUG_BUILD && logger.warn(`reactRouterV6Instrumentation was unable to wrap Routes because of one or more missing parameters. useEffect: ${_useEffect}. useLocation: ${_useLocation}. useNavigationType: ${_useNavigationType}. - createRoutesFromChildren: ${_createRoutesFromChildren}. matchRoutes: ${_matchRoutes}. customStartTransaction: ${_customStartTransaction}.`); + createRoutesFromChildren: ${_createRoutesFromChildren}. matchRoutes: ${_matchRoutes}.`); return Routes; } @@ -337,7 +279,7 @@ export function withSentryReactRouterV6Routing
, R
}
export function wrapUseRoutes(origUseRoutes: UseRoutes): UseRoutes {
- if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes || !_customStartTransaction) {
+ if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes) {
DEBUG_BUILD &&
logger.warn(
'reactRouterV6Instrumentation was unable to wrap `useRoutes` because of one or more missing parameters.',
@@ -409,7 +351,7 @@ export function wrapCreateBrowserRouter<
router.subscribe((state: RouterState) => {
const location = state.location;
- if (_startTransactionOnLocationChange && (state.historyAction === 'PUSH' || state.historyAction === 'POP')) {
+ if (state.historyAction === 'PUSH' || state.historyAction === 'POP') {
handleNavigation(location, routes, state.historyAction, undefined, basename);
}
});
@@ -419,11 +361,6 @@ export function wrapCreateBrowserRouter<
}
function getActiveRootSpan(): Span | undefined {
- // Legacy behavior for "old" react router instrumentation
- if (activeTransaction) {
- return activeTransaction;
- }
-
const span = getActiveSpan();
const rootSpan = span ? getRootSpan(span) : undefined;
diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts
index fb0652acb036..ffb961bbfe3b 100644
--- a/packages/react/src/types.ts
+++ b/packages/react/src/types.ts
@@ -1,6 +1,5 @@
// Disabling `no-explicit-any` for the whole file as `any` has became common requirement.
/* eslint-disable @typescript-eslint/no-explicit-any */
-import type { Transaction, TransactionContext } from '@sentry/types';
export type Action = 'PUSH' | 'REPLACE' | 'POP';
@@ -9,12 +8,6 @@ export type Location = {
action?: Action;
} & Record