long-stack-trace.ts 6.0 KB

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