| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d8d190e commit 2d6425b
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,6 +18,7 @@ import { | |||
| 18 | 18 | import { startSpan } from '@sentry/core/browser'; | |
| 19 | 19 | import type { ClientInstrumentation } from 'react-router'; | |
| 20 | 20 | import { DEBUG_BUILD } from '../common/debug-build'; | |
| 21 | + import { routeProvider } from './routeCache'; | ||
| 21 | 22 | import { captureInstrumentationError, getPathFromRequest, getPattern, normalizeRoutePath } from '../common/utils'; | |
| 22 | 23 | import { | |
| 23 | 24 | resolveNavigateAbsoluteUrl, | |
@@ -252,7 +253,7 @@ export function createSentryClientInstrumentation( | |||
| 252 | 253 | const routePattern = pattern || urlPath; | |
| 253 | 254 | // Parameterize the active navigation root span. (Route hooks don't fire on initial | |
| 254 | 255 | // pageload, so this only affects navigations.) | |
| 255 | - updateRootSpanRoute(routePattern, !!pattern); | ||
| 256 | + updateRootSpanRoute(routePattern, !!pattern, urlPath); | ||
| 256 | 257 | ||
| 257 | 258 | await startSpan( | |
| 258 | 259 | { | |
@@ -279,7 +280,7 @@ export function createSentryClientInstrumentation( | |||
| 279 | 280 | const urlPath = getPathFromRequest(info.request); | |
| 280 | 281 | const pattern = normalizeRoutePath(getPattern(info)); | |
| 281 | 282 | const routePattern = pattern || urlPath; | |
| 282 | - updateRootSpanRoute(routePattern, !!pattern); | ||
| 283 | + updateRootSpanRoute(routePattern, !!pattern, urlPath); | ||
| 283 | 284 | ||
| 284 | 285 | await startSpan( | |
| 285 | 286 | { | |
@@ -365,13 +366,19 @@ export function createSentryClientInstrumentation( | |||
| 365 | 366 | ||
| 366 | 367 | /** | |
| 367 | 368 | * Updates the active navigation/pageload root span name with the parameterized route, so the | |
| 368 | - * transaction reflects the parameterized route pattern (e.g. `/users/:id`). | ||
| 369 | + * transaction reflects the parameterized route pattern (e.g. `/users/:id`), and records the route | ||
| 370 | + * against `urlPath` for the route provider. | ||
| 369 | 371 | */ | |
| 370 | - function updateRootSpanRoute(routeName: string, hasPattern: boolean): void { | ||
| 372 | + function updateRootSpanRoute(routeName: string, hasPattern: boolean, urlPath: string): void { | ||
| 371 | 373 | if (!hasPattern) { | |
| 372 | 374 | return; | |
| 373 | 375 | } | |
| 374 | 376 | ||
| 377 | + // The instrumentation API resolves routes the hydrated router subscription never sees, so feed the | ||
| 378 | + // provider from here too. Keyed on the request path rather than `location`, because route hooks | ||
| 379 | + // run during the navigation, before the URL commits. | ||
| 380 | + routeProvider.record(urlPath, routeName); | ||
| 381 | + | ||
| 375 | 382 | const activeSpan = getActiveSpan(); | |
| 376 | 383 | const rootSpan = activeSpan && getRootSpan(activeSpan); | |
| 377 | 384 | if (!rootSpan) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,8 +15,10 @@ import { | |||
| 15 | 15 | import type { DataRouter } from 'react-router'; | |
| 16 | 16 | import { DEBUG_BUILD } from '../common/debug-build'; | |
| 17 | 17 | import { isClientInstrumentationApiUsed } from './createClientInstrumentation'; | |
| 18 | + import { routeProvider } from './routeCache'; | ||
| 18 | 19 | import { | |
| 19 | 20 | finalizeNavigationSpanFromRouterState, | |
| 21 | + getMatchedRoute, | ||
| 20 | 22 | getParameterizedRoute, | |
| 21 | 23 | normalizePathname, | |
| 22 | 24 | resolveNavigateAbsoluteUrl, | |
@@ -46,6 +48,8 @@ export function instrumentHydratedRouter(): void { | |||
| 46 | 48 | ||
| 47 | 49 | if (router) { | |
| 48 | 50 | // The first time we hit the router, we try to update the pageload transaction | |
| 51 | + routeProvider.record(router.state.location.pathname, getMatchedRoute(router.state)); | ||
| 52 | + | ||
| 49 | 53 | const pageloadSpan = getActiveRootSpan(); | |
| 50 | 54 | ||
| 51 | 55 | if (pageloadSpan) { | |
@@ -121,6 +125,8 @@ export function instrumentHydratedRouter(): void { | |||
| 121 | 125 | // whose route info only became available after `trySubscribe`, e.g. lazy routes) with the | |
| 122 | 126 | // parameterized route. | |
| 123 | 127 | router.subscribe(newState => { | |
| 128 | + routeProvider.record(newState.location.pathname, getMatchedRoute(newState)); | ||
| 129 | + | ||
| 124 | 130 | const rootSpan = getActiveRootSpan(); | |
| 125 | 131 | ||
| 126 | 132 | if (!rootSpan) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,6 @@ | |||
| 1 | + import { createCachedRouteProvider } from '@sentry/core'; | ||
| 2 | + | ||
| 3 | + // The Data Router exposes its matches only through router state, and the package has no runtime | ||
| 4 | + // dependency on `react-router` to call `matchRoutes` with. The provider answers from routes the | ||
| 5 | + // hydrated router has already resolved instead. | ||
| 6 | + export const routeProvider = createCachedRouteProvider(); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,11 +1,13 @@ | |||
| 1 | 1 | import { browserTracingIntegration as originalBrowserTracingIntegration } from '@sentry/browser'; | |
| 2 | 2 | import type { Integration } from '@sentry/core'; | |
| 3 | + import { setRouteProvider } from '@sentry/core'; | ||
| 3 | 4 | import type { ClientInstrumentation } from 'react-router'; | |
| 4 | 5 | import { | |
| 5 | 6 | createSentryClientInstrumentation, | |
| 6 | 7 | type CreateSentryClientInstrumentationOptions, | |
| 7 | 8 | } from './createClientInstrumentation'; | |
| 8 | 9 | import { instrumentHydratedRouter } from './hydratedRouter'; | |
| 10 | + import { routeProvider } from './routeCache'; | ||
| 9 | 11 | ||
| 10 | 12 | /** | |
| 11 | 13 | * Options for the React Router tracing integration. | |
@@ -53,6 +55,10 @@ export function reactRouterTracingIntegration( | |||
| 53 | 55 | return { | |
| 54 | 56 | ...browserTracingIntegrationInstance, | |
| 55 | 57 | name: 'ReactRouterTracingIntegration', | |
| 58 | + setup(client) { | ||
| 59 | + setRouteProvider(routeProvider, client); | ||
| 60 | + browserTracingIntegrationInstance.setup?.(client); | ||
| 61 | + }, | ||
| 56 | 62 | afterAllSetup(client) { | |
| 57 | 63 | browserTracingIntegrationInstance.afterAllSetup(client); | |
| 58 | 64 | instrumentHydratedRouter(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -119,8 +119,20 @@ export function normalizePathname(pathname: string): string { | |||
| 119 | 119 | } | |
| 120 | 120 | ||
| 121 | 121 | export function getParameterizedRoute(routerState: RouterState): string { | |
| 122 | + return getMatchedRoute(routerState) ?? normalizePathname(routerState.location.pathname); | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + /** | ||
| 126 | + * The parameterized route the router matched, or `undefined` when nothing matched. | ||
| 127 | + * | ||
| 128 | + * Unlike {@link getParameterizedRoute} this does not fall back to the raw pathname, so callers that | ||
| 129 | + * must not treat a URL as a route (the route provider) can tell the two apart. | ||
| 130 | + */ | ||
| 131 | + export function getMatchedRoute(routerState: RouterState): string | undefined { | ||
| 122 | 132 | const lastMatch = routerState.matches[routerState.matches.length - 1]; | |
| 123 | - return normalizePathname(lastMatch?.route.path || routerState.location.pathname); | ||
| 133 | + const path = lastMatch?.route.path; | ||
| 134 | + | ||
| 135 | + return path ? normalizePathname(path) : undefined; | ||
| 124 | 136 | } | |
| 125 | 137 | ||
| 126 | 138 | /** | |
| Back | FazBrowse Home | New Git URL |
0 commit comments