Skip to content

Commit 8b8bb47

Browse files
authored
feat(sveltekit): Automatically add BrowserTracing integration (#7528)
With this patch, the `BrowserTracing` integration is automatically added to the client SvelteKit SDK initialization. As in NextJS, tree-shaking will still work by replacing the `__SENTRY_TRACING__` flag with `false` when bundling.
1 parent dcb31fe commit 8b8bb47

File tree

9 files changed

+118
-17
lines changed

9 files changed

+118
-17
lines changed

packages/nextjs/src/client/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import type { BrowserOptions } from '@sentry/react';
44
import { configureScope, init as reactInit, Integrations } from '@sentry/react';
55
import { BrowserTracing, defaultRequestInstrumentationOptions } from '@sentry/tracing';
66
import type { EventProcessor } from '@sentry/types';
7+
import { addOrUpdateIntegration } from '@sentry/utils';
78

89
import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor';
910
import { getVercelEnv } from '../common/getVercelEnv';
1011
import { buildMetadata } from '../common/metadata';
11-
import { addOrUpdateIntegration } from '../common/userIntegrations';
1212
import { nextRouterInstrumentation } from './performance';
1313
import { applyTunnelRouteOption } from './tunnelRoute';
1414

packages/nextjs/src/server/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ import { RewriteFrames } from '@sentry/integrations';
44
import type { NodeOptions } from '@sentry/node';
55
import { configureScope, getCurrentHub, init as nodeInit, Integrations } from '@sentry/node';
66
import type { EventProcessor } from '@sentry/types';
7-
import { escapeStringForRegex, logger } from '@sentry/utils';
7+
import type { IntegrationWithExclusionOption } from '@sentry/utils';
8+
import { addOrUpdateIntegration, escapeStringForRegex, logger } from '@sentry/utils';
89
import * as domainModule from 'domain';
910
import * as path from 'path';
1011

1112
import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor';
1213
import { getVercelEnv } from '../common/getVercelEnv';
1314
import { buildMetadata } from '../common/metadata';
14-
import type { IntegrationWithExclusionOption } from '../common/userIntegrations';
15-
import { addOrUpdateIntegration } from '../common/userIntegrations';
1615
import { isBuild } from './utils/isBuild';
1716

1817
export * from '@sentry/node';

packages/nextjs/test/clientSdk.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import * as SentryReact from '@sentry/react';
33
import { WINDOW } from '@sentry/react';
44
import { Integrations as TracingIntegrations } from '@sentry/tracing';
55
import type { Integration } from '@sentry/types';
6+
import type { UserIntegrationsFunction } from '@sentry/utils';
67
import { logger } from '@sentry/utils';
78
import { JSDOM } from 'jsdom';
89

910
import { init, Integrations, nextRouterInstrumentation } from '../src/client';
10-
import type { UserIntegrationsFunction } from '../src/common/userIntegrations';
1111

1212
const { BrowserTracing } = TracingIntegrations;
1313

packages/sveltekit/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"@sentry/svelte": "7.44.1",
2626
"@sentry/types": "7.44.1",
2727
"@sentry/utils": "7.44.1",
28+
"@sentry-internal/tracing": "7.44.1",
2829
"magic-string": "^0.30.0"
2930
},
3031
"devDependencies": {

packages/sveltekit/src/client/sdk.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,49 @@
1+
import { defaultRequestInstrumentationOptions } from '@sentry-internal/tracing';
2+
import { hasTracingEnabled } from '@sentry/core';
13
import type { BrowserOptions } from '@sentry/svelte';
2-
import { configureScope, init as initSvelteSdk } from '@sentry/svelte';
4+
import { BrowserTracing, configureScope, init as initSvelteSdk } from '@sentry/svelte';
5+
import { addOrUpdateIntegration } from '@sentry/utils';
36

47
import { applySdkMetadata } from '../common/metadata';
58

9+
// Treeshakable guard to remove all code related to tracing
10+
declare const __SENTRY_TRACING__: boolean;
11+
612
/**
713
*
814
* @param options
915
*/
1016
export function init(options: BrowserOptions): void {
1117
applySdkMetadata(options, ['sveltekit', 'svelte']);
1218

19+
addClientIntegrations(options);
20+
1321
initSvelteSdk(options);
1422

1523
configureScope(scope => {
1624
scope.setTag('runtime', 'browser');
1725
});
1826
}
27+
28+
function addClientIntegrations(options: BrowserOptions): void {
29+
let integrations = options.integrations || [];
30+
31+
// This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false",
32+
// in which case everything inside will get treeshaken away
33+
if (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) {
34+
if (hasTracingEnabled(options)) {
35+
const defaultBrowserTracingIntegration = new BrowserTracing({
36+
tracePropagationTargets: [...defaultRequestInstrumentationOptions.tracePropagationTargets],
37+
// TODO: Add SvelteKit router instrumentations
38+
// routingInstrumentation: sveltekitRoutingInstrumentation,
39+
});
40+
41+
integrations = addOrUpdateIntegration(defaultBrowserTracingIntegration, integrations, {
42+
// TODO: Add SvelteKit router instrumentations
43+
// options.routingInstrumentation: sveltekitRoutingInstrumentation,
44+
});
45+
}
46+
}
47+
48+
options.integrations = integrations;
49+
}

packages/sveltekit/test/client/sdk.test.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { getCurrentHub } from '@sentry/core';
2+
import type { BrowserClient } from '@sentry/svelte';
23
import * as SentrySvelte from '@sentry/svelte';
34
import { SDK_VERSION, WINDOW } from '@sentry/svelte';
45
import { vi } from 'vitest';
56

6-
import { init } from '../../src/client/sdk';
7+
import { BrowserTracing, init } from '../../src/client';
78

89
const svelteInit = vi.spyOn(SentrySvelte, 'init');
910

@@ -47,5 +48,77 @@ describe('Sentry client SDK', () => {
4748
// @ts-ignore need access to protected _tags attribute
4849
expect(currentScope._tags).toEqual({ runtime: 'browser' });
4950
});
51+
52+
describe('automatically added integrations', () => {
53+
it.each([
54+
['tracesSampleRate', { tracesSampleRate: 0 }],
55+
['tracesSampler', { tracesSampler: () => 1.0 }],
56+
['enableTracing', { enableTracing: true }],
57+
])('adds the BrowserTracing integration if tracing is enabled via %s', (_, tracingOptions) => {
58+
init({
59+
dsn: 'https://[email protected]/1337',
60+
...tracingOptions,
61+
});
62+
63+
const integrationsToInit = svelteInit.mock.calls[0][0].integrations;
64+
const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById('BrowserTracing');
65+
66+
expect(integrationsToInit).toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
67+
expect(browserTracing).toBeDefined();
68+
});
69+
70+
it.each([
71+
['enableTracing', { enableTracing: false }],
72+
['no tracing option set', {}],
73+
])("doesn't add the BrowserTracing integration if tracing is disabled via %s", (_, tracingOptions) => {
74+
init({
75+
dsn: 'https://[email protected]/1337',
76+
...tracingOptions,
77+
});
78+
79+
const integrationsToInit = svelteInit.mock.calls[0][0].integrations;
80+
const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById('BrowserTracing');
81+
82+
expect(integrationsToInit).not.toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
83+
expect(browserTracing).toBeUndefined();
84+
});
85+
86+
it("doesn't add the BrowserTracing integration if `__SENTRY_TRACING__` is set to false", () => {
87+
// This is the closest we can get to unit-testing the `__SENTRY_TRACING__` tree-shaking guard
88+
// IRL, the code to add the integration would most likely be removed by the bundler.
89+
90+
globalThis.__SENTRY_TRACING__ = false;
91+
92+
init({
93+
dsn: 'https://[email protected]/1337',
94+
enableTracing: true,
95+
});
96+
97+
const integrationsToInit = svelteInit.mock.calls[0][0].integrations;
98+
const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById('BrowserTracing');
99+
100+
expect(integrationsToInit).not.toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
101+
expect(browserTracing).toBeUndefined();
102+
103+
delete globalThis.__SENTRY_TRACING__;
104+
});
105+
106+
// TODO: this test is only meaningful once we have a routing instrumentation which we always want to add
107+
// to a user-provided BrowserTracing integration (see NextJS SDK)
108+
it.skip('Merges the user-provided BrowserTracing integration with the automatically added one', () => {
109+
init({
110+
dsn: 'https://[email protected]/1337',
111+
integrations: [new BrowserTracing({ tracePropagationTargets: ['myDomain.com'] })],
112+
enableTracing: true,
113+
});
114+
115+
const integrationsToInit = svelteInit.mock.calls[0][0].integrations;
116+
const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById('BrowserTracing');
117+
118+
expect(integrationsToInit).toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
119+
expect(browserTracing).toBeDefined();
120+
expect((browserTracing as BrowserTracing).options.tracePropagationTargets).toEqual(['myDomain.com']);
121+
});
122+
});
50123
});
51124
});

packages/utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ export * from './clientreport';
2727
export * from './ratelimit';
2828
export * from './baggage';
2929
export * from './url';
30+
export * from './userIntegrations';

packages/nextjs/src/common/userIntegrations.ts renamed to packages/utils/src/userIntegrations.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ import type { Integration } from '@sentry/types';
22

33
export type UserIntegrationsFunction = (integrations: Integration[]) => Integration[];
44
export type UserIntegrations = Integration[] | UserIntegrationsFunction;
5-
6-
type ForcedIntegrationOptions = {
7-
[keyPath: string]: unknown;
8-
};
9-
105
export type IntegrationWithExclusionOption = Integration & {
116
/**
127
* Allow the user to exclude this integration by not returning it from a function provided as the `integrations` option
@@ -16,6 +11,10 @@ export type IntegrationWithExclusionOption = Integration & {
1611
allowExclusionByUser?: boolean;
1712
};
1813

14+
type ForcedIntegrationOptions = {
15+
[keyPath: string]: unknown;
16+
};
17+
1918
/**
2019
* Recursively traverses an object to update an existing nested key.
2120
* Note: The provided key path must include existing properties,

packages/nextjs/test/utils/userIntegrations.test.ts renamed to packages/utils/test/userIntegrations.test.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import type {
2-
IntegrationWithExclusionOption as Integration,
3-
UserIntegrations,
4-
} from '../../src/common/userIntegrations';
5-
import { addOrUpdateIntegration } from '../../src/common/userIntegrations';
1+
import type { IntegrationWithExclusionOption as Integration, UserIntegrations } from '../src/userIntegrations';
2+
import { addOrUpdateIntegration } from '../src/userIntegrations';
63

74
type MockIntegrationOptions = {
85
name: string;

0 commit comments

Comments
 (0)