long-stack-trace-zone.js 6.5 KB

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