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

Commit 4df13d3

Browse files
committed
feat(error): add a helper method to get non zone aware stack trace
1 parent 14c7a6f commit 4df13d3

File tree

5 files changed

+253
-13
lines changed

5 files changed

+253
-13
lines changed

Diff for: karma-dist.conf.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ module.exports = function (config) {
1717
config.files.push('dist/sync-test.js');
1818
config.files.push('dist/task-tracking.js');
1919
config.files.push('dist/wtf.js');
20+
config.files.push('dist/zone-helper.js');
2021
config.files.push('build/test/main.js');
2122
};

Diff for: lib/zone-spec/long-stack-trace.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ function getStacktraceWithCaughtError(): Error {
3535
// Some implementations of exception handling don't create a stack trace if the exception
3636
// isn't thrown, however it's faster not to actually throw the exception.
3737
const error = getStacktraceWithUncaughtError();
38-
const coughtError = getStacktraceWithCaughtError();
38+
const caughtError = getStacktraceWithCaughtError();
3939
const getStacktrace = error.stack ?
4040
getStacktraceWithUncaughtError :
41-
(coughtError.stack ? getStacktraceWithCaughtError : getStacktraceWithUncaughtError);
41+
(caughtError.stack ? getStacktraceWithCaughtError : getStacktraceWithUncaughtError);
4242

4343
function getFrames(error: Error): string[] {
4444
return error.stack ? error.stack.split(NEWLINE) : [];

Diff for: lib/zone.ts

+151-2
Original file line numberDiff line numberDiff line change
@@ -1814,8 +1814,8 @@ const Zone: ZoneType = (function(global: any) {
18141814
// This check makes sure that we don't filter frames on name only (must have
18151815
// linenumber)
18161816
if (/:\d+:\d+/.test(frame)) {
1817-
// Get rid of the path so that we don't accidintely find function name in path.
1818-
// In chrome the seperator is `(` and `@` in FF and safari
1817+
// Get rid of the path so that we don't accidentally find function name in path.
1818+
// In chrome the separator is `(` and `@` in FF and safari
18191819
// Chrome: at Zone.run (zone.js:100)
18201820
// Chrome: at Zone.run (http://localhost:9876/base/build/lib/zone.js:100:24)
18211821
// FireFox: Zone.prototype.run@http://localhost:9876/base/build/lib/zone.js:101:24
@@ -1858,5 +1858,154 @@ const Zone: ZoneType = (function(global: any) {
18581858
// Cause the error to extract the stack frames.
18591859
detectZone.runTask(detectZone.scheduleMacroTask('detect', detectRunFn, null, () => null, null));
18601860

1861+
// detect zone aware frames for output simple stack
1862+
const zoneAwareStackFrames = {};
1863+
1864+
function handleDetectError(error: Error) {
1865+
let frames = error.stack ? error.stack.split(/\n/) : [];
1866+
while (frames.length) {
1867+
let frame = frames.shift();
1868+
// On safari it is possible to have stack frame with no line number.
1869+
// This check makes sure that we don't filter frames on name only (must have
1870+
// linenumber)
1871+
if (/:\d+:\d+/.test(frame)) {
1872+
const f = frame.split(' [')[0];
1873+
zoneAwareStackFrames[f] = f;
1874+
}
1875+
}
1876+
}
1877+
1878+
const detectEmptyZone = Zone.root.fork({
1879+
name: 'detectEmptyZone',
1880+
onHandleError(parentDelegate, currentZone, targetZone, error) {
1881+
parentDelegate.handleError(targetZone, error);
1882+
handleDetectError(error);
1883+
return false;
1884+
}
1885+
});
1886+
1887+
const detectZoneWithCallbacks = Zone.root.fork({
1888+
name: 'detectCallbackZone',
1889+
onFork: (parentDelegate, currentZone, targetZone, zoneSpec) => {
1890+
handleDetectError(Error('onFork'));
1891+
return parentDelegate.fork(targetZone, zoneSpec);
1892+
},
1893+
onIntercept: (parentDelegate, currentZone, targetZone, delegate, source) => {
1894+
handleDetectError(Error('onIntercept'));
1895+
return parentDelegate.intercept(targetZone, delegate, source);
1896+
},
1897+
onInvoke:
1898+
(parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source) => {
1899+
handleDetectError(Error('onInvoke'));
1900+
return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
1901+
},
1902+
onScheduleTask: (parentZoneDelegate, currentZone, targetZone, task) => {
1903+
handleDetectError(Error('onScheduleTask'));
1904+
return parentZoneDelegate.scheduleTask(targetZone, task);
1905+
},
1906+
onInvokeTask: (parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs) => {
1907+
handleDetectError(Error('onInvokeTask'));
1908+
return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs);
1909+
},
1910+
onCancelTask: (parentZoneDelegate, currentZone, targetZone, task) => {
1911+
handleDetectError(Error('onCancelTask'));
1912+
return parentZoneDelegate.cancelTask(targetZone, task);
1913+
},
1914+
1915+
onHasTask: (delegate, current, target, hasTaskState) => {
1916+
handleDetectError(Error('onHasTask'));
1917+
return delegate.hasTask(target, hasTaskState);
1918+
},
1919+
1920+
onHandleError(parentDelegate, currentZone, targetZone, error) {
1921+
parentDelegate.handleError(targetZone, error);
1922+
handleDetectError(error);
1923+
return false;
1924+
}
1925+
});
1926+
1927+
let detectFn = () => {
1928+
throw new Error('zoneAwareFrames');
1929+
};
1930+
1931+
let detectPromiseFn = () => {
1932+
new Promise((resolve, reject) => {
1933+
reject(new Error('zoneAwareFrames'));
1934+
});
1935+
};
1936+
1937+
let detectPromiseCaughtFn = () => {
1938+
const p = new Promise((resolve, reject) => {
1939+
reject(new Error('zoneAwareFrames'));
1940+
});
1941+
p.catch(err => {
1942+
throw err;
1943+
});
1944+
};
1945+
1946+
// Cause the error to extract the stack frames.
1947+
detectEmptyZone.runTask(
1948+
detectEmptyZone.scheduleEventTask('detect', detectFn, null, () => null, null));
1949+
detectZoneWithCallbacks.runTask(
1950+
detectZoneWithCallbacks.scheduleEventTask('detect', detectFn, null, () => null, null));
1951+
detectEmptyZone.runTask(
1952+
detectEmptyZone.scheduleMacroTask('detect', detectFn, null, () => null, null));
1953+
detectZoneWithCallbacks.runTask(
1954+
detectZoneWithCallbacks.scheduleMacroTask('detect', detectFn, null, () => null, null));
1955+
detectEmptyZone.runTask(detectEmptyZone.scheduleMicroTask('detect', detectFn, null, () => null));
1956+
detectZoneWithCallbacks.runTask(
1957+
detectZoneWithCallbacks.scheduleMicroTask('detect', detectFn, null, () => null));
1958+
1959+
detectEmptyZone.runGuarded(() => {
1960+
detectEmptyZone.run(detectFn);
1961+
});
1962+
detectZoneWithCallbacks.runGuarded(() => {
1963+
detectEmptyZone.run(detectFn);
1964+
});
1965+
1966+
detectEmptyZone.runGuarded(detectPromiseFn);
1967+
detectZoneWithCallbacks.runGuarded(detectPromiseFn);
1968+
1969+
detectEmptyZone.runGuarded(detectPromiseCaughtFn);
1970+
detectZoneWithCallbacks.runGuarded(detectPromiseCaughtFn);
1971+
1972+
// some functions are not easily to be detected here,
1973+
// for example Timeout.ZoneTask.invoke, if we want to detect those functions
1974+
// by detect zone, we have to run all patched APIs, it is too risky
1975+
// so for those functions, just check whether the stack contains the string or not.
1976+
const otherZoneAwareFunctionNames = [
1977+
'ZoneTask.invoke', 'ZoneAware', 'getStacktraceWithUncaughtError', 'new LongStackTrace',
1978+
'long-stack-trace'
1979+
];
1980+
1981+
Object.defineProperty(ZoneAwareError, 'getNonZoneAwareStack', {
1982+
value: function(err: Error) {
1983+
if (err.stack) {
1984+
let frames = err.stack.split('\n');
1985+
const simplifiedFrames: string[] = [];
1986+
for (let i = 0; i < frames.length; i++) {
1987+
const frame = frames[i].split(' [')[0];
1988+
const frameWithoutZone = frame.split(' [')[0];
1989+
if (zoneAwareStackFrames.hasOwnProperty(frameWithoutZone) &&
1990+
zoneAwareStackFrames[frameWithoutZone]) {
1991+
frames.splice(i, 1);
1992+
i--;
1993+
} else if (
1994+
otherZoneAwareFunctionNames
1995+
.filter(f => frame.toLowerCase().indexOf(f.toLowerCase()) !== -1)
1996+
.length > 0) {
1997+
frames.splice(i, 1);
1998+
i--;
1999+
} else {
2000+
// we still need the zone information on each stack frame
2001+
simplifiedFrames.push(frames[i]);
2002+
}
2003+
}
2004+
return simplifiedFrames.join('\n');
2005+
}
2006+
return err.stack;
2007+
}
2008+
});
2009+
18612010
return global['Zone'] = Zone;
18622011
})(typeof window === 'object' && window || typeof self === 'object' && self || global);

Diff for: test/common/Error.spec.ts

+97-7
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ describe('ZoneAwareError', () => {
158158
});
159159

160160
it('should show zone names in stack frames and remove extra frames', () => {
161-
const rootZone = getRootZone();
161+
const rootZone = Zone.root;
162162
const innerZone = rootZone.fork({name: 'InnerZone'});
163163

164164
rootZone.run(testFn);
@@ -206,10 +206,100 @@ describe('ZoneAwareError', () => {
206206
});
207207
});
208208

209-
function getRootZone() {
210-
let zone = Zone.current;
211-
while (zone.parent) {
212-
zone = zone.parent;
209+
const getNonZoneAwareStack: (err: Error) => string = Error['getNonZoneAwareStack'];
210+
const zoneAwareFrames = [
211+
'Zone.run', 'Zone.runGuarded', 'Zone.scheduleEventTask', 'Zone.scheduleMicroTask',
212+
'Zone.scheduleMacroTask', 'Zone.runTask', 'ZoneDelegate.scheduleTask', 'ZoneDelegate.invokeTask',
213+
'ZoneTask.invoke', 'zoneAwareAddListener', 'drainMicroTaskQueue', 'LongStackTrace',
214+
'getStacktraceWithUncaughtError'
215+
];
216+
217+
function assertStackDoesNotContainZoneFrames(err: Error) {
218+
const simpleStack = getNonZoneAwareStack(err);
219+
if (!simpleStack) {
220+
return;
221+
}
222+
const frames = simpleStack.split('\n');
223+
for (let i = 0; i < frames.length; i++) {
224+
expect(zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1)).toEqual([]);
225+
}
226+
};
227+
228+
const errorZoneSpec = {
229+
name: 'errorZone',
230+
done: null,
231+
onHandleError: (parentDelegate, currentZone, targetZone, error) => {
232+
assertStackDoesNotContainZoneFrames(error);
233+
setTimeout(() => {
234+
errorZoneSpec.done && errorZoneSpec.done();
235+
}, 0);
236+
return false;
213237
}
214-
return zone;
215-
}
238+
};
239+
240+
const errorZone = Zone.root.fork(errorZoneSpec);
241+
242+
const assertStackDoesNotContainZoneFramesTest = function(testFn: Function) {
243+
return function(done) {
244+
errorZoneSpec.done = done;
245+
errorZone.run(testFn);
246+
};
247+
};
248+
249+
describe('Error getSimpleStack', () => {
250+
it('error which occurs in setTimeout callback should not have zone frames visible',
251+
assertStackDoesNotContainZoneFramesTest(() => {
252+
setTimeout(() => {
253+
throw new Error('test error');
254+
}, 10);
255+
}));
256+
257+
it('error which cause by promise rejection should not have zone frames visible', (done) => {
258+
const p = new Promise((resolve, reject) => {
259+
reject(new Error('test error'));
260+
});
261+
p.catch(err => {
262+
assertStackDoesNotContainZoneFrames(err);
263+
done();
264+
});
265+
});
266+
267+
it('error which occurs in eventTask callback should not have zone frames visible',
268+
assertStackDoesNotContainZoneFramesTest(() => {
269+
const task = Zone.current.scheduleEventTask('errorEvent', () => {
270+
throw new Error('test error');
271+
}, null, () => null, null);
272+
task.invoke();
273+
}));
274+
275+
it('error which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible',
276+
assertStackDoesNotContainZoneFramesTest(() => {
277+
const task =
278+
Zone.current.fork(Zone['longStackTraceZoneSpec']).scheduleEventTask('errorEvent', () => {
279+
throw new Error('test error');
280+
}, null, () => null, null);
281+
task.invoke();
282+
}));
283+
284+
it('stack frames of the callback in user customized zoneSpec should be kept',
285+
assertStackDoesNotContainZoneFramesTest(() => {
286+
const task = Zone.current.fork(Zone['longStackTraceZoneSpec'])
287+
.fork({
288+
name: 'customZone',
289+
onScheduleTask: (parentDelegate, currentZone, targetZone, task) => {
290+
return parentDelegate.scheduleTask(targetZone, task);
291+
},
292+
onHandleError: (parentDelegate, currentZone, targetZone, error) => {
293+
parentDelegate.handleError(targetZone, error);
294+
const containsCustomZoneSpecStackTrace =
295+
getNonZoneAwareStack(error).indexOf('onScheduleTask') !== -1;
296+
expect(containsCustomZoneSpecStackTrace).toBeTruthy();
297+
return false;
298+
}
299+
})
300+
.scheduleEventTask('errorEvent', () => {
301+
throw new Error('test error');
302+
}, null, () => null, null);
303+
task.invoke();
304+
}));
305+
});

Diff for: test/node/fs.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('nodejs file system', () => {
6161
done();
6262
});
6363
});
64-
writeFile('testfile', 'test new content');
64+
writeFile('testfile', 'test new content', () => {});
6565
});
6666
});
6767
});
@@ -81,7 +81,7 @@ describe('nodejs file system', () => {
8181
done();
8282
});
8383
});
84-
writeFile('testfile', 'test new content');
84+
writeFile('testfile', 'test new content', () => {});
8585
});
8686
});
8787
});

0 commit comments

Comments
 (0)