devErrorSymbolicationEventProcessor.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { _nullishCoalesce, _optionalChain } from '@sentry/utils';
  2. import { GLOBAL_OBJ } from '@sentry/utils';
  3. import * as stackTraceParser from 'stacktrace-parser';
  4. const globalWithInjectedValues = GLOBAL_OBJ
  5. ;
  6. async function resolveStackFrame(
  7. frame,
  8. error,
  9. ) {
  10. try {
  11. if (!(_optionalChain([frame, 'access', _ => _.file, 'optionalAccess', _2 => _2.startsWith, 'call', _3 => _3('webpack-internal:')]) || _optionalChain([frame, 'access', _4 => _4.file, 'optionalAccess', _5 => _5.startsWith, 'call', _6 => _6('file:')]))) {
  12. return null;
  13. }
  14. const params = new URLSearchParams();
  15. params.append('isServer', String(false)); // doesn't matter since it is overwritten by isAppDirectory
  16. params.append('isEdgeServer', String(false)); // doesn't matter since it is overwritten by isAppDirectory
  17. params.append('isAppDirectory', String(true)); // will force server to do more thorough checking
  18. params.append('errorMessage', error.toString());
  19. Object.keys(frame).forEach(key => {
  20. params.append(key, (_nullishCoalesce(frame[key ], () => ( ''))).toString());
  21. });
  22. let basePath = _nullishCoalesce(globalWithInjectedValues.__sentryBasePath, () => ( ''));
  23. // Prefix the basepath with a slash if it doesn't have one
  24. if (basePath !== '' && !basePath.match(/^\//)) {
  25. basePath = `/${basePath}`;
  26. }
  27. const controller = new AbortController();
  28. const timer = setTimeout(() => controller.abort(), 3000);
  29. const res = await fetch(
  30. `${
  31. // eslint-disable-next-line no-restricted-globals
  32. typeof window === 'undefined' ? 'http://localhost:3000' : '' // TODO: handle the case where users define a different port
  33. }${basePath}/__nextjs_original-stack-frame?${params.toString()}`,
  34. {
  35. signal: controller.signal,
  36. },
  37. ).finally(() => {
  38. clearTimeout(timer);
  39. });
  40. if (!res.ok || res.status === 204) {
  41. return null;
  42. }
  43. const body = await res.json();
  44. return {
  45. originalCodeFrame: body.originalCodeFrame,
  46. originalStackFrame: body.originalStackFrame,
  47. };
  48. } catch (e) {
  49. return null;
  50. }
  51. }
  52. function parseOriginalCodeFrame(codeFrame)
  53. {
  54. const preProcessedLines = codeFrame
  55. // Remove ASCII control characters that are used for syntax highlighting
  56. .replace(
  57. // eslint-disable-next-line no-control-regex
  58. /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, // https://stackoverflow.com/a/29497680
  59. '',
  60. )
  61. .split('\n')
  62. // Remove line that is supposed to indicate where the error happened
  63. .filter(line => !line.match(/^\s*\|/))
  64. // Find the error line
  65. .map(line => ({
  66. line,
  67. isErrorLine: !!line.match(/^>/),
  68. }))
  69. // Remove the leading part that is just for prettier output
  70. .map(lineObj => ({
  71. ...lineObj,
  72. line: lineObj.line.replace(/^.*\|/, ''),
  73. }));
  74. const preContextLines = [];
  75. let contextLine = undefined;
  76. const postContextLines = [];
  77. let reachedContextLine = false;
  78. for (const preProcessedLine of preProcessedLines) {
  79. if (preProcessedLine.isErrorLine) {
  80. contextLine = preProcessedLine.line;
  81. reachedContextLine = true;
  82. } else if (reachedContextLine) {
  83. postContextLines.push(preProcessedLine.line);
  84. } else {
  85. preContextLines.push(preProcessedLine.line);
  86. }
  87. }
  88. return {
  89. contextLine,
  90. preContextLines,
  91. postContextLines,
  92. };
  93. }
  94. /**
  95. * Event processor that will symbolicate errors by using the webpack/nextjs dev server that is used to show stack traces
  96. * in the dev overlay.
  97. */
  98. async function devErrorSymbolicationEventProcessor(event, hint) {
  99. // Due to changes across Next.js versions, there are a million things that can go wrong here so we just try-catch the // entire event processor.Symbolicated stack traces are just a nice to have.
  100. try {
  101. if (hint.originalException && hint.originalException instanceof Error && hint.originalException.stack) {
  102. const frames = stackTraceParser.parse(hint.originalException.stack);
  103. const resolvedFrames = await Promise.all(
  104. frames.map(frame => resolveStackFrame(frame, hint.originalException )),
  105. );
  106. if (_optionalChain([event, 'access', _7 => _7.exception, 'optionalAccess', _8 => _8.values, 'optionalAccess', _9 => _9[0], 'access', _10 => _10.stacktrace, 'optionalAccess', _11 => _11.frames])) {
  107. event.exception.values[0].stacktrace.frames = event.exception.values[0].stacktrace.frames.map(
  108. (frame, i, frames) => {
  109. const resolvedFrame = resolvedFrames[frames.length - 1 - i];
  110. if (!resolvedFrame || !resolvedFrame.originalStackFrame || !resolvedFrame.originalCodeFrame) {
  111. return {
  112. ...frame,
  113. platform: _optionalChain([frame, 'access', _12 => _12.filename, 'optionalAccess', _13 => _13.startsWith, 'call', _14 => _14('node:internal')]) ? 'nodejs' : undefined, // simple hack that will prevent a source mapping error from showing up
  114. in_app: false,
  115. };
  116. }
  117. const { contextLine, preContextLines, postContextLines } = parseOriginalCodeFrame(
  118. resolvedFrame.originalCodeFrame,
  119. );
  120. return {
  121. ...frame,
  122. pre_context: preContextLines,
  123. context_line: contextLine,
  124. post_context: postContextLines,
  125. function: resolvedFrame.originalStackFrame.methodName,
  126. filename: resolvedFrame.originalStackFrame.file || undefined,
  127. lineno: resolvedFrame.originalStackFrame.lineNumber || undefined,
  128. colno: resolvedFrame.originalStackFrame.column || undefined,
  129. };
  130. },
  131. );
  132. }
  133. }
  134. } catch (e) {
  135. return event;
  136. }
  137. return event;
  138. }
  139. export { devErrorSymbolicationEventProcessor };
  140. //# sourceMappingURL=devErrorSymbolicationEventProcessor.js.map