package.build.esm.stacktrace.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of utils Show documentation
Show all versions of utils Show documentation
Utilities for all Sentry JavaScript SDKs
The newest version!
const STACKTRACE_FRAME_LIMIT = 50;
const UNKNOWN_FUNCTION = '?';
// Used to sanitize webpack (error: *) wrapped stack errors
const WEBPACK_ERROR_REGEXP = /\(error: (.*)\)/;
const STRIP_FRAME_REGEXP = /captureMessage|captureException/;
/**
* 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, skipFirstLines = 0, framesToPop = 0) => {
const frames = [];
const lines = stack.split('\n');
for (let i = skipFirstLines; i < lines.length; i++) {
const line = lines[i] ;
// 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;
// https://github.com/getsentry/sentry-javascript/issues/7813
// Skip Error: lines
if (cleanedLine.match(/\S*Error: /)) {
continue;
}
for (const parser of sortedParsers) {
const frame = parser(cleanedLine);
if (frame) {
frames.push(frame);
break;
}
}
if (frames.length >= STACKTRACE_FRAME_LIMIT + framesToPop) {
break;
}
}
return stripSentryFramesAndReverse(frames.slice(framesToPop));
};
}
/**
* 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;
}
/**
* Removes Sentry frames from the top and bottom of the stack if present and enforces a limit of max number of frames.
* Assumes stack input is ordered from top to bottom and returns the reverse representation so call site of the
* function that caused the crash is the last frame in the array.
* @hidden
*/
function stripSentryFramesAndReverse(stack) {
if (!stack.length) {
return [];
}
const localStack = Array.from(stack);
// If stack starts with one of our API calls, remove it (starts, meaning it's the top of the stack - aka last call)
if (/sentryWrapped/.test(getLastStackFrame(localStack).function || '')) {
localStack.pop();
}
// Reversing in the middle of the procedure allows us to just pop the values off the stack
localStack.reverse();
// 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 (STRIP_FRAME_REGEXP.test(getLastStackFrame(localStack).function || '')) {
localStack.pop();
// When using synthetic events, we will have a 2 levels deep stack, as `new Error('Sentry syntheticException')`
// is produced within the hub itself, making it:
//
// Sentry.captureException()
// getCurrentHub().captureException()
//
// instead of just the top `Sentry` call itself.
// This forces us to possibly strip an additional frame in the exact same was as above.
if (STRIP_FRAME_REGEXP.test(getLastStackFrame(localStack).function || '')) {
localStack.pop();
}
}
return localStack.slice(0, STACKTRACE_FRAME_LIMIT).map(frame => ({
...frame,
filename: frame.filename || getLastStackFrame(localStack).filename,
function: frame.function || UNKNOWN_FUNCTION,
}));
}
function getLastStackFrame(arr) {
return arr[arr.length - 1] || {};
}
const defaultFunctionName = '';
/**
* 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;
}
}
/**
* Get's stack frames from an event without needing to check for undefined properties.
*/
function getFramesFromEvent(event) {
const exception = event.exception;
if (exception) {
const frames = [];
try {
// @ts-expect-error Object could be undefined
exception.values.forEach(value => {
// @ts-expect-error Value could be undefined
if (value.stacktrace.frames) {
// @ts-expect-error Value could be undefined
frames.push(...value.stacktrace.frames);
}
});
return frames;
} catch (_oO) {
return undefined;
}
}
return undefined;
}
export { UNKNOWN_FUNCTION, createStackParser, getFramesFromEvent, getFunctionName, stackParserFromStackParserOptions, stripSentryFramesAndReverse };
//# sourceMappingURL=stacktrace.js.map