I’m working on a new (mostly greenfield) responsive website for eBay Sweden that has a fair amount of JavaScript and that is viewed in lots of different browsers (mobile, tablet, desktop). Naturally we want to log our JavaScript exceptions and their stacktraces, just like we log server-side exceptions. It is impossible to test every combination of device and browser so we rely on logging to find the edge cases we miss in our testing.
The way we handle our JavaScript exceptions is to:
- catch the exception.
- collect data about the useragent, context etc.
- Save it to our logs by sending an ajax request with the data and the exception information.
I can finally log JS Exceptions!
We decided to use window.onerror which is a DOM event handler that acts like a global try..catch. This is great for catching unexpected exceptions i.e. the ones that never occur while testing.
It is very simple to get started with, you just have to override the handler like this:
window.onerror = function (errorMsg, url, lineNumber) { alert('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber); }
But It Was Too Good To Be True
If you test this on a local server (say IIS or nginx) then it should work fine. But it is not the same as a normal try..catch, so producing a stacktrace with a library like stacktrace.js will probably not work too well. The window.onerror handler does not have the same context and the context varies enormously from browser to browser.
Also, if you have minified your files then line number is not very useful. For example:
Error: ‘a’ is undefined Script: build.js Line: 3
Variable ‘a’ is very hard to find when line 3 has 30000 characters of minified JavaScript.
Unfortunately, I do not have a solution for this for all browsers. This will get better over the next few months as a new standard for window.onerror has been agreed upon. It is already implemented for Chrome.
The new standard adds two parameters; column number and an error object. Our window.onerror handler now looks like this:
window.onerror = function (errorMsg, url, lineNumber, column, errorObj) { alert('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber + ' Column: ' + column + ' StackTrace: ' + errorObj); }
I made a little test project to see what level of support there is. It contains one test page with a button and two script files. The button triggers a function in one script file that creates an exception and the other script file contains the window.onerror handler.
As of 2014-01-18 the results were:
- Firefox 26.0 returns the first three parameters (hopefully this will be implemented soon)
- Internet Explorer 10 will return a column number but no error object. However by using arguments.callee.caller you can get a stacktrace.
- Chrome 32.0.1700.76 (for desktop) returns all five parameters
- Chrome for Android (version 32) returns all five parameters
- Safari for iOS (6 and 7) returns the first three parameters (here is the Webkit issue)
So for older mobiles you will never be able to get decent error logging but when all the browsers have implemented this and people switch to newer phones it will get better. Not perfect but this would still be a good start if not for…
Chrome and Firefox Break It Totally For CDNs
It is actually even worse if you use a CDN for your script files. window.onerror won’t work at all in this case. If you have tried this then you have probably visited this StackOverflow page:
Cryptic “Script Error.” reported in Javascript in Chrome and Firefox
Firefox and Chrome will for security reasons not report exceptions from scripts that are of a different origin. All you get is a cryptic “Script Error.” and nothing else. Totally useless.
Solving the Cryptic “Script Error.”
The latest versions of Chrome (see point 3 in the linked post) and Firefox have now provided a way to allow reporting of exceptions from CDNs.
There are two steps to be taken to get it working:
- Set the Access-Control-Allow-Origin header via the CDN webserver serving the external JavaScript. This allows sharing of a cross domain resource (CORS).
Access-Control-Allow-Origin:*
- Use the new crossorigin attribute on the source tag. Here is an example from our website at work:
<script crossorigin="anonymous" src="//static.tradera.com/touchweb/static/output/script/344c8698.build.js"></script>
The crossorigin tag has two possible values.
- anonymous means no user credentials are needed to access the file.
- use-credentials if you are using the Access-Control-Allow-Credentials header when serving the external JavaScript file from the CDN.
Now it should work. There is one caveat here. If you have set the crossorigin attribute on the script tag then you MUST set the Access-Control-Allow-Origin header otherwise the browser will discard the script for not having CORS enabled.
At the moment, we filter out the exceptions for iOS Safari and older Androids by checking if the error message is equal to “Script error.”.
window.onerror = function (errorMsg, url, lineNumber, column, errorObj) { if (errorMsg.indexOf('Script error.') > -1) { return; }
Getting Closer
The state of play in January of 2014 is that you can catch unexpected JavaScript exceptions for most desktop browsers and for Android mobiles with a recent version of Chrome for Android as well as Windows Phones. Hope this helps out other developers trying to track JavaScript errors on public sites where you can’t ask the user for help recreating errors. Roll on 2015 and better JavaScript error handling!
Thanks for sharing the new implementation of window.onerror on Chrome.
And I also have a further question for catching error in production. Cause almost js will be minify/uglify in production, then the line number and stack on error will non-sense to Dev to analyse the bug. Any suggestion to get the full line number and stack?
Thanks!
Sorry for the spam, I read your post again. This problem you wrote at the begin of the post. I’m very appreciate your good sharing. Thanks again.
You can use the sourcemap generated during minification process to find the actual line and column number of the unminified file where the error happened.
Thanks for the explanation!
I was able to solve some complex javascript issue which broke my head for 3 days using this approach, of catching the javascript errors. Thanks.
You’re welcome! It has already gotten a little bit better since I wrote this post. Android 2.2 and 2.3 are nearly gone and the Chrome and FireFox errors are getting better. I’m looking forward to this change in Chrome: https://plus.google.com/+AddyOsmani/posts/DdWkiKsvbA2
Hi Daniel, Thanks!! for sharing.
Exits any github related?
I’m working on a issueTracker as part of my navigation api to tracking issues in UI with context.
This is my approach:
– Intercept Ajax execution, if exists any exception save the log with the navigation graph and reset the flow.
– Listen window.onerror event to tracking issue with or without exception in back and save the navigation graph and reset the issue-flow.
– Create a resolve method when both events were fired.
– Create a admin to list the issues, make reports, notification, etc ( IssueTracker-admin )
– In the case of an UI-issue associated to backend exception, i can also use the transactionID to show the graph trace from the backend flow.
Any feedback ? :) thanks!
Thanks for the great post! It seems like these days the issue is only concern Safari iOS, did you find a way to over come it?
Note: You still get “Script error” in Chrome on cross-domain scripts when the error in generated inside of eval().
This is an issue you use `devtool: “eval”` in webpack for your source maps.
Thanks a lot! The crossorigin tip was really useful