diff --git a/packages/node/package.json b/packages/node/package.json index df67320a65f7..8a0a9b3bea42 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -20,11 +20,9 @@ "@sentry/hub": "5.0.0-beta0", "@sentry/types": "5.0.0-beta0", "@sentry/utils": "5.0.0-beta0", - "@types/stack-trace": "0.0.29", "cookie": "0.3.1", "https-proxy-agent": "2.2.1", "lru_map": "0.3.3", - "stack-trace": "0.0.10", "tslib": "^1.9.3" }, "devDependencies": { diff --git a/packages/node/src/parsers.ts b/packages/node/src/parsers.ts index d987a7971eb4..78422c24e21a 100644 --- a/packages/node/src/parsers.ts +++ b/packages/node/src/parsers.ts @@ -4,9 +4,9 @@ import { snipLine } from '@sentry/utils/string'; import { SyncPromise } from '@sentry/utils/syncpromise'; import { readFile } from 'fs'; import { LRUMap } from 'lru_map'; -import * as stacktrace from 'stack-trace'; import { NodeOptions } from './backend'; +import * as stacktrace from './stack-trace'; // tslint:disable-next-line:no-unsafe-any const DEFAULT_LINES_OF_CONTEXT: number = 7; @@ -23,7 +23,7 @@ export function resetFileContentCache(): void { /** JSDoc */ function getFunction(frame: stacktrace.StackFrame): string { try { - return frame.getFunctionName() || `${frame.getTypeName()}.${frame.getMethodName() || ''}`; + return frame.functionName || `${frame.typeName}.${frame.methodName || ''}`; } catch (e) { // This seems to happen sometimes when using 'use strict', // stemming from `getTypeName`. @@ -142,14 +142,14 @@ export function parseStack(stack: stacktrace.StackFrame[], options?: NodeOptions const frames: StackFrame[] = stack.map(frame => { const parsedFrame: StackFrame = { - colno: frame.getColumnNumber(), - filename: frame.getFileName() || '', + colno: frame.columnNumber, + filename: frame.fileName || '', function: getFunction(frame), - lineno: frame.getLineNumber(), + lineno: frame.lineNumber, }; const isInternal = - frame.isNative() || + frame.native || (parsedFrame.filename && !parsedFrame.filename.startsWith('/') && !parsedFrame.filename.startsWith('.') && diff --git a/packages/node/src/stack-trace.ts b/packages/node/src/stack-trace.ts new file mode 100644 index 000000000000..1f642cca1bdf --- /dev/null +++ b/packages/node/src/stack-trace.ts @@ -0,0 +1,99 @@ +/** + * stack-trace - Parses node.js stack traces + * + * This was originally forked to fix this issue: + * https://github.com/felixge/node-stack-trace/issues/31 + * + * Mar 19,2019 - #4fd379e + * + * https://github.com/felixge/node-stack-trace/ + * @license MIT + */ + +/** Decoded StackFrame */ +export interface StackFrame { + fileName: string; + lineNumber: number; + functionName: string; + typeName: string; + methodName: string; + native: boolean; + columnNumber: number; +} + +/** Extracts StackFrames fron the Error */ +export function parse(err: Error): StackFrame[] { + if (!err.stack) { + return []; + } + + const lines = err.stack.split('\n').slice(1); + + return lines + .map(line => { + if (line.match(/^\s*[-]{4,}$/)) { + return { + columnNumber: null, + fileName: line, + functionName: null, + lineNumber: null, + methodName: null, + native: null, + typeName: null, + }; + } + + const lineMatch = line.match(/at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/); + if (!lineMatch) { + return undefined; + } + + let object = null; + let method = null; + let functionName = null; + let typeName = null; + let methodName = null; + const isNative = lineMatch[5] === 'native'; + + if (lineMatch[1]) { + functionName = lineMatch[1]; + let methodStart = functionName.lastIndexOf('.'); + if (functionName[methodStart - 1] === '.') { + methodStart--; + } + if (methodStart > 0) { + object = functionName.substr(0, methodStart); + method = functionName.substr(methodStart + 1); + const objectEnd = object.indexOf('.Module'); + if (objectEnd > 0) { + functionName = functionName.substr(objectEnd + 1); + object = object.substr(0, objectEnd); + } + } + typeName = null; + } + + if (method) { + typeName = object; + methodName = method; + } + + if (method === '') { + methodName = null; + functionName = null; + } + + const properties = { + columnNumber: parseInt(lineMatch[4], 10) || null, + fileName: lineMatch[2] || null, + functionName, + lineNumber: parseInt(lineMatch[3], 10) || null, + methodName, + native: isNative, + typeName, + }; + + return properties; + }) + .filter(callSite => !!callSite) as StackFrame[]; +} diff --git a/packages/node/test/parsers.test.ts b/packages/node/test/parsers.test.ts index 5e685f4ae8f5..ca17bcb249c8 100644 --- a/packages/node/test/parsers.test.ts +++ b/packages/node/test/parsers.test.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; -import * as stacktrace from 'stack-trace'; import * as Parsers from '../src/parsers'; +import * as stacktrace from '../src/stack-trace'; import { getError } from './helper/error'; diff --git a/packages/node/test/stack-trace.test.ts b/packages/node/test/stack-trace.test.ts new file mode 100644 index 000000000000..6da02b4c69c3 --- /dev/null +++ b/packages/node/test/stack-trace.test.ts @@ -0,0 +1,170 @@ +/** + * stack-trace - Parses node.js stack traces + * + * These tests were originally forked to fix this issue: + * https://github.com/felixge/node-stack-trace/issues/31 + * + * Mar 19,2019 - #4fd379e + * + * https://github.com/felixge/node-stack-trace/ + * @license MIT + */ + +import * as stacktrace from '../src/stack-trace'; + +// tslint:disable:typedef +// tslint:disable:prefer-template + +function testBasic() { + return new Error('something went wrong'); +} + +function testWrapper() { + return testBasic(); +} + +describe('stack-trace.ts', () => { + test('testObjectInMethodName', () => { + const err: { [key: string]: any } = {}; + 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 trace = stacktrace.parse(err as Error); + + expect(trace[0].fileName).toEqual('/Users/hoitz/develop/test.coffee'); + expect(trace[1].fileName).toEqual('timers.js'); + }); + + test('testBasic', () => { + const trace = stacktrace.parse(testBasic()); + + expect(trace[0].fileName).toEqual(__filename); + expect(trace[0].functionName).toEqual('testBasic'); + }); + + test('testWrapper', () => { + const trace = stacktrace.parse(testWrapper()); + + expect(trace[0].functionName).toEqual('testBasic'); + expect(trace[1].functionName).toEqual('testWrapper'); + }); + + test('testNoStack', () => { + const err = { stack: undefined }; + const trace = stacktrace.parse(err as Error); + + expect(trace).toEqual([]); + }); + + test('testCorruptStack', () => { + const err: { [key: string]: any } = {}; + err.stack = + 'AssertionError: true == false\n' + + ' fuck' + + ' at Test.run (/Users/felix/code/node-fast-or-slow/lib/test.js:45:10)\n' + + 'oh no' + + ' at TestCase.run (/Users/felix/code/node-fast-or-slow/lib/test_case.js:61:8)\n'; + + const trace = stacktrace.parse(err as Error); + + expect(trace.length).toEqual(2); + }); + + test('testTraceWitoutColumnNumbers', () => { + const err: { [key: string]: any } = {}; + 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 trace = stacktrace.parse(err as Error); + + expect(trace[0].fileName).toEqual('/Users/felix/code/node-fast-or-slow/test/fast/example/test-example.js'); + expect(trace[0].lineNumber).toEqual(6); + expect(trace[0].columnNumber).toEqual(null); + }); + + test('testStackWithNativeCall', () => { + const err: { [key: string]: any } = {}; + 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' + + ' at Test.run (/Users/felix/code/node-fast-or-slow/lib/test.js:45:10)\n' + + ' at TestCase.runNext (/Users/felix/code/node-fast-or-slow/lib/test_case.js:73:8)\n' + + ' at TestCase.run (/Users/felix/code/node-fast-or-slow/lib/test_case.js:61:8)\n' + + ' at Array.0 (native)\n' + + ' at EventEmitter._tickCallback (node.js:126:26)'; + + const trace = stacktrace.parse(err as Error); + const nativeCallSite = trace[4]; + + expect(nativeCallSite.fileName).toEqual(null); + expect(nativeCallSite.functionName).toEqual('Array.0'); + expect(nativeCallSite.typeName).toEqual('Array'); + expect(nativeCallSite.methodName).toEqual('0'); + expect(nativeCallSite.lineNumber).toEqual(null); + expect(nativeCallSite.columnNumber).toEqual(null); + expect(nativeCallSite.native).toEqual(true); + }); + + test('testStackWithFileOnly', () => { + const err: { [key: string]: any } = {}; + err.stack = 'AssertionError: true == false\n' + ' at /Users/felix/code/node-fast-or-slow/lib/test_case.js:80:10'; + + const trace = stacktrace.parse(err as Error); + const callSite = trace[0]; + + expect(callSite.fileName).toEqual('/Users/felix/code/node-fast-or-slow/lib/test_case.js'); + expect(callSite.functionName).toEqual(null); + expect(callSite.typeName).toEqual(null); + expect(callSite.methodName).toEqual(null); + expect(callSite.lineNumber).toEqual(80); + expect(callSite.columnNumber).toEqual(10); + expect(callSite.native).toEqual(false); + }); + + test('testStackWithMultilineMessage', () => { + const err: { [key: string]: any } = {}; + 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 trace = stacktrace.parse(err as Error); + const callSite = trace[0]; + + expect(callSite.fileName).toEqual('/Users/felix/code/node-fast-or-slow/lib/test_case.js'); + }); + + test('testStackWithAnonymousFunctionCall', () => { + const err: { [key: string]: any } = {}; + 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 trace = stacktrace.parse(err as Error); + const callSite0 = trace[0]; + + expect(callSite0.fileName).toEqual('/Users/den/Projects/should.js/lib/should.js'); + expect(callSite0.functionName).toEqual('Assertion.prop.(anonymous function)'); + expect(callSite0.typeName).toEqual('Assertion.prop'); + expect(callSite0.methodName).toEqual('(anonymous function)'); + expect(callSite0.lineNumber).toEqual(60); + expect(callSite0.columnNumber).toEqual(14); + expect(callSite0.native).toEqual(false); + }); + + test('testTraceBracesInPath', () => { + const err: { [key: string]: any } = {}; + 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 trace = stacktrace.parse(err as Error); + + expect(trace.length).toEqual(2); + expect(trace[0].fileName).toEqual('/Users/felix (something)/code/node-fast-or-slow/lib/test.js'); + }); +}); diff --git a/yarn.lock b/yarn.lock index fe2a4c835319..f39c0400c47e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1325,10 +1325,6 @@ resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.10.tgz#1f921f0c347b19f754e61dbc671c088df73fe1ff" integrity sha512-4w7SvsiUOtd4mUfund9QROPSJ5At/GQskDpqd87pJIRI6ULWSJqHI3GIZE337wQuN3aznroJGr94+o8fwvL37Q== -"@types/stack-trace@0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/stack-trace/-/stack-trace-0.0.29.tgz#eb7a7c60098edb35630ed900742a5ecb20cfcb4d" - "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -9803,10 +9799,6 @@ ssri@^6.0.0, ssri@^6.0.1: dependencies: figgy-pudding "^3.5.1" -stack-trace@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - stack-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"