diff --git a/packages/node/src/stack-parser.ts b/packages/node/src/stack-parser.ts index 2cbd6f116ccf..a37e38001da3 100644 --- a/packages/node/src/stack-parser.ts +++ b/packages/node/src/stack-parser.ts @@ -36,8 +36,9 @@ function getModule(filename: string | undefined): string | undefined { } const FILENAME_MATCH = /^\s*[-]{4,}$/; -const FULL_MATCH = /at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/; +const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/; +// eslint-disable-next-line complexity const node: StackLineParserFn = (line: string) => { if (line.match(FILENAME_MATCH)) { return { @@ -87,7 +88,12 @@ const node: StackLineParserFn = (line: string) => { functionName = undefined; } - const filename = lineMatch[2]; + if (functionName === undefined) { + methodName = methodName || ''; + functionName = typeName ? `${typeName}.${methodName}` : methodName; + } + + const filename = lineMatch[2]?.startsWith('file://') ? lineMatch[2].substr(7) : lineMatch[2]; const isNative = lineMatch[5] === 'native'; const isInternal = isNative || (filename && !filename.startsWith('/') && !filename.startsWith('.') && filename.indexOf(':\\') !== 1); @@ -100,7 +106,7 @@ const node: StackLineParserFn = (line: string) => { return { filename, module: getModule(filename), - function: functionName || `${typeName}.${methodName || ''}`, + function: functionName, lineno: parseInt(lineMatch[3], 10) || undefined, colno: parseInt(lineMatch[4], 10) || undefined, in_app, diff --git a/packages/node/test/stacktrace.test.ts b/packages/node/test/stacktrace.test.ts index 1c4cd5126c85..656ba1a69a9b 100644 --- a/packages/node/test/stacktrace.test.ts +++ b/packages/node/test/stacktrace.test.ts @@ -53,13 +53,13 @@ describe('Stack parsing', () => { }); test('parses object in fn name', () => { - const err: { [key: string]: any } = {}; + const err = new Error(); err.stack = 'Error: Foo\n' + ' at [object Object].global.every [as _onTimeout] (/Users/hoitz/develop/test.coffee:36:3)\n' + ' at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)\n'; - const frames = parseStackFrames(err as Error); + const frames = parseStackFrames(err); expect(frames).toEqual([ { @@ -89,7 +89,7 @@ describe('Stack parsing', () => { }); test('parses corrupt stack', () => { - const err: { [key: string]: any } = {}; + const err = new Error(); err.stack = 'AssertionError: true == false\n' + ' fuck' + @@ -97,7 +97,7 @@ describe('Stack parsing', () => { 'oh no' + ' at TestCase.run (/Users/felix/code/node-fast-or-slow/lib/test_case.js:61:8)\n'; - const frames = parseStackFrames(err as Error); + const frames = parseStackFrames(err); expect(frames).toEqual([ { @@ -120,13 +120,13 @@ describe('Stack parsing', () => { }); test('parses with missing column numbers', () => { - const err: { [key: string]: any } = {}; + const err = new Error(); err.stack = 'AssertionError: true == false\n' + ' at Test.fn (/Users/felix/code/node-fast-or-slow/test/fast/example/test-example.js:6)\n' + ' at Test.run (/Users/felix/code/node-fast-or-slow/lib/test.js:45)'; - const frames = parseStackFrames(err as Error); + const frames = parseStackFrames(err); expect(frames).toEqual([ { @@ -147,7 +147,7 @@ describe('Stack parsing', () => { }); test('parses with native methods', () => { - const err: { [key: string]: any } = {}; + const err = new Error(); err.stack = 'AssertionError: true == false\n' + ' at Test.fn (/Users/felix/code/node-fast-or-slow/test/fast/example/test-example.js:6:10)\n' + @@ -157,7 +157,7 @@ describe('Stack parsing', () => { ' at Array.0 (native)\n' + ' at EventEmitter._tickCallback (node.js:126:26)'; - const frames = parseStackFrames(err as Error); + const frames = parseStackFrames(err); expect(frames).toEqual([ { @@ -209,16 +209,16 @@ describe('Stack parsing', () => { }); test('parses with file only', () => { - const err: { [key: string]: any } = {}; + const err = new Error(); err.stack = 'AssertionError: true == false\n' + ' at /Users/felix/code/node-fast-or-slow/lib/test_case.js:80:10'; - const frames = parseStackFrames(err as Error); + const frames = parseStackFrames(err); expect(frames).toEqual([ { filename: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', module: 'test_case', - function: 'undefined.', + function: '', lineno: 80, colno: 10, in_app: true, @@ -227,18 +227,18 @@ describe('Stack parsing', () => { }); test('parses with multi line message', () => { - const err: { [key: string]: any } = {}; + const err = new Error(); err.stack = 'AssertionError: true == false\nAnd some more shit\n' + ' at /Users/felix/code/node-fast-or-slow/lib/test_case.js:80:10'; - const frames = parseStackFrames(err as Error); + const frames = parseStackFrames(err); expect(frames).toEqual([ { filename: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', module: 'test_case', - function: 'undefined.', + function: '', lineno: 80, colno: 10, in_app: true, @@ -247,12 +247,12 @@ describe('Stack parsing', () => { }); test('parses with anonymous fn call', () => { - const err: { [key: string]: any } = {}; + const err = new Error(); err.stack = 'AssertionError: expected [] to be arguments\n' + ' at Assertion.prop.(anonymous function) (/Users/den/Projects/should.js/lib/should.js:60:14)\n'; - const frames = parseStackFrames(err as Error); + const frames = parseStackFrames(err); expect(frames).toEqual([ { @@ -267,13 +267,13 @@ describe('Stack parsing', () => { }); test('parses with braces in paths', () => { - const err: { [key: string]: any } = {}; + const err = new Error(); err.stack = 'AssertionError: true == false\n' + ' at Test.run (/Users/felix (something)/code/node-fast-or-slow/lib/test.js:45:10)\n' + ' at TestCase.run (/Users/felix (something)/code/node-fast-or-slow/lib/test_case.js:61:8)\n'; - const frames = parseStackFrames(err as Error); + const frames = parseStackFrames(err); expect(frames).toEqual([ { @@ -294,4 +294,88 @@ describe('Stack parsing', () => { }, ]); }); + + test('parses with async frames', () => { + // https://github.com/getsentry/sentry-javascript/issues/4692#issuecomment-1063835795 + const err = new Error(); + err.stack = + 'Error: Client request error\n' + + ' at Object.httpRequestError (file:///code/node_modules/@waroncancer/gaia/lib/error/error-factory.js:17:73)\n' + + ' at Object.run (file:///code/node_modules/@waroncancer/gaia/lib/http-client/http-client.js:81:36)\n' + + ' at processTicksAndRejections (node:internal/process/task_queues:96:5)\n' + + ' at async Object.send (file:///code/lib/post-created/send-post-created-notification-module.js:17:27)\n' + + ' at async each (file:///code/lib/process-post-events-module.js:14:21)\n' + + ' at async Runner.processEachMessage (/code/node_modules/kafkajs/src/consumer/runner.js:151:9)\n' + + ' at async onBatch (/code/node_modules/kafkajs/src/consumer/runner.js:326:9)\n' + + ' at async /code/node_modules/kafkajs/src/consumer/runner.js:376:15\n'; + + const frames = parseStackFrames(err); + + expect(frames).toEqual([ + { + filename: '/code/node_modules/kafkajs/src/consumer/runner.js', + module: 'kafkajs.src.consumer:runner', + function: '', + lineno: 376, + colno: 15, + in_app: false, + }, + { + filename: '/code/node_modules/kafkajs/src/consumer/runner.js', + module: 'kafkajs.src.consumer:runner', + function: 'onBatch', + lineno: 326, + colno: 9, + in_app: false, + }, + { + filename: '/code/node_modules/kafkajs/src/consumer/runner.js', + module: 'kafkajs.src.consumer:runner', + function: 'Runner.processEachMessage', + lineno: 151, + colno: 9, + in_app: false, + }, + { + filename: '/code/lib/process-post-events-module.js', + module: 'process-post-events-module', + function: 'each', + lineno: 14, + colno: 21, + in_app: true, + }, + { + filename: '/code/lib/post-created/send-post-created-notification-module.js', + module: 'send-post-created-notification-module', + function: 'Object.send', + lineno: 17, + colno: 27, + in_app: true, + }, + { + filename: 'node:internal/process/task_queues', + module: 'task_queues', + function: 'processTicksAndRejections', + lineno: 96, + colno: 5, + in_app: false, + }, + { + filename: '/code/node_modules/@waroncancer/gaia/lib/http-client/http-client.js', + module: '@waroncancer.gaia.lib.http-client:http-client', + function: 'Object.run', + lineno: 81, + colno: 36, + in_app: false, + }, + { + filename: '/code/node_modules/@waroncancer/gaia/lib/error/error-factory.js', + module: '@waroncancer.gaia.lib.error:error-factory', + function: 'Object.httpRequestError', + lineno: 17, + colno: 73, + in_app: false, + }, + ]); + }); });