
So you're thinking of tracking your JS errors - william_hc
http://blog.meldium.com/home/2013/9/30/so-youre-thinking-of-tracking-your-js-errors
======
paulirish
Logging JS errors is _quickly_ becoming easier than you think. :)

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...](https://mikewest.org/2013/08/debugging-runtime-errors-with-window-
onerror)

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](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/](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.

~~~
NKCSS
Suggesting TraceKit is pretty much:

"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.

~~~
robocat
If using jQuery, it is a few lines of code to add try/catch around the
majority of your code entry points.

Go to [https://github.com/getsentry/raven-
js/blob/master/dist/1.0.8...](https://github.com/getsentry/raven-
js/blob/master/dist/1.0.8/raven.js) and you will see code that wraps
$.event.add(), $.fn.ready(), $.ajax(), window.setTimeout() and
window.setInterval() (search _helper).

------
philfreo
The CORS thing is a big deal. Basically it means your JS error reporting is
useless (window.onerror won't report any details) if your JavaScript is loaded
from a CDN, unless your CDN has CORS enabled.

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.

~~~
nixme
So somehow I've never noticed this as a problem. We get full stack traces for
everything. On the other hand we use the hosted version of Sentry
([https://getsentry.com](https://getsentry.com)), but since everything is open
source, we bundle their raven library ([https://github.com/getsentry/raven-
js](https://github.com/getsentry/raven-js)) into our CDN served package. So
window.onerror is set from the same JS as the rest of the app. I'm guessing
that avoids the cross-domain issue?

~~~
philfreo
My understanding is that if your website and error-producing-JS file are
different domains then you'll have an issue still. I think raven-js does some
other stuff regarding letting you rethrow Exceptions, which would still work,
but window.onerror is probably not reporting much in most browsers. Could that
be the case?

------
unfletch

      "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.

~~~
william_hc
I didn't say this was impossible, just probably a bad idea. If your
application is simple enough for this to work though, go for it.

~~~
ef4
But there's no need to wrap _every_ function, just the ones that can be top-
level entry points. It's a very small list, if your application is well-
designed.

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.

~~~
william_hc
Fair enough. I guess that's the pure js/jquery version of hooking into
Angular/Ember's onerror. Makes sense. At least we won't need to do this for
much longer though.

------
robocat
Getting the stack trace is usually painless if using jQuery. The large
majority of code runs inside an event handler registered using jQuery, so
there is a single place to put a try/catch within jQuery and ravenjs/getsentry
do that. It also overrides window.setTimeout for the same reason.

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.

~~~
william_hc
All good points.

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.

~~~
robocat
Go to [https://github.com/getsentry/raven-
js/blob/master/dist/1.0.8...](https://github.com/getsentry/raven-
js/blob/master/dist/1.0.8/raven.js) and search for jQuery. You will see code
that wraps $.event.add(), $.fn.ready(), and $.ajax()

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.

------
fenguin
In addition to the excellent services mentioned in the article, I'd like to
recommend Rollbar ([http://rollbar.com/](http://rollbar.com/)) -- we've been
using them for almost a year and have come to rely on them exclusively.

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 :)

------
dudus
And I'm just sitting here, catching errors and sending them to Google
Analytics as Events.

------
cromwellian
A recent version of Chrome Blink enhanced windonw.onerror to provide a real
Error object with column numbers as well, so you no longer need to wrap all
entries into JS with a uncaughtexception handler, which is what Closure, GWT,
Dart, Angular, et al do.

Combined with sourcemaps, you can get perfect deobfuscated stack traces with
little overhead.

------
cheeaun
I've compiled a list of JS error logging services here:
[https://gist.github.com/cheeaun/5835757](https://gist.github.com/cheeaun/5835757)
. I've also tried tagging which one of them uses tracekit and supports source
maps, etc.

------
doublerebel
V8 makes JS error logging very simple, as we see in Node.js. I've also
implemented remote JS error logging for Titanium Mobile on Android (also V8)
using TestFlight. JavaScriptCore has similar error handling, but only in the
past year [1].

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://bugs.webkit.org/show_bug.cgi?id=40118](https://bugs.webkit.org/show_bug.cgi?id=40118)

------
munro
If you're using Q, you get long stack traces [1] for debugging asynchronous
behavior! That way you can follow the entire path of an exception, through
multiple callbacks. Q also wraps every function with a try/catch statement,
and will push caught exceptions into the error pipeline [2].

[1] [https://github.com/kriskowal/q#long-stack-
traces](https://github.com/kriskowal/q#long-stack-traces) [2]
[https://github.com/kriskowal/q#handling-
errors](https://github.com/kriskowal/q#handling-errors)

------
olegp
If you're using Angular, it is possible to patch the error handler service and
augment it with additional info like the expression that was the cause, scope
erc. That's what we've done at [https://starthq.com](https://starthq.com)

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.

------
anonymous
Is it just me, or does anyone else think the way CORS works right now is
completely bass-ackwards? We have a page served by Host A that wants to access
resources on Host B. Why is it that Host B has to allow this and not Host A?
If I compromise a script on Host A, of course I'll also add the needed headers
in the reply by Host B! Isn't it a lot more sensible that Host A should
include a header that says "you are allowed to also download resources from
Host B"?

~~~
Joeri
Actually, it only makes sense as it works right now. When you are a developer
on host A and decide to include a script, then you have already figured out
that script is safe to include, so no bookkeeping is necessary. If a hacker
has gained access to host A they can run any code they want, so there is no
reason to prevent inclusion of scripts from host B. However, the developer of
host B might have designed their resource so that it provides privileged user
data based on a cookie. If it can be loaded into the page of host A it would
give the developer (or hacker) of host A access to the privileged info of all
users that frequent host B. This is why the developer of host B must
explicitly publish a resource for inclusion on foreign pages.

------
johnyzee
Another good reason for compiling to JavaScript from a higher level platform.

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.

[1] [http://www.webworks.dk/enginetest](http://www.webworks.dk/enginetest)

------
ecaron
This is one reason that I've been very excited/anxious for
[http://trackjs.com/](http://trackjs.com/) to launch.

------
VeejayRampay
Javascript doesn't really have good tooling but at least if you don't like it,
there are dozens of alternatives, just like in any other software space.

What I just said in the previous sentence is (NaN === NaN).

~~~
fullsailor
What alternatives to JavaScript are there for browser-side scripting? Even
things like CoffeeScript and Dart only translate into JavaScript and still use
its runtime.

We just have to continue to improve the tooling.

~~~
marcosdumay
What he said in his first sentence is false. Thus, there is no alternative.

It's a quite strange way of writing a sarcastic comment, but it does agree
with you.

