diff --git a/packages/core/src/js/tracing/reactnavigation.ts b/packages/core/src/js/tracing/reactnavigation.ts index da4232e3fc..7179aab6d4 100644 --- a/packages/core/src/js/tracing/reactnavigation.ts +++ b/packages/core/src/js/tracing/reactnavigation.ts @@ -34,6 +34,30 @@ export const INTEGRATION_NAME = 'ReactNavigation'; const NAVIGATION_HISTORY_MAX_SIZE = 200; +/** + * Builds a full path from the navigation state by traversing nested navigators. + * For example, with nested navigators: "Home/Settings/Profile" + */ +function getPathFromState(state?: NavigationState): string | undefined { + if (!state) { + return undefined; + } + + const routeNames: string[] = []; + let currentState: NavigationState | undefined = state; + + while (currentState) { + const index: number = currentState.index ?? 0; + const route: NavigationRoute | undefined = currentState.routes[index]; + if (route?.name) { + routeNames.push(route.name); + } + currentState = route?.state; + } + + return routeNames.length > 0 ? routeNames.join('/') : undefined; +} + interface ReactNavigationIntegrationOptions { /** * How long the instrumentation will wait for the route to mount after a change has been initiated, @@ -319,16 +343,21 @@ export const reactNavigationIntegration = ({ const routeHasBeenSeen = recentRouteKeys.includes(route.key); - navigationProcessingSpan?.updateName(`Navigation dispatch to screen ${route.name} mounted`); + // Get the full navigation path for nested navigators + const navigationState = navigationContainer.getState(); + const fullRoutePath = getPathFromState(navigationState); + const routeName = fullRoutePath || route.name; + + navigationProcessingSpan?.updateName(`Navigation dispatch to screen ${routeName} mounted`); navigationProcessingSpan?.setStatus({ code: SPAN_STATUS_OK }); navigationProcessingSpan?.end(stateChangedTimestamp); navigationProcessingSpan = undefined; if (spanToJSON(latestNavigationSpan).description === DEFAULT_NAVIGATION_SPAN_NAME) { - latestNavigationSpan.updateName(route.name); + latestNavigationSpan.updateName(routeName); } latestNavigationSpan.setAttributes({ - 'route.name': route.name, + 'route.name': routeName, 'route.key': route.key, // TODO: filter PII params instead of dropping them all // 'route.params': {}, @@ -347,14 +376,14 @@ export const reactNavigationIntegration = ({ addBreadcrumb({ category: 'navigation', type: 'navigation', - message: `Navigation to ${route.name}`, + message: `Navigation to ${routeName}`, data: { from: previousRoute?.name, - to: route.name, + to: routeName, }, }); - tracing?.setCurrentRoute(route.name); + tracing?.setCurrentRoute(routeName); pushRecentRouteKey(route.key); latestRoute = route; @@ -412,11 +441,18 @@ export interface NavigationRoute { key: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any params?: Record; + state?: NavigationState; +} + +interface NavigationState { + index?: number; + routes: NavigationRoute[]; } interface NavigationContainer { addListener: (type: string, listener: (event?: unknown) => void) => void; getCurrentRoute: () => NavigationRoute; + getState: () => NavigationState | undefined; } /** diff --git a/samples/expo/app/_layout.tsx b/samples/expo/app/_layout.tsx index 121a5284a0..0bbb4a51eb 100644 --- a/samples/expo/app/_layout.tsx +++ b/samples/expo/app/_layout.tsx @@ -28,7 +28,7 @@ const navigationIntegration = Sentry.reactNavigationIntegration({ Sentry.init({ // Replace the example DSN below with your own DSN: - dsn: SENTRY_INTERNAL_DSN, + dsn: 'https://c96d5b6136315b7a6cef6230a38b4842@o4509786567475200.ingest.de.sentry.io/4510182962298960', debug: true, environment: 'dev', enableLogs: true,