Skip to content

Commit b40658d

Browse files
Lms24cursoragent
andcommitted
feat: Set url.template on pageload and navigation spans across framework SDKs
Set the `url.template` attribute (the parameterized route, e.g. `/users/:id`) on pageload and navigation root spans whenever the routing instrumentation resolves a route and sets `sentry.source` to `route`. This builds on the base browser change that adds `url.path`/`url.full`. Covers: Angular, Astro, Next.js (app + pages router), React Router (framework, v3, v6/v7 compat), TanStack Router (React/Vue/Solid), Remix, SolidRouter, SvelteKit and Vue Router (also used by Nuxt). Co-Authored-By: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 7c4bfcb commit b40658d

15 files changed

Lines changed: 51 additions & 0 deletions

File tree

packages/angular/src/tracing.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export function _updateSpanAttributesForParametrizedUrl(route: string, span?: Sp
6060
if (span && attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'url') {
6161
span.updateName(route);
6262
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
63+
span.setAttribute('url.template', route);
6364
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, `auto.${spanToJSON(span).op}.angular`);
6465
}
6566
}

packages/astro/src/client/browserTracingIntegration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export function browserTracingIntegration(
4848
attributes: {
4949
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
5050
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.astro',
51+
...(source === 'route' && { 'url.template': name }),
5152
},
5253
});
5354
}

packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export function appRouterInstrumentPageLoad(client: Client): void {
5454
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
5555
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.nextjs.app_router_instrumentation',
5656
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? 'route' : 'url',
57+
...(parameterizedPathname && { 'url.template': parameterizedPathname }),
5758
},
5859
});
5960
}
@@ -116,6 +117,7 @@ export function appRouterInstrumentNavigation(client: Client): void {
116117
currentNavigationSpan.setAttributes({
117118
'navigation.type': `router.${navigationType}`,
118119
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? 'route' : 'url',
120+
...(parameterizedPathname && { 'url.template': parameterizedPathname }),
119121
});
120122
currentRouterPatchingNavigationSpanRef.current = undefined;
121123
} else {
@@ -126,6 +128,7 @@ export function appRouterInstrumentNavigation(client: Client): void {
126128
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.nextjs.app_router_instrumentation',
127129
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? 'route' : 'url',
128130
'navigation.type': `router.${navigationType}`,
131+
...(parameterizedPathname && { 'url.template': parameterizedPathname }),
129132
},
130133
});
131134
}
@@ -140,13 +143,17 @@ export function appRouterInstrumentNavigation(client: Client): void {
140143
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
141144
parameterizedPathname ? 'route' : 'url',
142145
);
146+
if (parameterizedPathname) {
147+
currentRouterPatchingNavigationSpanRef.current.setAttribute('url.template', parameterizedPathname);
148+
}
143149
} else {
144150
currentRouterPatchingNavigationSpanRef.current = startBrowserTracingNavigationSpan(client, {
145151
name: parameterizedPathname ?? pathname,
146152
attributes: {
147153
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.nextjs.app_router_instrumentation',
148154
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? 'route' : 'url',
149155
'navigation.type': 'browser.popstate',
156+
...(parameterizedPathname && { 'url.template': parameterizedPathname }),
150157
},
151158
});
152159
}
@@ -245,6 +252,7 @@ function patchRouter(client: Client, router: NextRouter, currentNavigationSpanRe
245252
attributes: {
246253
...transactionAttributes,
247254
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? 'route' : 'url',
255+
...(parameterizedPathname && { 'url.template': parameterizedPathname }),
248256
},
249257
});
250258

packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export function pagesRouterInstrumentPageLoad(client: Client): void {
129129
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
130130
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.nextjs.pages_router_instrumentation',
131131
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: route ? 'route' : 'url',
132+
...(route && { 'url.template': route }),
132133
...(params && { ...params }),
133134
},
134135
},
@@ -166,6 +167,7 @@ export function pagesRouterInstrumentNavigation(client: Client): void {
166167
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
167168
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.nextjs.pages_router_instrumentation',
168169
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: spanSource,
170+
...(spanSource === 'route' && { 'url.template': newLocation }),
169171
},
170172
});
171173
});

packages/react-router/src/client/createClientInstrumentation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ function updateRootSpanRoute(routeName: string, hasPattern: boolean): void {
359359

360360
updateSpanName(rootSpan, routeName);
361361
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
362+
rootSpan.setAttribute('url.template', routeName);
362363
}
363364

364365
/**

packages/react-router/src/client/hydratedRouter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export function instrumentHydratedRouter(): void {
5050
pageloadSpan.setAttributes({
5151
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
5252
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react_router',
53+
'url.template': parameterizePageloadRoute,
5354
});
5455
}
5556
}
@@ -99,6 +100,7 @@ export function instrumentHydratedRouter(): void {
99100
) {
100101
rootSpan.updateName(parameterizedRoute);
101102
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
103+
rootSpan.setAttribute('url.template', parameterizedRoute);
102104
}
103105
});
104106
return true;

packages/react/src/reactrouter-compat-utils/instrumentation.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@ export function updateNavigationSpan(
398398
if (isImprovement) {
399399
activeRootSpan.updateName(name);
400400
activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
401+
if (source === 'route') {
402+
activeRootSpan.setAttribute('url.template', name);
403+
}
401404

402405
// Only mark as finalized for non-wildcard route names (allows URL→route upgrades).
403406
if (!transactionNameHasWildcard(name) && source === 'route') {
@@ -997,6 +1000,9 @@ export function handleNavigation(opts: {
9971000
// Update existing real span from wildcard to parameterized route name
9981001
trackedNav.span.updateName(name);
9991002
trackedNav.span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source as 'route' | 'url' | 'custom');
1003+
if (source === 'route') {
1004+
trackedNav.span.setAttribute('url.template', name);
1005+
}
10001006
addNonEnumerableProperty(
10011007
trackedNav.span as { __sentry_navigation_name_set__?: boolean },
10021008
'__sentry_navigation_name_set__',
@@ -1032,6 +1038,7 @@ export function handleNavigation(opts: {
10321038
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
10331039
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
10341040
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.reactrouter${version ? `_v${version}` : ''}`,
1041+
...(source === 'route' && { 'url.template': placeholderEntry.routeName }),
10351042
},
10361043
});
10371044
} catch (e) {
@@ -1120,6 +1127,9 @@ function updatePageloadTransaction({
11201127
if (activeRootSpan) {
11211128
activeRootSpan.updateName(name);
11221129
activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
1130+
if (source === 'route') {
1131+
activeRootSpan.setAttribute('url.template', name);
1132+
}
11231133

11241134
// Patch span.end() to ensure we update the name one last time before the span is sent
11251135
patchSpanEnd(activeRootSpan, location, routes, basename, 'pageload');
@@ -1216,6 +1226,9 @@ function tryUpdateSpanNameBeforeEnd(
12161226
if (isImprovement && spanNotEnded) {
12171227
span.updateName(name);
12181228
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
1229+
if (source === 'route') {
1230+
span.setAttribute('url.template', name);
1231+
}
12191232
}
12201233
} catch (error) {
12211234
DEBUG_BUILD && debug.warn(`Error updating span details before ending: ${error}`);

packages/react/src/reactrouterv3.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export function reactRouterV3BrowserTracingIntegration(
6868
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
6969
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v3',
7070
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
71+
...(source === 'route' && { 'url.template': localName }),
7172
},
7273
});
7374
},
@@ -88,6 +89,7 @@ export function reactRouterV3BrowserTracingIntegration(
8889
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
8990
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v3',
9091
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
92+
...(source === 'route' && { 'url.template': localName }),
9193
},
9294
});
9395
},

packages/react/src/tanstackrouter.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export function tanstackRouterBrowserTracingIntegration(
5959
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
6060
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.tanstack_router',
6161
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeMatch ? 'route' : 'url',
62+
...(routeMatch && { 'url.template': routeMatch.routeId }),
6263
...routeMatchToParamSpanAttributes(routeMatch),
6364
},
6465
});
@@ -94,6 +95,7 @@ export function tanstackRouterBrowserTracingIntegration(
9495
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
9596
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.tanstack_router',
9697
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: onBeforeNavigateRouteMatch ? 'route' : 'url',
98+
...(onBeforeNavigateRouteMatch && { 'url.template': onBeforeNavigateRouteMatch.routeId }),
9799
},
98100
});
99101

@@ -114,6 +116,7 @@ export function tanstackRouterBrowserTracingIntegration(
114116
if (onResolvedRouteMatch) {
115117
navigationSpan.updateName(onResolvedRouteMatch.routeId);
116118
navigationSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
119+
navigationSpan.setAttribute('url.template', onResolvedRouteMatch.routeId);
117120
navigationSpan.setAttributes(routeMatchToParamSpanAttributes(onResolvedRouteMatch));
118121
}
119122
}

packages/remix/src/client/performance.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export function startPageloadSpan(client: Client): void {
103103
attributes: {
104104
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.remix',
105105
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
106+
...(source === 'route' && { 'url.template': spanName }),
106107
},
107108
};
108109

@@ -126,6 +127,7 @@ function startNavigationSpan(matches: RouteMatch<string>[], location: ReturnType
126127
attributes: {
127128
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.remix',
128129
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
130+
...(source === 'route' && { 'url.template': name }),
129131
},
130132
};
131133

@@ -182,6 +184,9 @@ export function withSentry<P extends Record<string, unknown>, R extends React.Co
182184
if (transaction) {
183185
transaction.updateName(name);
184186
transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
187+
if (source === 'route') {
188+
transaction.setAttribute('url.template', name);
189+
}
185190
}
186191
}
187192
}

0 commit comments

Comments
 (0)