Hacker News new | past | comments | ask | show | jobs | submit login
So you're thinking of tracking your JS errors (meldium.com)
85 points by william_hc on Sept 30, 2013 | hide | past | favorite | 37 comments



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

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.


"within the next week" -- Awesome!

"We're turning on CORS for the Google JS Library CDN, too." -- Awesome!

Things are looking up!


Unfortunately, Firefox and Safari still don't provide column numbers in error stack traces, only line numbers, which are next to useless if your code is minified. Here's the Firefox bug:

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.


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.


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... and you will see code that wraps $.event.add(), $.fn.ready(), $.ajax(), window.setTimeout() and window.setInterval() (search _helper).


Super excited about CORS and sourcemaps on all of the CDNs.

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.


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.


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), but since everything is open source, we bundle their raven library (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?


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?


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


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.


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.


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.


Wow, that is actually... surprisingly simple, almost even elegant.

It's times like these when you realize just how flexible JavaScript development can be.


I didn't say you did. I don't think the size of the app is a factor, either.

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.


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.


Most of my errors are injected javascript. (not that I'm a bug free programmer, but I can fix my shit. the other stuff, it just keeps coming. and coming. It's like the fecking zombie hordes.).

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


We also receive a phenomenal amount of noise due to browser extensions and so on. Switching to HTTPS helped, and so has filtering out errors from scripts on domains we don't expect (i.e. not our main domain or CDN's domain).

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.


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.


Go to https://github.com/getsentry/raven-js/blob/master/dist/1.0.8... 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.


In addition to the excellent services mentioned in the article, I'd like to recommend Rollbar (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 :)


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


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.


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


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


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 [2] https://github.com/kriskowal/q#handling-errors


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

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.


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


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.


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


This is one reason that I've been very excited/anxious for http://trackjs.com/ to launch.


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


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.


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.


The post in question specifically talks about catching client side errors in browsers, for which there isn't any real alternative to JS.


The lack of alternatives is the precise and central point of my comment actually.


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

I assume by that you mean as per an IEEE specification.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: