diff --git a/packages/types/src/stackframe.ts b/packages/types/src/stackframe.ts index 7b8fa40a8a23..7a8058a1792c 100644 --- a/packages/types/src/stackframe.ts +++ b/packages/types/src/stackframe.ts @@ -14,4 +14,5 @@ export interface StackFrame { instruction_addr?: string; addr_mode?: string; vars?: { [key: string]: any }; + debug_id?: string; } diff --git a/packages/utils/src/stacktrace.ts b/packages/utils/src/stacktrace.ts index 3f9b5cf475b4..8af1c6f0d1b0 100644 --- a/packages/utils/src/stacktrace.ts +++ b/packages/utils/src/stacktrace.ts @@ -1,7 +1,14 @@ import type { StackFrame, StackLineParser, StackLineParserFn, StackParser } from '@sentry/types'; +import { GLOBAL_OBJ } from './worldwide'; + const STACKTRACE_LIMIT = 50; +type DebugIdFilename = string; +type DebugId = string; + +const debugIdParserCache = new Map>(); + /** * Creates a stack parser with the supplied line parsers * @@ -15,6 +22,28 @@ export function createStackParser(...parsers: StackLineParser[]): StackParser { return (stack: string, skipFirst: number = 0): StackFrame[] => { const frames: StackFrame[] = []; + for (const parser of sortedParsers) { + let debugIdCache = debugIdParserCache.get(parser); + if (!debugIdCache) { + debugIdCache = new Map(); + debugIdParserCache.set(parser, debugIdCache); + } + + const debugIdMap = GLOBAL_OBJ._sentryDebugIds; + + if (debugIdMap) { + Object.keys(debugIdMap).forEach(debugIdStackTrace => { + debugIdStackTrace.split('\n').forEach(line => { + const frame = parser(line); + if (frame && frame.filename) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + debugIdCache!.set(frame.filename, debugIdMap[debugIdStackTrace]); + } + }); + }); + } + } + for (const line of stack.split('\n').slice(skipFirst)) { // Ignore lines over 1kb as they are unlikely to be stack frames. // Many of the regular expressions use backtracking which results in run time that increases exponentially with @@ -32,6 +61,14 @@ export function createStackParser(...parsers: StackLineParser[]): StackParser { const frame = parser(cleanedLine); if (frame) { + const debugIdCache = debugIdParserCache.get(parser); + if (debugIdCache && frame.filename) { + const cachedDebugId = debugIdCache.get(frame.filename); + if (cachedDebugId) { + frame.debug_id = cachedDebugId; + } + } + frames.push(frame); break; } diff --git a/packages/utils/src/worldwide.ts b/packages/utils/src/worldwide.ts index 4cc141cc3d65..028eba2ac774 100644 --- a/packages/utils/src/worldwide.ts +++ b/packages/utils/src/worldwide.ts @@ -29,6 +29,7 @@ export interface InternalGlobal { id?: string; }; SENTRY_SDK_SOURCE?: SdkSource; + _sentryDebugIds?: Record; __SENTRY__: { globalEventProcessors: any; hub: any; diff --git a/packages/utils/test/stacktrace.test.ts b/packages/utils/test/stacktrace.test.ts index 61b44de34366..a2438c687bc4 100644 --- a/packages/utils/test/stacktrace.test.ts +++ b/packages/utils/test/stacktrace.test.ts @@ -1,4 +1,5 @@ -import { stripSentryFramesAndReverse } from '../src/stacktrace'; +import { createStackParser, stripSentryFramesAndReverse } from '../src/stacktrace'; +import { GLOBAL_OBJ } from '../src/worldwide'; describe('Stacktrace', () => { describe('stripSentryFramesAndReverse()', () => { @@ -68,3 +69,41 @@ describe('Stacktrace', () => { }); }); }); + +describe('Stack parsers created with createStackParser', () => { + afterEach(() => { + GLOBAL_OBJ._sentryDebugIds = undefined; + }); + + it('put debug ids onto individual frames', () => { + GLOBAL_OBJ._sentryDebugIds = { + 'filename1.js\nfilename1.js': 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa', + 'filename2.js\nfilename2.js': 'bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbb', + }; + + const fakeErrorStack = 'filename1.js\nfilename2.js\nfilename1.js\nfilename3.js'; + const stackParser = createStackParser([0, line => ({ filename: line })]); + + const result = stackParser(fakeErrorStack); + + expect(result[0]).toStrictEqual({ filename: 'filename3.js', function: '?' }); + + expect(result[1]).toStrictEqual({ + filename: 'filename1.js', + function: '?', + debug_id: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa', + }); + + expect(result[2]).toStrictEqual({ + filename: 'filename2.js', + function: '?', + debug_id: 'bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbb', + }); + + expect(result[3]).toStrictEqual({ + filename: 'filename1.js', + function: '?', + debug_id: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa', + }); + }); +});