/ shared / logger / node_modules / @sentry / utils / esm / stacktrace.js
stacktrace.js
  1  import { node } from './node-stack-trace.js';
  2  
  3  const STACKTRACE_FRAME_LIMIT = 50;
  4  // Used to sanitize webpack (error: *) wrapped stack errors
  5  const WEBPACK_ERROR_REGEXP = /\(error: (.*)\)/;
  6  
  7  /**
  8   * Creates a stack parser with the supplied line parsers
  9   *
 10   * StackFrames are returned in the correct order for Sentry Exception
 11   * frames and with Sentry SDK internal frames removed from the top and bottom
 12   *
 13   */
 14  function createStackParser(...parsers) {
 15    const sortedParsers = parsers.sort((a, b) => a[0] - b[0]).map(p => p[1]);
 16  
 17    return (stack, skipFirst = 0) => {
 18      const frames = [];
 19      const lines = stack.split('\n');
 20  
 21      for (let i = skipFirst; i < lines.length; i++) {
 22        const line = lines[i];
 23        // Ignore lines over 1kb as they are unlikely to be stack frames.
 24        // Many of the regular expressions use backtracking which results in run time that increases exponentially with
 25        // input size. Huge strings can result in hangs/Denial of Service:
 26        // https://github.com/getsentry/sentry-javascript/issues/2286
 27        if (line.length > 1024) {
 28          continue;
 29        }
 30  
 31        // https://github.com/getsentry/sentry-javascript/issues/5459
 32        // Remove webpack (error: *) wrappers
 33        const cleanedLine = WEBPACK_ERROR_REGEXP.test(line) ? line.replace(WEBPACK_ERROR_REGEXP, '$1') : line;
 34  
 35        // https://github.com/getsentry/sentry-javascript/issues/7813
 36        // Skip Error: lines
 37        if (cleanedLine.match(/\S*Error: /)) {
 38          continue;
 39        }
 40  
 41        for (const parser of sortedParsers) {
 42          const frame = parser(cleanedLine);
 43  
 44          if (frame) {
 45            frames.push(frame);
 46            break;
 47          }
 48        }
 49  
 50        if (frames.length >= STACKTRACE_FRAME_LIMIT) {
 51          break;
 52        }
 53      }
 54  
 55      return stripSentryFramesAndReverse(frames);
 56    };
 57  }
 58  
 59  /**
 60   * Gets a stack parser implementation from Options.stackParser
 61   * @see Options
 62   *
 63   * If options contains an array of line parsers, it is converted into a parser
 64   */
 65  function stackParserFromStackParserOptions(stackParser) {
 66    if (Array.isArray(stackParser)) {
 67      return createStackParser(...stackParser);
 68    }
 69    return stackParser;
 70  }
 71  
 72  /**
 73   * Removes Sentry frames from the top and bottom of the stack if present and enforces a limit of max number of frames.
 74   * Assumes stack input is ordered from top to bottom and returns the reverse representation so call site of the
 75   * function that caused the crash is the last frame in the array.
 76   * @hidden
 77   */
 78  function stripSentryFramesAndReverse(stack) {
 79    if (!stack.length) {
 80      return [];
 81    }
 82  
 83    const localStack = stack.slice(0, STACKTRACE_FRAME_LIMIT);
 84  
 85    const lastFrameFunction = localStack[localStack.length - 1].function;
 86    // If stack starts with one of our API calls, remove it (starts, meaning it's the top of the stack - aka last call)
 87    if (lastFrameFunction && /sentryWrapped/.test(lastFrameFunction)) {
 88      localStack.pop();
 89    }
 90  
 91    // Reversing in the middle of the procedure allows us to just pop the values off the stack
 92    localStack.reverse();
 93  
 94    const firstFrameFunction = localStack[localStack.length - 1].function;
 95    // 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)
 96    if (firstFrameFunction && /captureMessage|captureException/.test(firstFrameFunction)) {
 97      localStack.pop();
 98    }
 99  
100    return localStack.map(frame => ({
101      ...frame,
102      filename: frame.filename || localStack[localStack.length - 1].filename,
103      function: frame.function || '?',
104    }));
105  }
106  
107  const defaultFunctionName = '<anonymous>';
108  
109  /**
110   * Safely extract function name from itself
111   */
112  function getFunctionName(fn) {
113    try {
114      if (!fn || typeof fn !== 'function') {
115        return defaultFunctionName;
116      }
117      return fn.name || defaultFunctionName;
118    } catch (e) {
119      // Just accessing custom props in some Selenium environments
120      // can cause a "Permission denied" exception (see raven-js#495).
121      return defaultFunctionName;
122    }
123  }
124  
125  /**
126   * Node.js stack line parser
127   *
128   * This is in @sentry/utils so it can be used from the Electron SDK in the browser for when `nodeIntegration == true`.
129   * This allows it to be used without referencing or importing any node specific code which causes bundlers to complain
130   */
131  function nodeStackLineParser(getModule) {
132    return [90, node(getModule)];
133  }
134  
135  export { createStackParser, getFunctionName, nodeStackLineParser, stackParserFromStackParserOptions, stripSentryFramesAndReverse };
136  //# sourceMappingURL=stacktrace.js.map