diff --git a/packages/sveltekit/src/server/sdk.ts b/packages/sveltekit/src/server/sdk.ts index 902a6a7d1a06..670f7879e7ba 100644 --- a/packages/sveltekit/src/server/sdk.ts +++ b/packages/sveltekit/src/server/sdk.ts @@ -5,6 +5,7 @@ import { init as initNodeSdk, Integrations } from '@sentry/node'; import { addOrUpdateIntegration } from '@sentry/utils'; import { applySdkMetadata } from '../common/metadata'; +import { rewriteFramesIteratee } from './utils'; /** * @@ -24,5 +25,8 @@ export function init(options: NodeOptions): void { function addServerIntegrations(options: NodeOptions): void { options.integrations = addOrUpdateIntegration(new Integrations.Undici(), options.integrations || []); - options.integrations = addOrUpdateIntegration(new RewriteFrames(), options.integrations || []); + options.integrations = addOrUpdateIntegration( + new RewriteFrames({ iteratee: rewriteFramesIteratee }), + options.integrations || [], + ); } diff --git a/packages/sveltekit/src/server/utils.ts b/packages/sveltekit/src/server/utils.ts index 67c3bfe9e050..644cd8477fff 100644 --- a/packages/sveltekit/src/server/utils.ts +++ b/packages/sveltekit/src/server/utils.ts @@ -1,5 +1,5 @@ -import type { DynamicSamplingContext, TraceparentData } from '@sentry/types'; -import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils'; +import type { DynamicSamplingContext, StackFrame, TraceparentData } from '@sentry/types'; +import { baggageHeaderToDynamicSamplingContext, basename, extractTraceparentData } from '@sentry/utils'; import type { RequestEvent } from '@sveltejs/kit'; /** @@ -17,3 +17,40 @@ export function getTracePropagationData(event: RequestEvent): { return { traceparentData, dynamicSamplingContext }; } + +/** + * A custom iteratee function for the `RewriteFrames` integration. + * + * Does the same as the default iteratee, but also removes the `module` property from the + * frame to improve issue grouping. + * + * For some reason, our stack trace processing pipeline isn't able to resolve the bundled + * module name to the original file name correctly, leading to individual error groups for + * each module. Removing the `module` field makes the grouping algorithm fall back to the + * `filename` field, which is correctly resolved and hence grouping works as expected. + */ +export function rewriteFramesIteratee(frame: StackFrame): StackFrame { + if (!frame.filename) { + return frame; + } + + const prefix = 'app:///'; + + // Check if the frame filename begins with `/` or a Windows-style prefix such as `C:\` + const isWindowsFrame = /^[a-zA-Z]:\\/.test(frame.filename); + const startsWithSlash = /^\//.test(frame.filename); + if (isWindowsFrame || startsWithSlash) { + const filename = isWindowsFrame + ? frame.filename + .replace(/^[a-zA-Z]:/, '') // remove Windows-style prefix + .replace(/\\/g, '/') // replace all `\\` instances with `/` + : frame.filename; + + const base = basename(filename); + frame.filename = `${prefix}${base}`; + } + + delete frame.module; + + return frame; +} diff --git a/packages/sveltekit/test/server/utils.test.ts b/packages/sveltekit/test/server/utils.test.ts index 8e5c064c338c..179cc6682d85 100644 --- a/packages/sveltekit/test/server/utils.test.ts +++ b/packages/sveltekit/test/server/utils.test.ts @@ -1,4 +1,7 @@ -import { getTracePropagationData } from '../../src/server/utils'; +import { RewriteFrames } from '@sentry/integrations'; +import type { StackFrame } from '@sentry/types'; + +import { getTracePropagationData, rewriteFramesIteratee } from '../../src/server/utils'; const MOCK_REQUEST_EVENT: any = { request: { @@ -53,3 +56,42 @@ describe('getTracePropagationData', () => { expect(dynamicSamplingContext).toBeUndefined(); }); }); + +describe('rewriteFramesIteratee', () => { + it('removes the module property from the frame', () => { + const frame: StackFrame = { + filename: '/some/path/to/server/chunks/3-ab34d22f.js', + module: '3-ab34d22f.js', + }; + + const result = rewriteFramesIteratee(frame); + + expect(result).not.toHaveProperty('module'); + }); + + it('does the same filename modification as the default RewriteFrames iteratee', () => { + const frame: StackFrame = { + filename: '/some/path/to/server/chunks/3-ab34d22f.js', + lineno: 1, + colno: 1, + module: '3-ab34d22f.js', + }; + + const originalRewriteFrames = new RewriteFrames(); + // @ts-ignore this property exists + const defaultIteratee = originalRewriteFrames._iteratee; + + const defaultResult = defaultIteratee({ ...frame }); + delete defaultResult.module; + + const result = rewriteFramesIteratee({ ...frame }); + + expect(result).toEqual({ + filename: 'app:///3-ab34d22f.js', + lineno: 1, + colno: 1, + }); + + expect(result).toStrictEqual(defaultResult); + }); +});