Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 149 additions & 1 deletion packages/sveltekit/src/client/browserTracingIntegration.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,162 @@
import { BrowserTracing as OriginalBrowserTracing } from '@sentry/svelte';
import { navigating, page } from '$app/stores';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core';
import {
BrowserTracing as OriginalBrowserTracing,
WINDOW,
browserTracingIntegration as originalBrowserTracingIntegration,
getActiveSpan,
startBrowserTracingNavigationSpan,
startBrowserTracingPageLoadSpan,
startInactiveSpan,
} from '@sentry/svelte';
import type { Client, Integration, Span } from '@sentry/types';
import { svelteKitRoutingInstrumentation } from './router';

/**
* A custom BrowserTracing integration for Sveltekit.
*
* @deprecated use `browserTracingIntegration()` instead. The new `browserTracingIntegration()`
* includes SvelteKit-specific routing instrumentation out of the box. Therefore there's no need
* to pass in `svelteKitRoutingInstrumentation` anymore.
*/
export class BrowserTracing extends OriginalBrowserTracing {
public constructor(options?: ConstructorParameters<typeof OriginalBrowserTracing>[0]) {
super({
// eslint-disable-next-line deprecation/deprecation
routingInstrumentation: svelteKitRoutingInstrumentation,
...options,
});
}
}

/**
* A custom `BrowserTracing` integration for SvelteKit.
*/
export function browserTracingIntegration(
options: Parameters<typeof originalBrowserTracingIntegration>[0] = {},
): Integration {
const integration = {
...originalBrowserTracingIntegration({
...options,
instrumentNavigation: false,
instrumentPageLoad: false,
}),
};

return {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice ❤️ pretty clean!

...integration,
afterAllSetup: client => {
integration.afterAllSetup(client);

if (options.instrumentPageLoad !== false) {
_instrumentPageload(client);
}

if (options.instrumentNavigation !== false) {
_instrumentNavigations(client);
}
},
};
}

function _instrumentPageload(client: Client): void {
const initialPath = WINDOW && WINDOW.location && WINDOW.location.pathname;

startBrowserTracingPageLoadSpan(client, {
name: initialPath,
op: 'pageload',
description: initialPath,
tags: {
'routing.instrumentation': '@sentry/sveltekit',
},
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.sveltekit',
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
},
});

const pageloadSpan = getActiveSpan();

page.subscribe(page => {
if (!page) {
return;
}

const routeId = page.route && page.route.id;

if (pageloadSpan && routeId) {
pageloadSpan.updateName(routeId);
pageloadSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
}
});
}

/**
* Use the `navigating` store to start a transaction on navigations.
*/
function _instrumentNavigations(client: Client): void {
let routingSpan: Span | undefined;
let activeSpan: Span | undefined;

navigating.subscribe(navigation => {
if (!navigation) {
// `navigating` emits a 'null' value when the navigation is completed.
// So in this case, we can finish the routing span. If the transaction was an IdleTransaction,
// it will finish automatically and if it was user-created users also need to finish it.
if (routingSpan) {
routingSpan.end();
routingSpan = undefined;
}
return;
}

const from = navigation.from;
const to = navigation.to;

// for the origin we can fall back to window.location.pathname because in this emission, it still is set to the origin path
const rawRouteOrigin = (from && from.url.pathname) || (WINDOW && WINDOW.location && WINDOW.location.pathname);

const rawRouteDestination = to && to.url.pathname;

// We don't want to create transactions for navigations of same origin and destination.
// We need to look at the raw URL here because parameterized routes can still differ in their raw parameters.
if (rawRouteOrigin === rawRouteDestination) {
return;
}

const parameterizedRouteOrigin = from && from.route.id;
const parameterizedRouteDestination = to && to.route.id;

activeSpan = getActiveSpan();

if (!activeSpan) {
startBrowserTracingNavigationSpan(client, {
name: parameterizedRouteDestination || rawRouteDestination || 'unknown',
op: 'navigation',
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.sveltekit',
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedRouteDestination ? 'route' : 'url',
},
tags: {
'routing.instrumentation': '@sentry/sveltekit',
},
});
activeSpan = getActiveSpan();
}

if (activeSpan) {
if (routingSpan) {
// If a routing span is still open from a previous navigation, we finish it.
routingSpan.end();
}
routingSpan = startInactiveSpan({
op: 'ui.sveltekit.routing',
name: 'SvelteKit Route Change',
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.sveltekit',
},
});
activeSpan.setAttribute('sentry.sveltekit.navigation.from', parameterizedRouteOrigin || undefined);
}
});
}
1 change: 1 addition & 0 deletions packages/sveltekit/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from '@sentry/svelte';
export { init } from './sdk';
export { handleErrorWithSentry } from './handleError';
export { wrapLoadWithSentry } from './load';
export { browserTracingIntegration } from './browserTracingIntegration';
3 changes: 3 additions & 0 deletions packages/sveltekit/src/client/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ const DEFAULT_TAGS = {
* @param startTransactionFn the function used to start (idle) transactions
* @param startTransactionOnPageLoad controls if pageload transactions should be created (defaults to `true`)
* @param startTransactionOnLocationChange controls if navigation transactions should be created (defauls to `true`)
*
* @deprecated use `browserTracingIntegration()` instead which includes SvelteKit-specific routing instrumentation out of the box.
* Therefore, this function will be removed in v8.
*/
export function svelteKitRoutingInstrumentation<T extends Transaction>(
startTransactionFn: (context: TransactionContext) => T | undefined,
Expand Down
12 changes: 10 additions & 2 deletions packages/sveltekit/src/client/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { getDefaultIntegrations as getDefaultSvelteIntegrations } from '@sentry/
import { WINDOW, getCurrentScope, init as initSvelteSdk } from '@sentry/svelte';
import type { Integration } from '@sentry/types';

import { BrowserTracing } from './browserTracingIntegration';
import {
BrowserTracing,
browserTracingIntegration as svelteKitBrowserTracingIntegration,
} from './browserTracingIntegration';

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

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

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

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

Expand Down
Loading