Skip to content

Commit 03f0935

Browse files
Lms24onurtemizkan
authored andcommitted
feat(sveltekit): Add custom browserTracingIntegration() (#10450)
Deprecates SvelteKit's `BrowserTracing` integration in favour of the new `browserTracingIntegration` functional integration. The new integration now also directly initializes the routing instrumentation.
1 parent 0eaa90c commit 03f0935

File tree

7 files changed

+457
-12
lines changed

7 files changed

+457
-12
lines changed
Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,162 @@
1-
import { BrowserTracing as OriginalBrowserTracing } from '@sentry/svelte';
1+
import { navigating, page } from '$app/stores';
2+
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core';
3+
import {
4+
BrowserTracing as OriginalBrowserTracing,
5+
WINDOW,
6+
browserTracingIntegration as originalBrowserTracingIntegration,
7+
getActiveSpan,
8+
startBrowserTracingNavigationSpan,
9+
startBrowserTracingPageLoadSpan,
10+
startInactiveSpan,
11+
} from '@sentry/svelte';
12+
import type { Client, Integration, Span } from '@sentry/types';
213
import { svelteKitRoutingInstrumentation } from './router';
314

415
/**
516
* A custom BrowserTracing integration for Sveltekit.
17+
*
18+
* @deprecated use `browserTracingIntegration()` instead. The new `browserTracingIntegration()`
19+
* includes SvelteKit-specific routing instrumentation out of the box. Therefore there's no need
20+
* to pass in `svelteKitRoutingInstrumentation` anymore.
621
*/
722
export class BrowserTracing extends OriginalBrowserTracing {
823
public constructor(options?: ConstructorParameters<typeof OriginalBrowserTracing>[0]) {
924
super({
25+
// eslint-disable-next-line deprecation/deprecation
1026
routingInstrumentation: svelteKitRoutingInstrumentation,
1127
...options,
1228
});
1329
}
1430
}
31+
32+
/**
33+
* A custom `BrowserTracing` integration for SvelteKit.
34+
*/
35+
export function browserTracingIntegration(
36+
options: Parameters<typeof originalBrowserTracingIntegration>[0] = {},
37+
): Integration {
38+
const integration = {
39+
...originalBrowserTracingIntegration({
40+
...options,
41+
instrumentNavigation: false,
42+
instrumentPageLoad: false,
43+
}),
44+
};
45+
46+
return {
47+
...integration,
48+
afterAllSetup: client => {
49+
integration.afterAllSetup(client);
50+
51+
if (options.instrumentPageLoad !== false) {
52+
_instrumentPageload(client);
53+
}
54+
55+
if (options.instrumentNavigation !== false) {
56+
_instrumentNavigations(client);
57+
}
58+
},
59+
};
60+
}
61+
62+
function _instrumentPageload(client: Client): void {
63+
const initialPath = WINDOW && WINDOW.location && WINDOW.location.pathname;
64+
65+
startBrowserTracingPageLoadSpan(client, {
66+
name: initialPath,
67+
op: 'pageload',
68+
description: initialPath,
69+
tags: {
70+
'routing.instrumentation': '@sentry/sveltekit',
71+
},
72+
attributes: {
73+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.sveltekit',
74+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
75+
},
76+
});
77+
78+
const pageloadSpan = getActiveSpan();
79+
80+
page.subscribe(page => {
81+
if (!page) {
82+
return;
83+
}
84+
85+
const routeId = page.route && page.route.id;
86+
87+
if (pageloadSpan && routeId) {
88+
pageloadSpan.updateName(routeId);
89+
pageloadSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
90+
}
91+
});
92+
}
93+
94+
/**
95+
* Use the `navigating` store to start a transaction on navigations.
96+
*/
97+
function _instrumentNavigations(client: Client): void {
98+
let routingSpan: Span | undefined;
99+
let activeSpan: Span | undefined;
100+
101+
navigating.subscribe(navigation => {
102+
if (!navigation) {
103+
// `navigating` emits a 'null' value when the navigation is completed.
104+
// So in this case, we can finish the routing span. If the transaction was an IdleTransaction,
105+
// it will finish automatically and if it was user-created users also need to finish it.
106+
if (routingSpan) {
107+
routingSpan.end();
108+
routingSpan = undefined;
109+
}
110+
return;
111+
}
112+
113+
const from = navigation.from;
114+
const to = navigation.to;
115+
116+
// for the origin we can fall back to window.location.pathname because in this emission, it still is set to the origin path
117+
const rawRouteOrigin = (from && from.url.pathname) || (WINDOW && WINDOW.location && WINDOW.location.pathname);
118+
119+
const rawRouteDestination = to && to.url.pathname;
120+
121+
// We don't want to create transactions for navigations of same origin and destination.
122+
// We need to look at the raw URL here because parameterized routes can still differ in their raw parameters.
123+
if (rawRouteOrigin === rawRouteDestination) {
124+
return;
125+
}
126+
127+
const parameterizedRouteOrigin = from && from.route.id;
128+
const parameterizedRouteDestination = to && to.route.id;
129+
130+
activeSpan = getActiveSpan();
131+
132+
if (!activeSpan) {
133+
startBrowserTracingNavigationSpan(client, {
134+
name: parameterizedRouteDestination || rawRouteDestination || 'unknown',
135+
op: 'navigation',
136+
attributes: {
137+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.sveltekit',
138+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedRouteDestination ? 'route' : 'url',
139+
},
140+
tags: {
141+
'routing.instrumentation': '@sentry/sveltekit',
142+
},
143+
});
144+
activeSpan = getActiveSpan();
145+
}
146+
147+
if (activeSpan) {
148+
if (routingSpan) {
149+
// If a routing span is still open from a previous navigation, we finish it.
150+
routingSpan.end();
151+
}
152+
routingSpan = startInactiveSpan({
153+
op: 'ui.sveltekit.routing',
154+
name: 'SvelteKit Route Change',
155+
attributes: {
156+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.sveltekit',
157+
},
158+
});
159+
activeSpan.setAttribute('sentry.sveltekit.navigation.from', parameterizedRouteOrigin || undefined);
160+
}
161+
});
162+
}

packages/sveltekit/src/client/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from '@sentry/svelte';
33
export { init } from './sdk';
44
export { handleErrorWithSentry } from './handleError';
55
export { wrapLoadWithSentry } from './load';
6+
export { browserTracingIntegration } from './browserTracingIntegration';

packages/sveltekit/src/client/router.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ const DEFAULT_TAGS = {
1717
* @param startTransactionFn the function used to start (idle) transactions
1818
* @param startTransactionOnPageLoad controls if pageload transactions should be created (defaults to `true`)
1919
* @param startTransactionOnLocationChange controls if navigation transactions should be created (defauls to `true`)
20+
*
21+
* @deprecated use `browserTracingIntegration()` instead which includes SvelteKit-specific routing instrumentation out of the box.
22+
* Therefore, this function will be removed in v8.
2023
*/
2124
export function svelteKitRoutingInstrumentation<T extends Transaction>(
2225
startTransactionFn: (context: TransactionContext) => T | undefined,

packages/sveltekit/src/client/sdk.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { getDefaultIntegrations as getDefaultSvelteIntegrations } from '@sentry/
44
import { WINDOW, getCurrentScope, init as initSvelteSdk } from '@sentry/svelte';
55
import type { Integration } from '@sentry/types';
66

7-
import { BrowserTracing } from './browserTracingIntegration';
7+
import {
8+
BrowserTracing,
9+
browserTracingIntegration as svelteKitBrowserTracingIntegration,
10+
} from './browserTracingIntegration';
811

912
type WindowWithSentryFetchProxy = typeof WINDOW & {
1013
_sentryFetchProxy?: typeof fetch;
@@ -64,6 +67,7 @@ function fixBrowserTracingIntegration(options: BrowserOptions): void {
6467
function isNewBrowserTracingIntegration(
6568
integration: Integration,
6669
): integration is Integration & { options?: Parameters<typeof browserTracingIntegration>[0] } {
70+
// eslint-disable-next-line deprecation/deprecation
6771
return !!integration.afterAllSetup && !!(integration as BrowserTracing).options;
6872
}
6973

@@ -77,15 +81,19 @@ function maybeUpdateBrowserTracingIntegration(integrations: Integration[]): Inte
7781
// If `browserTracingIntegration()` was added, we need to force-convert it to our custom one
7882
if (isNewBrowserTracingIntegration(browserTracing)) {
7983
const { options } = browserTracing;
84+
// eslint-disable-next-line deprecation/deprecation
8085
integrations[integrations.indexOf(browserTracing)] = new BrowserTracing(options);
8186
}
8287

8388
// If BrowserTracing was added, but it is not our forked version,
8489
// replace it with our forked version with the same options
90+
// eslint-disable-next-line deprecation/deprecation
8591
if (!(browserTracing instanceof BrowserTracing)) {
92+
// eslint-disable-next-line deprecation/deprecation
8693
const options: ConstructorParameters<typeof BrowserTracing>[0] = (browserTracing as BrowserTracing).options;
8794
// This option is overwritten by the custom integration
8895
delete options.routingInstrumentation;
96+
// eslint-disable-next-line deprecation/deprecation
8997
integrations[integrations.indexOf(browserTracing)] = new BrowserTracing(options);
9098
}
9199

@@ -97,7 +105,7 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] | undefi
97105
// will get treeshaken away
98106
if (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) {
99107
if (hasTracingEnabled(options)) {
100-
return [...getDefaultSvelteIntegrations(options), new BrowserTracing()];
108+
return [...getDefaultSvelteIntegrations(options), svelteKitBrowserTracingIntegration()];
101109
}
102110
}
103111

0 commit comments

Comments
 (0)