Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
feat(error): add a helper method to get non zone aware stack trace
Browse files Browse the repository at this point in the history
  • Loading branch information
JiaLiPassion committed Feb 12, 2017
1 parent f3547cc commit ab32b37
Show file tree
Hide file tree
Showing 10 changed files with 420 additions and 6 deletions.
159 changes: 159 additions & 0 deletions dist/zone-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
(factory());
}(this, (function () { 'use strict';

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
((function (_global) {
var ErrorType = _global['Error'];
var blackListedStackFrames = {};
var zoneAwareFunctionNames = ['Zone', 'drainMicrotask', 'getStacktraceWithUncaughtError', 'new LongStackTrace', 'Object.onScheduleTask'];
var detectZone = Zone.root.fork(Zone['longStackTraceZoneSpec']).fork({
name: 'detectZone',
onHandleError: function (parentDelegate, currentZone, targetZone, error) {
parentDelegate.handleError(targetZone, error);
var frames = error.stack.split(/\n/);
var runFrame = false, runGuardedFrame = false, runTaskFrame = false;
var _loop_1 = function () {
var frame = frames.shift();
// On safari it is possible to have stack frame with no line number.
// This check makes sure that we don't filter frames on name only (must have
// linenumber)
if (/:\d+:\d+/.test(frame)) {
var f_1 = frame.split(' [')[0];
if (zoneAwareFunctionNames.filter(function (zf) { return f_1.toLowerCase().indexOf(f_1.toLowerCase()) !== -1; }).length > 0) {
blackListedStackFrames[f_1] = f_1;
}
}
};
while (frames.length) {
_loop_1();
}
return false;
}
});
var detectZoneWithCallbacks = Zone.root.fork(Zone['longStackTraceZoneSpec']).fork({
name: 'detectZone',
onFork: function (parentDelegate, currentZone, targetZone, zoneSpec) {
return parentDelegate.fork(targetZone, zoneSpec);
},
onIntercept: function (parentDelegate, currentZone, targetZone, delegate, source) {
return parentDelegate.intercept(targetZone, delegate, source);
},
onInvoke: function (parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) {
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
},
onScheduleTask: function (parentZoneDelegate, currentZone, targetZone, task) {
return parentZoneDelegate.scheduleTask(targetZone, task);
},
onInvokeTask: function (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) {
return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs);
},
onCancelTask: function (parentZoneDelegate, currentZone, targetZone, task) {
return parentZoneDelegate.cancelTask(targetZone, task);
},
onHasTask: function (delegate, current, target, hasTaskState) {
return delegate.hasTask(target, hasTaskState);
},
onHandleError: function (parentDelegate, currentZone, targetZone, error) {
parentDelegate.handleError(targetZone, error);
var frames = error.stack.split(/\n/);
var runFrame = false, runGuardedFrame = false, runTaskFrame = false;
while (frames.length) {
var frame = frames.shift();
// On safari it is possible to have stack frame with no line number.
// This check makes sure that we don't filter frames on name only (must have
// linenumber)
if (/:\d+:\d+/.test(frame)) {
var f = frame.split(' [')[0];
if (f.indexOf('Zone') !== -1 || f.toLowerCase().indexOf('zoneaware') !== -1 || f.indexOf('drainMicroTaskQueue') !== -1) {
blackListedStackFrames[f] = f;
}
}
}
return false;
}
});
var detectFn = function () {
throw new Error('zoneAwareFrames');
};
var detectPromiseFn = function () {
var p = new Promise(function (resolve, reject) {
reject(new Error('zoneAwareFrames'));
});
};
var detectPromiseCaughtFn = function () {
var p = new Promise(function (resolve, reject) {
reject(new Error('zoneAwareFrames'));
});
p.catch(function (err) {
throw err;
});
};
// Cause the error to extract the stack frames.
detectZone.runTask(detectZone.scheduleEventTask('detect', detectFn, null, function () { return null; }, null));
detectZoneWithCallbacks.runTask(detectZoneWithCallbacks.scheduleEventTask('detect', detectFn, null, function () { return null; }, null));
detectZone.runTask(detectZone.scheduleMacroTask('detect', detectFn, null, function () { return null; }, null));
detectZoneWithCallbacks.runTask(detectZoneWithCallbacks.scheduleMacroTask('detect', detectFn, null, function () { return null; }, null));
detectZone.runTask(detectZone.scheduleMicroTask('detect', detectFn, null, function () { return null; }));
detectZoneWithCallbacks.runTask(detectZoneWithCallbacks.scheduleMicroTask('detect', detectFn, null, function () { return null; }));
detectZone.runGuarded(function () {
detectZone.run(detectFn);
});
detectZoneWithCallbacks.runGuarded(function () {
detectZone.run(detectFn);
});
detectZone.runGuarded(detectPromiseFn);
detectZoneWithCallbacks.runGuarded(detectPromiseFn);
detectZone.runGuarded(detectPromiseCaughtFn);
detectZoneWithCallbacks.runGuarded(detectPromiseCaughtFn);
var otherZoneAwareFunctionNames = ['ZoneTask.invoke', 'ZoneAware'];
Object.defineProperty(ErrorType, 'getNonZoneAwareStack', {
value: function (err) {
if (err.stack) {
var frames_1 = err.stack.split('\n');
var simplifiedFrames = [];
var _loop_2 = function (i) {
var frame = frames_1[i].split(' [')[0];
var frameWithoutZone = frame.split(' [')[0];
if (blackListedStackFrames.hasOwnProperty(frameWithoutZone) &&
blackListedStackFrames[frameWithoutZone]) {
frames_1.splice(i, 1);
i--;
}
else if (otherZoneAwareFunctionNames.filter(function (f) { return frame.toLowerCase().indexOf(f.toLowerCase()) !== -1; }).length > 0) {
frames_1.splice(i, 1);
i--;
}
else {
simplifiedFrames.push(frame);
}
out_i_1 = i;
};
var out_i_1;
for (var i = 0; i < frames_1.length; i++) {
_loop_2(i);
i = out_i_1;
}
return simplifiedFrames.join('\n');
}
return err.stack;
}
});
})(typeof window === 'object' && window || typeof self === 'object' && self || global));

})));
7 changes: 6 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ gulp.task('build/sync-test.js', ['compile-esm'], function(cb) {
return generateScript('./lib/zone-spec/sync-test.ts', 'sync-test.js', false, cb);
});

gulp.task('build/zone-helper.js', ['compile-esm'], function(cb) {
return generateScript('./lib/helper/zone-helper.ts', 'zone-helper.js', false, cb);
});

gulp.task('build', [
'build/zone.js',
'build/zone.js.d.ts',
Expand All @@ -181,7 +185,8 @@ gulp.task('build', [
'build/wtf.min.js',
'build/async-test.js',
'build/fake-async-test.js',
'build/sync-test.js'
'build/sync-test.js',
'build/zone-helper.js'
]);

gulp.task('test/node', ['compile'], function(cb) {
Expand Down
1 change: 1 addition & 0 deletions karma-dist.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ module.exports = function (config) {
config.files.push('dist/sync-test.js');
config.files.push('dist/task-tracking.js');
config.files.push('dist/wtf.js');
config.files.push('dist/zone-helper.js');
config.files.push('build/test/main.js');
};
147 changes: 147 additions & 0 deletions lib/helper/zone-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
((function(_global: any) {
const ErrorType = _global['Error'];
const blackListedStackFrames = {};
const zoneAwareFunctionNames = [
'Zone', 'drainMicrotask', 'getStacktraceWithUncaughtError', 'new LongStackTrace',
'Object.onScheduleTask'
];

function handleError(error: Error) {
let frames = error.stack ? error.stack.split(/\n/) : [];
while (frames.length) {
let frame = frames.shift();
// On safari it is possible to have stack frame with no line number.
// This check makes sure that we don't filter frames on name only (must have
// linenumber)
if (/:\d+:\d+/.test(frame)) {
const f = frame.split(' [')[0];
if (zoneAwareFunctionNames.filter(zf => f.toLowerCase().indexOf(f.toLowerCase()) !== -1)
.length > 0) {
blackListedStackFrames[f] = f;
}
}
}
}

const detectZone = Zone.root.fork(Zone['longStackTraceZoneSpec']).fork({
name: 'detectZone',
onHandleError(parentDelegate, currentZone, targetZone, error) {
parentDelegate.handleError(targetZone, error);
handleError(error);
return false;
}
});

const detectZoneWithCallbacks = Zone.root.fork(Zone['longStackTraceZoneSpec']).fork({
name: 'detectZone',
onFork: (parentDelegate, currentZone, targetZone, zoneSpec) => {
return parentDelegate.fork(targetZone, zoneSpec);
},
onIntercept: (parentDelegate, currentZone, targetZone, delegate, source) => {
return parentDelegate.intercept(targetZone, delegate, source);
},
onInvoke:
(parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) => {
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
},
onScheduleTask: (parentZoneDelegate, currentZone, targetZone, task) => {
return parentZoneDelegate.scheduleTask(targetZone, task);
},
onInvokeTask: (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) => {
return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs);
},
onCancelTask: (parentZoneDelegate, currentZone, targetZone, task) => {
return parentZoneDelegate.cancelTask(targetZone, task);
},

onHasTask: (delegate, current, target, hasTaskState) => {
return delegate.hasTask(target, hasTaskState);
},

onHandleError(parentDelegate, currentZone, targetZone, error) {
parentDelegate.handleError(targetZone, error);
handleError(error);
return false;
}
});

let detectFn = () => {
throw new Error('zoneAwareFrames');
};

let detectPromiseFn = () => {
const p = new Promise((resolve, reject) => {
reject(new Error('zoneAwareFrames'));
});
};

let detectPromiseCaughtFn = () => {
const p = new Promise((resolve, reject) => {
reject(new Error('zoneAwareFrames'));
});
p.catch(err => {
throw err;
});
};

// Cause the error to extract the stack frames.
detectZone.runTask(detectZone.scheduleEventTask('detect', detectFn, null, () => null, null));
detectZoneWithCallbacks.runTask(
detectZoneWithCallbacks.scheduleEventTask('detect', detectFn, null, () => null, null));
detectZone.runTask(detectZone.scheduleMacroTask('detect', detectFn, null, () => null, null));
detectZoneWithCallbacks.runTask(
detectZoneWithCallbacks.scheduleMacroTask('detect', detectFn, null, () => null, null));
detectZone.runTask(detectZone.scheduleMicroTask('detect', detectFn, null, () => null));
detectZoneWithCallbacks.runTask(
detectZoneWithCallbacks.scheduleMicroTask('detect', detectFn, null, () => null));

detectZone.runGuarded(() => {
detectZone.run(detectFn);
});
detectZoneWithCallbacks.runGuarded(() => {
detectZone.run(detectFn);
});

detectZone.runGuarded(detectPromiseFn);
detectZoneWithCallbacks.runGuarded(detectPromiseFn);

detectZone.runGuarded(detectPromiseCaughtFn);
detectZoneWithCallbacks.runGuarded(detectPromiseCaughtFn);

const otherZoneAwareFunctionNames = ['ZoneTask.invoke', 'ZoneAware'];

Object.defineProperty(ErrorType, 'getNonZoneAwareStack', {
value: function(err: Error) {
if (err.stack) {
let frames = err.stack.split('\n');
const simplifiedFrames: string[] = [];
for (let i = 0; i < frames.length; i++) {
const frame = frames[i].split(' [')[0];
const frameWithoutZone = frame.split(' [')[0];
if (blackListedStackFrames.hasOwnProperty(frameWithoutZone) &&
blackListedStackFrames[frameWithoutZone]) {
frames.splice(i, 1);
i--;
} else if (
otherZoneAwareFunctionNames
.filter(f => frame.toLowerCase().indexOf(f.toLowerCase()) !== -1)
.length > 0) {
frames.splice(i, 1);
i--;
} else {
simplifiedFrames.push(frame);
}
}
return simplifiedFrames.join('\n');
}
return err.stack;
}
});
})(typeof window === 'object' && window || typeof self === 'object' && self || global));
4 changes: 2 additions & 2 deletions lib/zone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1668,8 +1668,8 @@ const Zone: ZoneType = (function(global: any) {
// This check makes sure that we don't filter frames on name only (must have
// linenumber)
if (/:\d+:\d+/.test(frame)) {
// Get rid of the path so that we don't accidintely find function name in path.
// In chrome the seperator is `(` and `@` in FF and safari
// Get rid of the path so that we don't accidentally find function name in path.
// In chrome the separator is `(` and `@` in FF and safari
// Chrome: at Zone.run (zone.js:100)
// Chrome: at Zone.run (http://localhost:9876/base/build/lib/zone.js:100:24)
// FireFox: Zone.prototype.run@http://localhost:9876/base/build/lib/zone.js:101:24
Expand Down
3 changes: 2 additions & 1 deletion test/browser-zone-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ import '../lib/zone-spec/long-stack-trace';
import '../lib/zone-spec/proxy';
import '../lib/zone-spec/sync-test';
import '../lib/zone-spec/task-tracking';
import '../lib/zone-spec/wtf';
import '../lib/zone-spec/wtf';
import '../lib/helper/zone-helper';
1 change: 1 addition & 0 deletions test/common_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ import './zone-spec/sync-test.spec';
import './zone-spec/fake-async-test.spec';
import './zone-spec/proxy.spec';
import './zone-spec/task-tracking.spec';
import './helper/zone-helper.spec';

Error.stackTraceLimit = Number.POSITIVE_INFINITY;
Loading

0 comments on commit ab32b37

Please sign in to comment.