Skip to content

Commit b61ac96

Browse files
authored
fix(sveltekit): Improve server-side grouping by removing the stack frame module (#7835)
Add a custom `RewriteFrames` iteratee to the server SDK which * Does exactly the same thing as the default iteratee if simply initializiung `RewriteFrames()` without custom options * Removes the `module` field from each stack frame
1 parent d324516 commit b61ac96

File tree

3 files changed

+87
-4
lines changed

3 files changed

+87
-4
lines changed

packages/sveltekit/src/server/sdk.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { init as initNodeSdk, Integrations } from '@sentry/node';
55
import { addOrUpdateIntegration } from '@sentry/utils';
66

77
import { applySdkMetadata } from '../common/metadata';
8+
import { rewriteFramesIteratee } from './utils';
89

910
/**
1011
*
@@ -24,5 +25,8 @@ export function init(options: NodeOptions): void {
2425

2526
function addServerIntegrations(options: NodeOptions): void {
2627
options.integrations = addOrUpdateIntegration(new Integrations.Undici(), options.integrations || []);
27-
options.integrations = addOrUpdateIntegration(new RewriteFrames(), options.integrations || []);
28+
options.integrations = addOrUpdateIntegration(
29+
new RewriteFrames({ iteratee: rewriteFramesIteratee }),
30+
options.integrations || [],
31+
);
2832
}

packages/sveltekit/src/server/utils.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { DynamicSamplingContext, TraceparentData } from '@sentry/types';
2-
import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils';
1+
import type { DynamicSamplingContext, StackFrame, TraceparentData } from '@sentry/types';
2+
import { baggageHeaderToDynamicSamplingContext, basename, extractTraceparentData } from '@sentry/utils';
33
import type { RequestEvent } from '@sveltejs/kit';
44

55
/**
@@ -17,3 +17,40 @@ export function getTracePropagationData(event: RequestEvent): {
1717

1818
return { traceparentData, dynamicSamplingContext };
1919
}
20+
21+
/**
22+
* A custom iteratee function for the `RewriteFrames` integration.
23+
*
24+
* Does the same as the default iteratee, but also removes the `module` property from the
25+
* frame to improve issue grouping.
26+
*
27+
* For some reason, our stack trace processing pipeline isn't able to resolve the bundled
28+
* module name to the original file name correctly, leading to individual error groups for
29+
* each module. Removing the `module` field makes the grouping algorithm fall back to the
30+
* `filename` field, which is correctly resolved and hence grouping works as expected.
31+
*/
32+
export function rewriteFramesIteratee(frame: StackFrame): StackFrame {
33+
if (!frame.filename) {
34+
return frame;
35+
}
36+
37+
const prefix = 'app:///';
38+
39+
// Check if the frame filename begins with `/` or a Windows-style prefix such as `C:\`
40+
const isWindowsFrame = /^[a-zA-Z]:\\/.test(frame.filename);
41+
const startsWithSlash = /^\//.test(frame.filename);
42+
if (isWindowsFrame || startsWithSlash) {
43+
const filename = isWindowsFrame
44+
? frame.filename
45+
.replace(/^[a-zA-Z]:/, '') // remove Windows-style prefix
46+
.replace(/\\/g, '/') // replace all `\\` instances with `/`
47+
: frame.filename;
48+
49+
const base = basename(filename);
50+
frame.filename = `${prefix}${base}`;
51+
}
52+
53+
delete frame.module;
54+
55+
return frame;
56+
}

packages/sveltekit/test/server/utils.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { getTracePropagationData } from '../../src/server/utils';
1+
import { RewriteFrames } from '@sentry/integrations';
2+
import type { StackFrame } from '@sentry/types';
3+
4+
import { getTracePropagationData, rewriteFramesIteratee } from '../../src/server/utils';
25

36
const MOCK_REQUEST_EVENT: any = {
47
request: {
@@ -53,3 +56,42 @@ describe('getTracePropagationData', () => {
5356
expect(dynamicSamplingContext).toBeUndefined();
5457
});
5558
});
59+
60+
describe('rewriteFramesIteratee', () => {
61+
it('removes the module property from the frame', () => {
62+
const frame: StackFrame = {
63+
filename: '/some/path/to/server/chunks/3-ab34d22f.js',
64+
module: '3-ab34d22f.js',
65+
};
66+
67+
const result = rewriteFramesIteratee(frame);
68+
69+
expect(result).not.toHaveProperty('module');
70+
});
71+
72+
it('does the same filename modification as the default RewriteFrames iteratee', () => {
73+
const frame: StackFrame = {
74+
filename: '/some/path/to/server/chunks/3-ab34d22f.js',
75+
lineno: 1,
76+
colno: 1,
77+
module: '3-ab34d22f.js',
78+
};
79+
80+
const originalRewriteFrames = new RewriteFrames();
81+
// @ts-ignore this property exists
82+
const defaultIteratee = originalRewriteFrames._iteratee;
83+
84+
const defaultResult = defaultIteratee({ ...frame });
85+
delete defaultResult.module;
86+
87+
const result = rewriteFramesIteratee({ ...frame });
88+
89+
expect(result).toEqual({
90+
filename: 'app:///3-ab34d22f.js',
91+
lineno: 1,
92+
colno: 1,
93+
});
94+
95+
expect(result).toStrictEqual(defaultResult);
96+
});
97+
});

0 commit comments

Comments
 (0)