stacktrace.js 5.1 KB

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