Skip to content

Commit 30e9d0f

Browse files
committed
ref(sveltekit): Streamline how default integrations are added
1 parent 7688b60 commit 30e9d0f

File tree

5 files changed

+147
-89
lines changed

5 files changed

+147
-89
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { BrowserTracing as OriginalBrowserTracing } from '@sentry/svelte';
2+
import { svelteKitRoutingInstrumentation } from './router';
3+
4+
/**
5+
* A custom BrowserTracing integration for Next.js.
6+
*/
7+
export class BrowserTracing extends OriginalBrowserTracing {
8+
public constructor(options?: ConstructorParameters<typeof OriginalBrowserTracing>[0]) {
9+
super({
10+
routingInstrumentation: svelteKitRoutingInstrumentation,
11+
...options,
12+
});
13+
}
14+
}

packages/sveltekit/src/client/sdk.ts

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { hasTracingEnabled } from '@sentry/core';
22
import type { BrowserOptions } from '@sentry/svelte';
3-
import { BrowserTracing, WINDOW, getCurrentScope, init as initSvelteSdk } from '@sentry/svelte';
4-
import { addOrUpdateIntegration } from '@sentry/utils';
3+
import { getDefaultIntegrations as getDefaultSvelteIntegrations } from '@sentry/svelte';
4+
import { WINDOW, getCurrentScope, init as initSvelteSdk } from '@sentry/svelte';
5+
import type { Integration } from '@sentry/types';
56

67
import { applySdkMetadata } from '../common/metadata';
7-
import { svelteKitRoutingInstrumentation } from './router';
8+
import { BrowserTracing } from './browserTracingIntegration';
89

910
type WindowWithSentryFetchProxy = typeof WINDOW & {
1011
_sentryFetchProxy?: typeof fetch;
@@ -19,15 +20,20 @@ declare const __SENTRY_TRACING__: boolean;
1920
* @param options Configuration options for the SDK.
2021
*/
2122
export function init(options: BrowserOptions): void {
22-
applySdkMetadata(options, ['sveltekit', 'svelte']);
23+
const opts = {
24+
defaultIntegrations: getDefaultIntegrations(options),
25+
...options,
26+
};
2327

24-
addClientIntegrations(options);
28+
applySdkMetadata(opts, ['sveltekit', 'svelte']);
29+
30+
fixBrowserTracingIntegration(opts);
2531

2632
// 1. Switch window.fetch to our fetch proxy we injected earlier
2733
const actualFetch = switchToFetchProxy();
2834

2935
// 2. Initialize the SDK which will instrument our proxy
30-
initSvelteSdk(options);
36+
initSvelteSdk(opts);
3137

3238
// 3. Restore the original fetch now that our proxy is instrumented
3339
if (actualFetch) {
@@ -37,24 +43,49 @@ export function init(options: BrowserOptions): void {
3743
getCurrentScope().setTag('runtime', 'browser');
3844
}
3945

40-
function addClientIntegrations(options: BrowserOptions): void {
41-
let integrations = options.integrations || [];
46+
// TODO v8: Remove this again
47+
// We need to handle BrowserTracing passed to `integrations` that comes from `@sentry/tracing`, not `@sentry/sveltekit` :(
48+
function fixBrowserTracingIntegration(options: BrowserOptions): void {
49+
const { integrations } = options;
50+
if (!integrations) {
51+
return;
52+
}
53+
54+
if (Array.isArray(integrations)) {
55+
options.integrations = maybeUpdateBrowserTracingIntegration(integrations);
56+
} else {
57+
options.integrations = defaultIntegrations => {
58+
const userFinalIntegrations = integrations(defaultIntegrations);
59+
60+
return maybeUpdateBrowserTracingIntegration(userFinalIntegrations);
61+
};
62+
}
63+
}
64+
65+
function maybeUpdateBrowserTracingIntegration(integrations: Integration[]): Integration[] {
66+
const browserTracing = integrations.find(integration => integration.name === 'BrowserTracing');
67+
// If BrowserTracing was added, but it is not our forked version,
68+
// replace it with our forked version with the same options
69+
if (browserTracing && !(browserTracing instanceof BrowserTracing)) {
70+
const options: ConstructorParameters<typeof BrowserTracing>[0] = (browserTracing as BrowserTracing).options;
71+
// This option is overwritten by the custom integration
72+
delete options.routingInstrumentation;
73+
integrations[integrations.indexOf(browserTracing)] = new BrowserTracing(options);
74+
}
75+
76+
return integrations;
77+
}
4278

43-
// This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false",
44-
// in which case everything inside will get treeshaken away
79+
function getDefaultIntegrations(options: BrowserOptions): Integration[] | undefined {
80+
// This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false", in which case everything inside
81+
// will get treeshaken away
4582
if (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) {
4683
if (hasTracingEnabled(options)) {
47-
const defaultBrowserTracingIntegration = new BrowserTracing({
48-
routingInstrumentation: svelteKitRoutingInstrumentation,
49-
});
50-
51-
integrations = addOrUpdateIntegration(defaultBrowserTracingIntegration, integrations, {
52-
'options.routingInstrumentation': svelteKitRoutingInstrumentation,
53-
});
84+
return [...getDefaultSvelteIntegrations(options), new BrowserTracing()];
5485
}
5586
}
5687

57-
options.integrations = integrations;
88+
return undefined;
5889
}
5990

6091
/**
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { defineIntegration } from '@sentry/core';
2+
import { rewriteFramesIntegration as originalRewriteFramesIntegration } from '@sentry/integrations';
3+
import type { IntegrationFn, StackFrame } from '@sentry/types';
4+
import { GLOBAL_OBJ, basename, escapeStringForRegex, join } from '@sentry/utils';
5+
import { WRAPPED_MODULE_SUFFIX } from '../vite/autoInstrument';
6+
import type { GlobalWithSentryValues } from '../vite/injectGlobalValues';
7+
8+
type StackFrameIteratee = (frame: StackFrame) => StackFrame;
9+
interface RewriteFramesOptions {
10+
root?: string;
11+
prefix?: string;
12+
iteratee?: StackFrameIteratee;
13+
}
14+
15+
export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => {
16+
return originalRewriteFramesIntegration({
17+
iteratee: rewriteFramesIteratee,
18+
...options,
19+
});
20+
}) satisfies IntegrationFn;
21+
22+
export const rewriteFramesIntegration = defineIntegration(customRewriteFramesIntegration);
23+
24+
/**
25+
* A custom iteratee function for the `RewriteFrames` integration.
26+
*
27+
* Does the same as the default iteratee, but also removes the `module` property from the
28+
* frame to improve issue grouping.
29+
*
30+
* For some reason, our stack trace processing pipeline isn't able to resolve the bundled
31+
* module name to the original file name correctly, leading to individual error groups for
32+
* each module. Removing the `module` field makes the grouping algorithm fall back to the
33+
* `filename` field, which is correctly resolved and hence grouping works as expected.
34+
*/
35+
function rewriteFramesIteratee(frame: StackFrame): StackFrame {
36+
if (!frame.filename) {
37+
return frame;
38+
}
39+
const globalWithSentryValues: GlobalWithSentryValues = GLOBAL_OBJ;
40+
const svelteKitBuildOutDir = globalWithSentryValues.__sentry_sveltekit_output_dir;
41+
const prefix = 'app:///';
42+
43+
// Check if the frame filename begins with `/` or a Windows-style prefix such as `C:\`
44+
const isWindowsFrame = /^[a-zA-Z]:\\/.test(frame.filename);
45+
const startsWithSlash = /^\//.test(frame.filename);
46+
if (isWindowsFrame || startsWithSlash) {
47+
const filename = isWindowsFrame
48+
? frame.filename
49+
.replace(/^[a-zA-Z]:/, '') // remove Windows-style prefix
50+
.replace(/\\/g, '/') // replace all `\\` instances with `/`
51+
: frame.filename;
52+
53+
let strippedFilename;
54+
if (svelteKitBuildOutDir) {
55+
strippedFilename = filename.replace(
56+
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- not end user input + escaped anyway
57+
new RegExp(`^.*${escapeStringForRegex(join(svelteKitBuildOutDir, 'server'))}/`),
58+
'',
59+
);
60+
} else {
61+
strippedFilename = basename(filename);
62+
}
63+
frame.filename = `${prefix}${strippedFilename}`;
64+
}
65+
66+
delete frame.module;
67+
68+
// In dev-mode, the WRAPPED_MODULE_SUFFIX is still present in the frame's file name.
69+
// We need to remove it to make sure that the frame's filename matches the actual file
70+
if (frame.filename.endsWith(WRAPPED_MODULE_SUFFIX)) {
71+
frame.filename = frame.filename.slice(0, -WRAPPED_MODULE_SUFFIX.length);
72+
}
73+
74+
return frame;
75+
}

packages/sveltekit/src/server/sdk.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,24 @@
11
import { getCurrentScope } from '@sentry/core';
2-
import { RewriteFrames } from '@sentry/integrations';
32
import type { NodeOptions } from '@sentry/node';
3+
import { getDefaultIntegrations as getDefaultNodeIntegrations } from '@sentry/node';
44
import { init as initNodeSdk } from '@sentry/node';
5-
import { addOrUpdateIntegration } from '@sentry/utils';
65

76
import { applySdkMetadata } from '../common/metadata';
8-
import { rewriteFramesIteratee } from './utils';
7+
import { rewriteFramesIntegration } from './rewriteFramesIntegration';
98

109
/**
1110
*
1211
* @param options
1312
*/
1413
export function init(options: NodeOptions): void {
15-
applySdkMetadata(options, ['sveltekit', 'node']);
14+
const opts = {
15+
defaultIntegrations: [...getDefaultNodeIntegrations(options), rewriteFramesIntegration()],
16+
...options,
17+
};
1618

17-
addServerIntegrations(options);
19+
applySdkMetadata(opts, ['sveltekit', 'node']);
1820

19-
initNodeSdk(options);
21+
initNodeSdk(opts);
2022

2123
getCurrentScope().setTag('runtime', 'node');
2224
}
23-
24-
function addServerIntegrations(options: NodeOptions): void {
25-
options.integrations = addOrUpdateIntegration(
26-
// eslint-disable-next-line deprecation/deprecation
27-
new RewriteFrames({ iteratee: rewriteFramesIteratee }),
28-
options.integrations || [],
29-
);
30-
}

packages/sveltekit/src/server/utils.ts

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import { flush } from '@sentry/node';
2-
import type { StackFrame } from '@sentry/types';
3-
import { GLOBAL_OBJ, basename, escapeStringForRegex, join, logger, tracingContextFromHeaders } from '@sentry/utils';
2+
import { logger, tracingContextFromHeaders } from '@sentry/utils';
43
import type { RequestEvent } from '@sveltejs/kit';
54

65
import { DEBUG_BUILD } from '../common/debug-build';
7-
import { WRAPPED_MODULE_SUFFIX } from '../vite/autoInstrument';
8-
import type { GlobalWithSentryValues } from '../vite/injectGlobalValues';
96

107
/**
118
* Takes a request event and extracts traceparent and DSC data
@@ -19,59 +16,6 @@ export function getTracePropagationData(event: RequestEvent): ReturnType<typeof
1916
return tracingContextFromHeaders(sentryTraceHeader, baggageHeader);
2017
}
2118

22-
/**
23-
* A custom iteratee function for the `RewriteFrames` integration.
24-
*
25-
* Does the same as the default iteratee, but also removes the `module` property from the
26-
* frame to improve issue grouping.
27-
*
28-
* For some reason, our stack trace processing pipeline isn't able to resolve the bundled
29-
* module name to the original file name correctly, leading to individual error groups for
30-
* each module. Removing the `module` field makes the grouping algorithm fall back to the
31-
* `filename` field, which is correctly resolved and hence grouping works as expected.
32-
*/
33-
export function rewriteFramesIteratee(frame: StackFrame): StackFrame {
34-
if (!frame.filename) {
35-
return frame;
36-
}
37-
const globalWithSentryValues: GlobalWithSentryValues = GLOBAL_OBJ;
38-
const svelteKitBuildOutDir = globalWithSentryValues.__sentry_sveltekit_output_dir;
39-
const prefix = 'app:///';
40-
41-
// Check if the frame filename begins with `/` or a Windows-style prefix such as `C:\`
42-
const isWindowsFrame = /^[a-zA-Z]:\\/.test(frame.filename);
43-
const startsWithSlash = /^\//.test(frame.filename);
44-
if (isWindowsFrame || startsWithSlash) {
45-
const filename = isWindowsFrame
46-
? frame.filename
47-
.replace(/^[a-zA-Z]:/, '') // remove Windows-style prefix
48-
.replace(/\\/g, '/') // replace all `\\` instances with `/`
49-
: frame.filename;
50-
51-
let strippedFilename;
52-
if (svelteKitBuildOutDir) {
53-
strippedFilename = filename.replace(
54-
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- not end user input + escaped anyway
55-
new RegExp(`^.*${escapeStringForRegex(join(svelteKitBuildOutDir, 'server'))}/`),
56-
'',
57-
);
58-
} else {
59-
strippedFilename = basename(filename);
60-
}
61-
frame.filename = `${prefix}${strippedFilename}`;
62-
}
63-
64-
delete frame.module;
65-
66-
// In dev-mode, the WRAPPED_MODULE_SUFFIX is still present in the frame's file name.
67-
// We need to remove it to make sure that the frame's filename matches the actual file
68-
if (frame.filename.endsWith(WRAPPED_MODULE_SUFFIX)) {
69-
frame.filename = frame.filename.slice(0, -WRAPPED_MODULE_SUFFIX.length);
70-
}
71-
72-
return frame;
73-
}
74-
7519
/** Flush the event queue to ensure that events get sent to Sentry before the response is finished and the lambda ends */
7620
export async function flushIfServerless(): Promise<void> {
7721
const platformSupportsStreaming = !process.env.LAMBDA_TASK_ROOT && !process.env.VERCEL;

0 commit comments

Comments
 (0)