So you're thinking of tracking your JS errors
blog.meldium.com
blog.meldium.com
On the spec side, we have access to the the column number and… <drumroll>… the stack trace! This took a long time, but it's in now. https://mikewest.org/2013/08/debugging-runtime-errors-with-w...
Chrome already has support and.. if past release schedules are maintained, all users of Chrome should have this feature within the next week. Firefox already has support for the CORS behavior and is working on adding the stack to onerror.
If you want cross-browser stack traces: https://github.com/occ/TraceKit. It's used reliably by many large projects. If you want to track these in a reasonable dashboard: https://getsentry.com/ Sentry even supports sourcemaps, so you can map an error of "e.a is not a function" back to the original unminified JavaScript. Also, open source.
We're turning on CORS for the Google JS Library CDN, too.
"We're turning on CORS for the Google JS Library CDN, too." -- Awesome!
Things are looking up!
Once all vendors get onboard with reasonable native exception tracebacks life is going to be so much better.
Still a long ways to go, but great to see people making progress.
https://bugzilla.mozilla.org/show_bug.cgi?id=762556
Not much discussion about fixing it :( This caused a lot of pain at my last job. When we got Chrome and IE10 stack traces, we had a script run them through our source maps and show us the exact, full stack trace through the unminified source. Firefox's and Safari's traces remained impenetrable, short of manually inspecting the minified line of code and guessing.
"In order to get stack traces, you need to wrap your code in a try/catch block like above. Otherwise the error hits window.onerror handler and will only contain the error message, line number, and column number."
TraceKit doesn't do this for you; you still need to wrap in try/catch, so you might as well just do that and have the error/stack trace there.
Go to https://github.com/getsentry/raven-js/blob/master/dist/1.0.8... and you will see code that wraps $.event.add(), $.fn.ready(), $.ajax(), window.setTimeout() and window.setInterval() (search _helper).
If you're using AWS for your CDN, you may be in trouble currently. There's a combination of issues between CloudFront and S3 where this actually becomes a big deal.
While S3 does support CORS, it will only return the "Access-Control-Allow-Origin" header when the Origin header is passed (which only happens on CORS requests themselves). And even if you set an S3 CORS policy of "Access-Control-Allow-Origin: * " it will return a more specific header like "Access-Control-Allow-Origin: example.com", based on the request's "Origin" header. (Because of this behavior, S3 does correctly send a "Vary: Origin" header).
Now, CloudFront will cache the "Access-Control-Allow-Origin" response header, but it does so incorrectly because it doesn't respect the "Vary: Origin" header from S3.
So, that means that even if you think CloudFront is returning the right "Access-Control-Allow-Origin" header, it's only doing so from some geographic locations and its based on some randomness of whether or not the 1st cache hit per node happened to have the desired "Origin" header.
I filed a ticket with AWS a few months ago about CloudFront supporting "Vary: Origin", but they haven't, yet.
Therefore, to my knowledge, it's impossible today to get decent js error reporting if your JavaScript is served from CloudFront with an S3 origin.
"Theoretically, we could wrap every function call in a try/catch and we'd
have an error object in the catch block. This is bad; please don't try.
You'll have a particularly hard time with asynchronous callbacks anyways."
Couldn't disagree more with this point. Wrap the function, not the call. Something like this: function logExceptions( fn ) {
return function() {
try {
return fn.apply( this, arguments );
} catch( e ) {
logException( e );
}
};
}
myFunction = logExceptions( function() {
// do stuff
} );
Now you just call myFunction() normally. Pretty painless.Further, you only have to use a logger-wrapped function for your script's entry points, not "every function": the initialization method (if any), event handlers, setTimeout callbacks, etc. Since you're probably already calling non-native methods which wrap those APIs, that method can handle exception log wrapping for you, too.
For example, for event handling I call an internal on(obj, "name", function(){}) method, and on() passes the callback through logExceptions() for me.
This came from a fairly large library which runs on all manner of third-party sites, many billions of times each month. In that situation trapping and logging all exceptions is very important, and this method has worked reliably for years.
Patch setTimeout and setInterval. Register a jQuery ajax prefilter that wraps all your ajax callbacks. Patch jQuery's low-level UI event binding function.
And you're done. I've done this in a rather large application. It works fine.
It's times like these when you realize just how flexible JavaScript development can be.
The article doesn't mention my biggest problems:
* about half my reported exceptions are due to injected third party code. We use https, but browser extensions and plugins run JavaScript within your page. Some of the time it is obvious the exception is in third party code because of the script name or exception text, but even then it causes a lot of noise.
* With a single page app, we are extremely keen to monitor and diagnose communication problems. However often error logging will fail if the connection has failed. We use getsentry so that at least we are logging to a different domain, but ideally we need to store errors offline and forward them once getting online again.
Mind elaborating on the jQuery point for others? Link?
We haven't hit the other two concerns just yet but they definitely make sense. I feel that getsentry/ravenjs type products will have to offer something to filter out the noise and for offline log buffering. Both fixable, but probably not by us.
Search for _helper('setTimeout') and _helper('setInterval') to see how it injects exception capture for those functions.
The above cover the vast majority of code entry points if using jQuery.
We also flag when we've received an error and silence reporting future errors until the page reloads, to avoid being overwhelmed.
I wish the tooling around this was better. Logging errors into an offline data store sounds sensible, and maybe something we should look at.
It's especially fun when it's a totally useless error, like Invalid object, line 1, no file. yeah, thanks IE.
So what I've done in my window.onerror handler is enumerate all the script tags and look for ones that have a origin that's not my server. Then I send that back along with the trace of the error and all my other debugging data.
If I see a pile of scripts from google cdn and superfish and whatever, then I know that it's not my problem and if they call and bother support, support can tell them to clean up their windows install and then we'll talk.
idle speculation -- I wonder if there's a way to bugger the dom so that any script tag that's injected gets diverted to my code...
Their major selling points for us were GitHub integration and filtering by user-agent/other strings (baiduspider tormented us for a while), plus their packages for logging server-side errors (we use them for Node.js). Their price point is also very generous for startups :)
Combined with sourcemaps, you can get perfect deobfuscated stack traces with little overhead.
As with most past frontend JS issues, browser fragmentation is the cause. Luckily for JS, standards move much quicker these days. With the plethora of mobile and server logging SaaS companies popping up, the browser won't get left far behind.
However, as I've found with mobile, implementing even a half-working service with currently available technology is far better than debugging otherwise.
[1] https://github.com/kriskowal/q#long-stack-traces [2] https://github.com/kriskowal/q#handling-errors
We also have browser specific code to prettify the stack traces. We push the errors to our server and from there to Sentry and that works like a charm! In fact it's so good that we've used it to report issues to third party script providers such as Clicky analytics about errors in their scripts.
With GWT you get complete and accurate stacktraces straight into the Java codebase. Catch it, send it to the backend for logging. Most client-side JavaScript errors are noticed and fixed as soon as the first user has encountered them.
This even works with heavily obfuscated/minified JavaScript, thanks to the symbol maps output by the GWT compilation, which can be used to deobfuscate any stack trace.
Here is an example from our HTML5 game[1], 100,000+ lines of code built entirely in GWT:
Client sees this:
com.google.gwt.core.client.JavaScriptException:
stack: TypeError
at pHc (AAC650305790C67B7708D7A391EFF482.cache.html:4929:343)
at Object.wIc [as Nc] (AAC650305790C67B7708D7A391EFF482.cache.html:5065:16814)
at Object.Af [as Mc] (AAC650305790C67B7708D7A391EFF482.cache.html:5038:5333)
atAAC650305790C67B7708D7A391EFF482.cache.html:3558:58
at Hh (AAC650305790C67B7708D7A391EFF482.cache.html:2921:29)
at Kh (AAC650305790C67B7708D7A391EFF482.cache.html:4588:59)
at AAC650305790C67B7708D7A391EFF482.cache.html:3913:45
type: type_error
arguments: f,
at Unknown.pHc(Unknown Source)
at Unknown.wIc(Unknown Source)
at Unknown.Af(Unknown Source)
at Unknown.anonymous(Unknown Source)
at Unknown.Hh(Unknown Source)
at Unknown.Kh(Unknown Source)
at Unknown.anonymous(Unknown Source)
This is sent to the server, deobfuscated and logged like this: 2013-01-10 21:07:04 ERROR ExceptionLogCommandCommand...ommand.execute(ExceptionLogCommandCommand.java:44): Deobfuscated client-side exception:
java.lang.IllegalStateException: Player not in line-of-sight for shot (viewport=[[887, 638, width/height=800/560]], player x/y=[1413, 876])
at java.lang.IllegalStateException.IllegalStateException(IllegalStateException.java)
at webworks.engine.client.player.Enemy.shootGetShotPosition(Enemy.java)
at webworks.engine.client.player.AbstractPlayer$10.perform(AbstractPlayer.java)
at webworks.engine.client.player.AbstractPlayer.$animate(AbstractPlayer.java)
at webworks.engine.client.WebworksEngine$5.perform(WebworksEngine.java)
at webworks.engine.client.WebworksEngine.$render(WebworksEngine.java)
at webworks.engine.client.WebworksEngine$25.doRun(WebworksEngine.java)
mozilla/5.0 (windows nt 6.0) applewebkit/537.11 (khtml, like gecko) chrome/23.0.1271.97 safari/537.11
With this log entry, most of the time the error can be found simply by looking at the code for a few seconds.What I just said in the previous sentence is (NaN === NaN).
We just have to continue to improve the tooling.
It's a quite strange way of writing a sarcastic comment, but it does agree with you.
I assume by that you mean as per an IEEE specification.