UNPKG

@sentry/utils

Version:
207 lines (175 loc) 6.41 kB
Object.defineProperty(exports, '__esModule', { value: true }); const STACKTRACE_LIMIT = 50; // Used to sanitize webpack (error: *) wrapped stack errors const WEBPACK_ERROR_REGEXP = /\(error: (.*)\)/; /** * Creates a stack parser with the supplied line parsers * * StackFrames are returned in the correct order for Sentry Exception * frames and with Sentry SDK internal frames removed from the top and bottom * */ function createStackParser(...parsers) { const sortedParsers = parsers.sort((a, b) => a[0] - b[0]).map(p => p[1]); return (stack, skipFirst = 0) => { const frames = []; 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 // input size. Huge strings can result in hangs/Denial of Service: // https://github.com/getsentry/sentry-javascript/issues/2286 if (line.length > 1024) { continue; } // https://github.com/getsentry/sentry-javascript/issues/5459 // Remove webpack (error: *) wrappers const cleanedLine = WEBPACK_ERROR_REGEXP.test(line) ? line.replace(WEBPACK_ERROR_REGEXP, '$1') : line; for (const parser of sortedParsers) { const frame = parser(cleanedLine); if (frame) { frames.push(frame); break; } } } return stripSentryFramesAndReverse(frames); }; } /** * Gets a stack parser implementation from Options.stackParser * @see Options * * If options contains an array of line parsers, it is converted into a parser */ function stackParserFromStackParserOptions(stackParser) { if (Array.isArray(stackParser)) { return createStackParser(...stackParser); } return stackParser; } /** * @hidden */ function stripSentryFramesAndReverse(stack) { if (!stack.length) { return []; } let localStack = stack; const firstFrameFunction = localStack[0].function || ''; const lastFrameFunction = localStack[localStack.length - 1].function || ''; // If stack starts with one of our API calls, remove it (starts, meaning it's the top of the stack - aka last call) if (firstFrameFunction.indexOf('captureMessage') !== -1 || firstFrameFunction.indexOf('captureException') !== -1) { localStack = localStack.slice(1); } // If stack ends with one of our internal API calls, remove it (ends, meaning it's the bottom of the stack - aka top-most call) if (lastFrameFunction.indexOf('sentryWrapped') !== -1) { localStack = localStack.slice(0, -1); } // The frame where the crash happened, should be the last entry in the array return localStack .slice(0, STACKTRACE_LIMIT) .map(frame => ({ ...frame, filename: frame.filename || localStack[0].filename, function: frame.function || '?', })) .reverse(); } const defaultFunctionName = '<anonymous>'; /** * Safely extract function name from itself */ function getFunctionName(fn) { try { if (!fn || typeof fn !== 'function') { return defaultFunctionName; } return fn.name || defaultFunctionName; } catch (e) { // Just accessing custom props in some Selenium environments // can cause a "Permission denied" exception (see raven-js#495). return defaultFunctionName; } } // eslint-disable-next-line complexity function node(getModule) { const FILENAME_MATCH = /^\s*[-]{4,}$/; const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+):(\d+):(\d+)?|([^)]+))\)?/; // eslint-disable-next-line complexity return (line) => { if (line.match(FILENAME_MATCH)) { return { filename: line, }; } const lineMatch = line.match(FULL_MATCH); if (!lineMatch) { return undefined; } let object; let method; let functionName; let typeName; let methodName; if (lineMatch[1]) { functionName = lineMatch[1]; let methodStart = functionName.lastIndexOf('.'); if (functionName[methodStart - 1] === '.') { methodStart--; } if (methodStart > 0) { object = functionName.slice(0, methodStart); method = functionName.slice(methodStart + 1); const objectEnd = object.indexOf('.Module'); if (objectEnd > 0) { functionName = functionName.slice(objectEnd + 1); object = object.slice(0, objectEnd); } } typeName = undefined; } if (method) { typeName = object; methodName = method; } if (method === '<anonymous>') { methodName = undefined; functionName = undefined; } if (functionName === undefined) { methodName = methodName || '<anonymous>'; functionName = typeName ? `${typeName}.${methodName}` : methodName; } const filename = lineMatch[2] && lineMatch[2].startsWith('file://') ? lineMatch[2].slice(7) : lineMatch[2]; const isNative = lineMatch[5] === 'native'; const isInternal = isNative || (filename && !filename.startsWith('/') && !filename.startsWith('.') && filename.indexOf(':\\') !== 1); // in_app is all that's not an internal Node function or a module within node_modules // note that isNative appears to return true even for node core libraries // see https://github.com/getsentry/raven-node/issues/176 const in_app = !isInternal && filename !== undefined && !filename.includes('node_modules/'); return { filename, module: getModule ? getModule(filename) : undefined, function: functionName, lineno: parseInt(lineMatch[3], 10) || undefined, colno: parseInt(lineMatch[4], 10) || undefined, in_app, }; }; } /** * Node.js stack line parser * * This is in @sentry/utils so it can be used from the Electron SDK in the browser for when `nodeIntegration == true`. * This allows it to be used without referencing or importing any node specific code which causes bundlers to complain */ function nodeStackLineParser(getModule) { return [90, node(getModule)]; } exports.createStackParser = createStackParser; exports.getFunctionName = getFunctionName; exports.nodeStackLineParser = nodeStackLineParser; exports.stackParserFromStackParserOptions = stackParserFromStackParserOptions; exports.stripSentryFramesAndReverse = stripSentryFramesAndReverse; //# sourceMappingURL=stacktrace.js.map