Hacker News new | ask | show | jobs
by rakeshpai 5185 days ago
I'm the developer of Errorception (http://errorception.com/), and wanted to jump in to talk about some of the points raised on the thread here.

As chrisacky mentioned, making one HTTP post for every error is very wasteful. It's much better to buffer up such errors (Errorception does it in memory, since localStorage can be unavailable and/or full), and post them every once in a while.

As masklinn pointed out, window.onerror _is_ complete shit, so Errorception does a couple of tricks to make this slightly better. Firstly, on IE, the stack isn't lost when window.onerror is called, so it's possible to get the call stack and arguments at each level of the stack. Secondly, it's very easy to get other kind of details (browser, version, page, etc.), which helps a great deal in aiding debugging.

However, masklinn's suggestion about wrapping code in try/catch blocks is probably not a good idea. This is because some interpreters (I know v8 does this) don't compile the code in such code-paths. May cause a performance hit.

As DavidPP mentioned, depending on the nature of your application, it might be a good idea to not record too much sensitive information. For example, Errorception doesn't record function arguments if the page is served over SSL.

troels is right - this does create a massive flood of errors. There are several ways to deal with this. What we do for example, is hide away errors from most third-parties - Facebook, Twitter, GoogleBot, GoogleAnalytics, etc. The rest of the errors can still be huge in number, so we group similar errors together based on several parameters like browsers, browser versions, possible variation in inline code line-numbers because of dynamic content, etc.

Also, as Kartificial pointed out, this is probably something you don't want to do on your own server. You want to move this out of your infrastructure, and distribute it if possible.

There are other concerns - some that come to mind are page load time, ensuring that your error catching code itself doesn't raise errors (or if it does, it doesn't affect your app in any way), and that of managing data-growth on the server. These are fun problems, but it's probably not worth re-inventing the wheel.

</plug>

5 comments

The V8 performance issue with try/catch is widely misunderstood, and it causes people to unnecessarily avoid using it.

try/catch can slow down code in the same function as the try/catch statements. But it doesn't slow down any other function called from within there. In other words, it only affects the stack frame at which the try/catch statement actually appears.

So if you have some top level error trapping function like:

    errorCatcher = function(continuation){
      try {
        continuation();
      catch(err){
        reportErrorToServer(err);
      }
    }
and you only call it once at every entry point to your code, the impact will be totally undetectable, because there's only a single function invocation within the unoptimizable area.

I encourage people to performance test the difference caused by try/catch like this. You'll see that it doesn't hurt at all. It's only a problem if you actually write a try statement within some tight inner-loop function.

(Rewritten to report results)

I tried the following 4 variations in Node.js 0.6.13, V8 3.6.6.24. (The "global" business is for accessing the functions from a REPL.)

  var lots = 1000000;
  
  // A: try/catch inside loop
  global.a = function () {
      for (var i=0; i<lots; i++) {
          try { } catch (e) { }
      }
  }
  
  // B: one try/catch, inlined loop
  global.b = function () {
      try {
          for (var i=0; i<lots; i++) { }
      } catch (e) { }
  }
  
  // C: one try/catch, loop in local function
  global.c = function () {
      var loop = function () {
          for (var i=0; i<lots; i++) { }
      }
      try { loop(); } catch (e) { }
  }
  
  // D: one try/catch, loop in top-level function
  function loop () {
      for (var i=0; i<lots; i++) { }
  }
  global.d = function () {
      try { loop(); } catch (e) { }
  }
  
  // E: no try/catch
  global.e = function () { loop(); }
  
  global.time = function (times, fn) {
      var start = new Date().getTime();
      for (var i=0; i<times; i += 1) { fn(); }
      return (new Date().getTime() - start) / times;
  };
In the REPL:

  > ({ a : time(100,a), b : time(100,b), c : time(100,c), d : time(100,d), e : time(100,e) });
  { a 7.66 b 5.29 c 5.06 d 1.45 e 1.32 }
So a million try/catches (A) is bad. But hoisting the try outside the loop (B) or putting the loop into a local function (C) doesn't do nearly as well as putting the loop in an outside function (D), which is almost as fast as no try/catch at all (E). I tried a few variations (like making the loop actually do some work) and the outcomes were all comparable.

This clarifies and confirms what ef4 said: don't put cpu-intensive code inside the same function as a try/catch. Put it somewhere else and call it. Also, local functions don't necessarily count as "somewhere else".

My understanding is that each time the try is "executed" makes a performance hit.
(I've rewritten the GP)

That appears to be wrong, since B, C, and D all execute the same number of try/catches.

This is what I meant, though I guess I could've used a better term than "code paths". Thanks for clarifying this anyway. :)
"making one HTTP post for every error is very wasteful."

We implemented this where I work many years back, and as long as you are keeping on top of the errors this isn't a problem.

If you are having to buffer your errors before sending them off to be logged because you are noticing a drain on your resources, you're doing it wrong.

You aren't wrong if you are looking at the server's resource consumption. However, the need to buffer the errors is so that we can minimize network overhead, from the client's perspective. It's to reduce the number of HTTP connections, even if it means the payload is larger - much like why CSS sprites are a good idea.
CSS sprites are a good idea because modern web pages have a zillion little icons. If you have a zillion little errors per pageview, I'm inclined to agree with xd: that's the sign of an underlying problem that should be fixed directly. Or if the errors are truly spurious junk, then I've just fixed that by not sending them over the wire at all. Is there some case that wouldn't cover?
It's not very complicated to modify the logic to start buffering on a second error (the first one is sent immediately), and then flush errors to the server and repeat this logic every X seconds... it's just a matter of an extra flag in the error handler
True, also come to think about it, I guess it would limit the damage a rouge loop spitting errors at the server would cause.
Link about v8 performance cost of try/catch.

http://floitsch.blogspot.in/2012/03/optimizing-for-v8-introd... "It turns out that some constructs are not yet supported by Crankshaft. In particular, try/catch turns off all optimizations in a function."

Yes, but only in the function where the try catch appears. Whatever you call from there, even if it's protected within the stack of the try/catch statement, is still optimizable.

So entering a try/catch once and then doing all your work inside it is still fast. What's expensive is constantly entering new try/catch contexts.

Your plug was successful sir! I had never heard of Errorception and was planning to implement client side error handling myself. I will now be trying out your service for my BugMuncher app in the very near future.
It'd be really nice if you had a demo UI available to check out.
It would also be nice if there were an example showing the code necessary for integration.