long-stack-trace-zone.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. 'use strict';
  2. /**
  3. * @license Angular v<unknown>
  4. * (c) 2010-2024 Google LLC. https://angular.io/
  5. * License: MIT
  6. */
  7. /**
  8. * @fileoverview
  9. * @suppress {globalThis}
  10. */
  11. function patchLongStackTrace(Zone) {
  12. const NEWLINE = '\n';
  13. const IGNORE_FRAMES = {};
  14. const creationTrace = '__creationTrace__';
  15. const ERROR_TAG = 'STACKTRACE TRACKING';
  16. const SEP_TAG = '__SEP_TAG__';
  17. let sepTemplate = SEP_TAG + '@[native]';
  18. class LongStackTrace {
  19. constructor() {
  20. this.error = getStacktrace();
  21. this.timestamp = new Date();
  22. }
  23. }
  24. function getStacktraceWithUncaughtError() {
  25. return new Error(ERROR_TAG);
  26. }
  27. function getStacktraceWithCaughtError() {
  28. try {
  29. throw getStacktraceWithUncaughtError();
  30. }
  31. catch (err) {
  32. return err;
  33. }
  34. }
  35. // Some implementations of exception handling don't create a stack trace if the exception
  36. // isn't thrown, however it's faster not to actually throw the exception.
  37. const error = getStacktraceWithUncaughtError();
  38. const caughtError = getStacktraceWithCaughtError();
  39. const getStacktrace = error.stack
  40. ? getStacktraceWithUncaughtError
  41. : caughtError.stack
  42. ? getStacktraceWithCaughtError
  43. : getStacktraceWithUncaughtError;
  44. function getFrames(error) {
  45. return error.stack ? error.stack.split(NEWLINE) : [];
  46. }
  47. function addErrorStack(lines, error) {
  48. let trace = getFrames(error);
  49. for (let i = 0; i < trace.length; i++) {
  50. const frame = trace[i];
  51. // Filter out the Frames which are part of stack capturing.
  52. if (!IGNORE_FRAMES.hasOwnProperty(frame)) {
  53. lines.push(trace[i]);
  54. }
  55. }
  56. }
  57. function renderLongStackTrace(frames, stack) {
  58. const longTrace = [stack ? stack.trim() : ''];
  59. if (frames) {
  60. let timestamp = new Date().getTime();
  61. for (let i = 0; i < frames.length; i++) {
  62. const traceFrames = frames[i];
  63. const lastTime = traceFrames.timestamp;
  64. let separator = `____________________Elapsed ${timestamp - lastTime.getTime()} ms; At: ${lastTime}`;
  65. separator = separator.replace(/[^\w\d]/g, '_');
  66. longTrace.push(sepTemplate.replace(SEP_TAG, separator));
  67. addErrorStack(longTrace, traceFrames.error);
  68. timestamp = lastTime.getTime();
  69. }
  70. }
  71. return longTrace.join(NEWLINE);
  72. }
  73. // if Error.stackTraceLimit is 0, means stack trace
  74. // is disabled, so we don't need to generate long stack trace
  75. // this will improve performance in some test(some test will
  76. // set stackTraceLimit to 0, https://github.com/angular/zone.js/issues/698
  77. function stackTracesEnabled() {
  78. // Cast through any since this property only exists on Error in the nodejs
  79. // typings.
  80. return Error.stackTraceLimit > 0;
  81. }
  82. Zone['longStackTraceZoneSpec'] = {
  83. name: 'long-stack-trace',
  84. longStackTraceLimit: 10, // Max number of task to keep the stack trace for.
  85. // add a getLongStackTrace method in spec to
  86. // handle handled reject promise error.
  87. getLongStackTrace: function (error) {
  88. if (!error) {
  89. return undefined;
  90. }
  91. const trace = error[Zone.__symbol__('currentTaskTrace')];
  92. if (!trace) {
  93. return error.stack;
  94. }
  95. return renderLongStackTrace(trace, error.stack);
  96. },
  97. onScheduleTask: function (parentZoneDelegate, currentZone, targetZone, task) {
  98. if (stackTracesEnabled()) {
  99. const currentTask = Zone.currentTask;
  100. let trace = (currentTask && currentTask.data && currentTask.data[creationTrace]) || [];
  101. trace = [new LongStackTrace()].concat(trace);
  102. if (trace.length > this.longStackTraceLimit) {
  103. trace.length = this.longStackTraceLimit;
  104. }
  105. if (!task.data)
  106. task.data = {};
  107. if (task.type === 'eventTask') {
  108. // Fix issue https://github.com/angular/zone.js/issues/1195,
  109. // For event task of browser, by default, all task will share a
  110. // singleton instance of data object, we should create a new one here
  111. // The cast to `any` is required to workaround a closure bug which wrongly applies
  112. // URL sanitization rules to .data access.
  113. task.data = { ...task.data };
  114. }
  115. task.data[creationTrace] = trace;
  116. }
  117. return parentZoneDelegate.scheduleTask(targetZone, task);
  118. },
  119. onHandleError: function (parentZoneDelegate, currentZone, targetZone, error) {
  120. if (stackTracesEnabled()) {
  121. const parentTask = Zone.currentTask || error.task;
  122. if (error instanceof Error && parentTask) {
  123. const longStack = renderLongStackTrace(parentTask.data && parentTask.data[creationTrace], error.stack);
  124. try {
  125. error.stack = error.longStack = longStack;
  126. }
  127. catch (err) { }
  128. }
  129. }
  130. return parentZoneDelegate.handleError(targetZone, error);
  131. },
  132. };
  133. function captureStackTraces(stackTraces, count) {
  134. if (count > 0) {
  135. stackTraces.push(getFrames(new LongStackTrace().error));
  136. captureStackTraces(stackTraces, count - 1);
  137. }
  138. }
  139. function computeIgnoreFrames() {
  140. if (!stackTracesEnabled()) {
  141. return;
  142. }
  143. const frames = [];
  144. captureStackTraces(frames, 2);
  145. const frames1 = frames[0];
  146. const frames2 = frames[1];
  147. for (let i = 0; i < frames1.length; i++) {
  148. const frame1 = frames1[i];
  149. if (frame1.indexOf(ERROR_TAG) == -1) {
  150. let match = frame1.match(/^\s*at\s+/);
  151. if (match) {
  152. sepTemplate = match[0] + SEP_TAG + ' (http://localhost)';
  153. break;
  154. }
  155. }
  156. }
  157. for (let i = 0; i < frames1.length; i++) {
  158. const frame1 = frames1[i];
  159. const frame2 = frames2[i];
  160. if (frame1 === frame2) {
  161. IGNORE_FRAMES[frame1] = true;
  162. }
  163. else {
  164. break;
  165. }
  166. }
  167. }
  168. computeIgnoreFrames();
  169. }
  170. patchLongStackTrace(Zone);