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

feat(error): remove zone internal stack frames in error.stack #632

Merged
merged 2 commits into from
Mar 13, 2017

Conversation

JiaLiPassion
Copy link
Collaborator

@JiaLiPassion JiaLiPassion commented Feb 12, 2017

1.zone.js patches most basic APIs, so when we want to debug, we will see a lot of zone related stack traces, for example, in example/basic.html, the simple event listener stack trace will look like this.

Error: aw shucks
    at HTMLButtonElement.throwError (file:///Users/lijia/workspace/zone.js/example/basic.html:43:11) [long-stack-trace]
    at Zone.runTask (file:///Users/lijia/workspace/zone.js/dist/zone.js:162:47) [<root> => long-stack-trace]
    at HTMLButtonElement.ZoneTask.invoke (file:///Users/lijia/workspace/zone.js/dist/zone.js:356:33) [<root>]
  -------------   Elapsed: 2483 ms; At: Sun Feb 12 2017 16:18:19 GMT+0900 (JST)   -------------  
    at getStacktraceWithUncaughtError (file:///Users/lijia/workspace/zone.js/dist/long-stack-trace-zone.js:33:12) [long-stack-trace]
    at new LongStackTrace (file:///Users/lijia/workspace/zone.js/dist/long-stack-trace-zone.js:27:22) [long-stack-trace]
    at Object.onScheduleTask (file:///Users/lijia/workspace/zone.js/dist/long-stack-trace-zone.js:83:18) [long-stack-trace]
    at ZoneDelegate.scheduleTask (file:///Users/lijia/workspace/zone.js/dist/zone.js:263:49) [long-stack-trace]
    at Zone.scheduleEventTask (file:///Users/lijia/workspace/zone.js/dist/zone.js:182:39) [long-stack-trace]
    at zoneAwareAddListener (file:///Users/lijia/workspace/zone.js/dist/zone.js:1318:14) [long-stack-trace]
    at HTMLButtonElement.addEventListener (eval at createNamedFn (file:///Users/lijia/workspace/zone.js/dist/zone.js:1424:17), <anonymous>:3:43) [long-stack-trace]
    at HTMLButtonElement.bindSecondButton (file:///Users/lijia/workspace/zone.js/example/basic.html:38:8) [long-stack-trace]
    at Zone.runTask (file:///Users/lijia/workspace/zone.js/dist/zone.js:162:47) [<root> => long-stack-trace]
    at HTMLButtonElement.ZoneTask.invoke (file:///Users/lijia/workspace/zone.js/dist/zone.js:356:33) [<root>]
  -------------   Elapsed: 3122 ms; At: Sun Feb 12 2017 16:18:16 GMT+0900 (JST)   -------------  
    at getStacktraceWithUncaughtError (file:///Users/lijia/workspace/zone.js/dist/long-stack-trace-zone.js:33:12) [long-stack-trace]
    at new LongStackTrace (file:///Users/lijia/workspace/zone.js/dist/long-stack-trace-zone.js:27:22) [long-stack-trace]
    at Object.onScheduleTask (file:///Users/lijia/workspace/zone.js/dist/long-stack-trace-zone.js:83:18) [long-stack-trace]
    at ZoneDelegate.scheduleTask (file:///Users/lijia/workspace/zone.js/dist/zone.js:263:49) [long-stack-trace]
    at Zone.scheduleEventTask (file:///Users/lijia/workspace/zone.js/dist/zone.js:182:39) [long-stack-trace]
    at zoneAwareAddListener (file:///Users/lijia/workspace/zone.js/dist/zone.js:1318:14) [long-stack-trace]
    at HTMLButtonElement.addEventListener (eval at createNamedFn (file:///Users/lijia/workspace/zone.js/dist/zone.js:1424:17), <anonymous>:3:43) [long-stack-trace]
    at main (file:///Users/lijia/workspace/zone.js/example/basic.html:25:8) [long-stack-trace]
    at Zone.run (file:///Users/lijia/workspace/zone.js/dist/zone.js:124:43) [<root> => long-stack-trace]
    at file:///Users/lijia/workspace/zone.js/example/basic.html:57:39 [<root>]

It is a little difficult to find out which is the application's stack, so in this PR, we reduce the stack frames belong to zone internal.

Error: aw shucks
    at HTMLButtonElement.throwError (file:///Users/lijia/workspace/zone.js/example/basic.html:43:11)
  -------------   Elapsed: 2484 ms; At: Sun Feb 12 2017 16:18:19 GMT+0900 (JST)   -------------  
    at HTMLButtonElement.addEventListener (eval at createNamedFn (file:///Users/lijia/workspace/zone.js/dist/zone.js:1424:17), <anonymous>:3:43)
    at HTMLButtonElement.bindSecondButton (file:///Users/lijia/workspace/zone.js/example/basic.html:38:8)
  -------------   Elapsed: 3122 ms; At: Sun Feb 12 2017 16:18:16 GMT+0900 (JST)   -------------  
    at HTMLButtonElement.addEventListener (eval at createNamedFn (file:///Users/lijia/workspace/zone.js/dist/zone.js:1424:17), <anonymous>:3:43)
    at main (file:///Users/lijia/workspace/zone.js/example/basic.html:25:8)
    at file:///Users/lijia/workspace/zone.js/example/basic.html:57:39

Basically, only user's code will be left here. not all, but most.

to access the original stack, just call

err.orignalStack

Another changes is in IE, error.stack will be undefined when constructed, and only have the stack
when throw.
https://docs.microsoft.com/en-us/scripting/javascript/reference/stack-property-error-javascript

in this PR, the stack will be there when constructed.

  1. fix typo in zone.js

  2. fix test cases for fs.spec.ts to remove warning.

@@ -0,0 +1,147 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this separate file? Why not just fold it to the error handling in Zone.ts which already filters out some stack frames. We could just expand it to filter all?

I don't think most people would use this as it would require set up. I think having this as part of Zone.ts would make it so that everyone would benefit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank for the review. I will put it into zone.ts.

@JiaLiPassion
Copy link
Collaborator Author

@mhevery , I removed the zone-helper, and add the logic to long-stack-trace, because the detect logic need long-stack-trace zone spec. Please review.


it('test getNonZoneAwareStack', errorTest(() => {
setTimeout(() => {
throw new Error('test error');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is being asserted here?

Copy link
Collaborator Author

@JiaLiPassion JiaLiPassion Feb 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I write a help method errorTest which check the error stack, I will change the name of errorTest to assertStackDoesNotContainZoneFramesTest

};
};

it('test getNonZoneAwareStack', errorTest(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it('should do something' Saying it('test getNonZoneAwareStack does not say what is the expected behavior. Always write tests with what should happen, not what method you are testing.

Copy link
Collaborator Author

@JiaLiPassion JiaLiPassion Feb 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always write tests with what should happen, not what method you are testing.

I will remember it, thank you.

}, 10);
}));

it('test promise getNonZoneAwareStack', (done) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

example: it('should ensure that rejected errors should not have zone frames visible',

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right, I should write what to test.

const task = Zone.current.scheduleEventTask('errorEvent', () => {
throw new Error('test error');
}, null, () => null, null);
task.invoke();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no assertion, and the test does not tell me what should happen.

Copy link
Collaborator Author

@JiaLiPassion JiaLiPassion Feb 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I write a help method errorTest which check the error stack, I will change the name of errorTest to assertStackDoesNotContainZoneFramesTest


it('test longStackTrace getNonZoneAwareStack', errorTest(() => {
const task =
Zone.current.fork(Zone['longStackTraceZoneSpec']).scheduleEventTask('errorEvent', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is this test different from previous test?

Copy link
Collaborator Author

@JiaLiPassion JiaLiPassion Feb 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

longStackTraceZoneSpec has several stack frames such as 'new LongStackTrace' should also be trimmed, I will change the test case name.

task.invoke();
}));

it('test custom zoneSpec getNonZoneAwareStack', errorTest(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the expectation of this test?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I write a help method errorTest which check the error stack contains zone aware frames or not, I will change the name of errorTest to assertStackDoesNotContainZoneFramesTest.

'ZoneDelegate.invokeTask', 'ZoneTask.invoke', 'zoneAwareAddListener', 'drainMicroTaskQueue'
];

const expectStack = function(err: Error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better name should be. assertStakDoesNotContainZoneFrames

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I will use this name

}
const frames = simpleStack.split('\n');
for (let i = 0; i < frames.length; i++) {
expect(zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1).length).toBe(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this fails it will say was 5 expected 0 which is not useful.

expect(extraFrames).toEqual([]);

is better since the error would be

was ['someFrame] expected []

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right, I will update it.

return parentDelegate.scheduleTask(targetZone, task);
},
onHandleError: (parentDelegate, currentZone, targetZone, error) => {
parentDelegate.handleError(targetZone, error);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also have additional test, which shows that this works even when calling it using throw Error(...) (no new) as I think additional frames appear than.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will add the test case throw Error(...) cases

@@ -136,6 +136,151 @@ function captureStackTraces(stackTraces: string[][], count: number): void {
}
}

const zoneAwareStackFrames = {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just roll this into the existing stack frame filtering in the zone.ts I don't think that having it in long-stack-trace is the right place. If it is in the zone.ts than long-stack-trace will get it automatically.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sure, I put it into longStackTrace because it can catch the stack frames for onScheduleTask, and I realize I can just create a error in onScheduleTask callback, I will move those code into zone.ts

@JiaLiPassion JiaLiPassion force-pushed the simplestack branch 2 times, most recently from 738db56 to 4df13d3 Compare February 25, 2017 14:10
@JiaLiPassion
Copy link
Collaborator Author

@mhevery , I have refactor the code, please review, thank you.

@mhevery mhevery force-pushed the master branch 12 times, most recently from b55b4ad to f3b8885 Compare March 7, 2017 18:05
@@ -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');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this come from? We don't have zone-helper

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forget to remove it.

@@ -158,7 +158,7 @@ describe('ZoneAwareError', () => {
});

it('should show zone names in stack frames and remove extra frames', () => {
const rootZone = getRootZone();
const rootZone = Zone.root;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a test which says that new Error('foo') and Error(foo`) both get filtered correctly? I have seen some issues in the past there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, will do

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added the test case of Error('foo'), and because Error without new will cause different stack frames, so I also add detect logic in zone.ts.

lib/zone.ts Outdated
'long-stack-trace'
];

Object.defineProperty(ZoneAwareError, 'getNonZoneAwareStack', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why is this called getNonZoneAwareStack? what is returned is ZoneAware.
    Why do we even need this method?

  2. Why is this Object.defineProperty rather than simple function assignment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the method name is confused, it is ZoneAware but without the Zone related stack frames. such as Zone.run, Zone.scheduleXXXTask, ZoneDelegate.invoke...
I will change it to getSimplifiedStack, is that ok?

And use Object.defineProperty here is just to keep the coding style to be consistent with other property such as prepareStackTrace, captureStackTrace.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why do we need the method than? .stack property has the frames removed and .originalStack has the extra frames. Could you explain the motivation behind getNonZoneAwareStack

  2. I think Object.defineProperty is confusing. I would just drop it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The motivation is currently the .stack property still has a lot of information related with Zone,
such as Zone.runTask, ZoneDelegate.invokeTask and so on, ZoneAwareError only remove a part of the stack frames which Zone related. for example

Zone.current.fork({name: 'test'}).run(function(){
    document.getElementById('ok').addEventListener('click', function() {
     console.log(new Error('test').stack);
    });
  });

will get

Error: test
    at HTMLButtonElement.<anonymous> (test4.html:30) [test]
    at Zone.runTask (zone.js:166) [<root> => test]
    at HTMLButtonElement.ZoneTask.invoke (zone.js:418) [<root>]

throw a error in button click will get the .stack

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have remove the getNonZoneAwareStack and move the logic to .stack property.

@JiaLiPassion JiaLiPassion force-pushed the simplestack branch 2 times, most recently from 8b07351 to 0db2680 Compare March 11, 2017 18:29
@JiaLiPassion JiaLiPassion changed the title feat(error): add a helper method to get non zone aware stack trace feat(error): remove zone internal stack frames in error.stack Mar 12, 2017
@mhevery mhevery merged commit 76fa891 into angular:master Mar 13, 2017
@JiaLiPassion JiaLiPassion deleted the simplestack branch March 17, 2017 03:47
mhevery added a commit to mhevery/zone.js that referenced this pull request Mar 21, 2017
…r#632)

This feature was reverted because it was causing a performance regression. 
The setup of all of the stack traces added about 20-25ms to the total 
time of the Zone initialization. This was a bit more than 50% of overall
time zone needed to initialize. While this is a useful feature, the 
performance cost does not justify its cost.
mhevery added a commit to mhevery/zone.js that referenced this pull request Mar 21, 2017
…r#632)

This feature was reverted because it was causing a performance regression. 
The setup of all of the stack traces added about 20-25ms to the total 
time of the Zone initialization. This was a bit more than 50% of overall
time zone needed to initialize. While this is a useful feature, the 
performance cost does not justify its cost.
mhevery added a commit to mhevery/zone.js that referenced this pull request Mar 21, 2017
…r#632)

This feature was reverted because it was causing a performance regression.
The setup of all of the stack traces added about 20-25ms to the total
time of the Zone initialization. This was a bit more than 50% of overall
time zone needed to initialize. While this is a useful feature, the
performance cost does not justify its cost.
mhevery added a commit that referenced this pull request Mar 21, 2017
…690)

This feature was reverted because it was causing a performance regression.
The setup of all of the stack traces added about 20-25ms to the total
time of the Zone initialization. This was a bit more than 50% of overall
time zone needed to initialize. While this is a useful feature, the
performance cost does not justify its cost.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants