Tracing errors in client-side JavaScript applications

ARTHR, Newspaper Club’s online layout tool, is what the cool kids call a ‘rich web application’. It’s a Backbone.js application that renders templated Javascript views, generated dynamically.

As you should do with any grown-up piece of software, we put in an error logging system, so if someone manages to trigger a error, we get notified of it and can try to fix it.

We’re using the window.onerror callback, which takes three arguments: the error message, the URL of the script that triggered it, and the line number. On some browsers there are two more parameters: the column (useful for heavily minified code) and the error object, but that’s a fairly recent addition to the spec and not widely supported.

It became quickly obvious that some people have pretty odd browser setups. We spent a while trying to track down errors which turned out not to be in our code, or in any code on that page anywhere. These turned out to be from two sources: JS injected by HTTP proxies, and JS injected by browser extensions.

We fixed the problems with JS injected by HTTP proxies by running the whole application over SSL/TLS. The performance impact is negligible and a whole class of errors disappeared immediately.

And we fixed the problems caused by browser extensions by ignoring all script URLs outside of our domain and that of our CDN. They’ll still cause errors that’ll be visible in the console, but our code won’t trap and log them.

We ended up with a window.onerror function that looks a bit like this:

<script>
  var errorSent = false;

  window.onerror = function (message, file, line, column, error) {
    // If the error has occurred in a file beyond our control, we don't
    // handle the error. That's up to your crazy browser extensions.
    // This isn't a particularly robust check.
    var domainRegexp = new RegExp("^https?://[^.]+\.(newspaperclub|cloudfront)\.");
    if (!domainRegexp.test(file)) {
      return false;
    }

    if (!errorSent) {
      setTimeout(function () {
        var data, stack = '()@' + file + ':' + line;

        if (ARTHR && ARTHR.log) {
          // Add the column of the exception to the stack, if available.
          if (column) {
            stack =  stack + '#' + column;
          }

          // This is what we log.
          data = {
            message: message,
            stack: stack
          };

          // If the browser supports the new error parameter, try to unpack
          // the stack and message and pass it along to the logged data too.
          if (error) {
            if (typeof error === "string") {
              data.error = error;
            } else if (error instanceof Error) {
              data.error = error.name;
              data.error_message = error.message;
              data.error_stack = error.stack;
            } else {
              // Fallback to just logging whatever we've got.
              data.error = error;
            }
          }

          ARTHR.log.error(data);
        }
      }, 10);

      setTimeout(function () {
        if (ARTHR && ARTHR.GlobalNotificationView) {
          var notification = new ARTHR.GlobalNotificationView({
            title: "Sorry, Something Went Wrong",
            description: "<p>We're sorry, something has gone wrong with ARTHR.</p><p>The Newspaper Club team has been notified, but if the problem persists, please <a href=\"http://www.newspaperclub.com/about/contact\">contact us directly</a>, and we'll try and work out what's going wrong. Otherwise, please refresh the page and ARTHR will reload with the latest changes.</p><p><a href='#' class='button' onclick='javascript:window.location.reload(true)'>Restart ARTHR</a></p>",
            noticeType: "error",
            fullScreen: true,
            disableKeyboardClose: true,
            permanent: true
          });

          notification.render().display();
        }
      }, 10);

      errorSent = true;
    }

    return true;
  };
</script>

There’s a few things going on here. Firstly, we’re only catching the error the first time the browser throws an exception, so we don’t get swamped. Secondly, we’re calling our own ARTHR.log function which switches between local and remote logging, depending on the environment. In production it logs to the server over AJAX, so an entry appears in our logging system and in some situations an email is sent out to the team.

The code that displays the error to the user, and the code that sends the log message, are both executed using setTimeout with a short interval (10ms). This makes them run asynchronously, and ensures that the failure of one to execute (due to a bug or an odd situation) doesn’t prevent the other from running.

We still get a class of errors that are difficult to trace. Dumping the entire state of the application might be helpful here, including a snapshot of the DOM, all the bound events and so on. That might be straightforward, but I’ve not looked into it.

It’s often better to try and catch exceptions deeper into the code, rather than letting window.onerror handle it, but for unknown unknowns, this is a useful tool in the debugging arsenal. If you’ve got your own version of this, or there’s a much smarter way of handling JS error tracking, I’d love to hear it.

An Act of Rebellion

I choose to believe that somewhere out there there’s a freelance book designer on a mission. Their goal is to fight the homogeneity of modern cover design, and roll back the oppression forced upon them by the Big Publishers.

But you can’t slip an act of rebellion past just any old client. You have to pick your moment and find one so incompetent that they’ve never looked at a bookshelf before.

Designs of the Year Covers

From Ben.

(I had a look at my bookshelf at home, and the only two books that do this are French and Spanish. Is this a foreign thing?)

Station Ident

Newspaper Club Exhibition

Much like Alice, every now and again someone says to me: “you still doing that newspaper thing”? Yes, yes I am! Still!

It’s year five now, pretty much. I didn’t think I’d ever do a thing this long. I might never again. But it turns out businesses are hard, especially when they involve atoms and even more so if you want to be profitable, legal and have good customer service. Not that much of that is to do with me.

We’ve just launched one of the things I naively thought we’d be doing in the second year: selling print-on-demand newspapers from our site, with a nice profit sharing arrangement. This means you can, for example, grab a bunch of your favourite posts from your blog, put them into ARTHR to do a quick layout, print a single copy to check it all over, then start selling it from our site. We take our cut and send you your cut shortly after.

And we’re running a small beta test of a personalised newspaper service. I can’t say too much about that yet (other than it’s a lot of fun), because we’re still working out the shape of it, but if you think something like that might work for [your large media organisation], we should have a chat.

PaperLater

So it’s good. And hard. But good.

adequate responses

“adequate responses, including of a technical and technological nature”

From. Pretty much describes my job.

Roll 13, 14

Still shooting film. Still enjoying it. If I get one good shot out of a roll I’m happy.

Roll 13/02

Roll 13/31

Roll 14/23

I’ve been using Eye Culture in Bethnal Green for processing and scans. High res JPEGs are about £6/roll. They do a good job.

Facts Not Opinions

Facts not opinions

Facts not opinions, inscribed on Kirkaldy Testing Museum.

1. I love Matt Edgar’s posts about historical engineers. It’s like a mini In Our Time, without the posh people.
2. I’d like to see more mottos inscribed on buildings. Mottos, not slogans.

Recent quotes

If a cook touches a sauce, it gets passed through a sieve.

Love that.

Sometimes all you need is for someone to see what you are planning and not look bemused.

On Bill Drummond and Jimmy Cauty, from the book about the KLF with the long subtitle.

Perhaps more than anything they did, The Manual led to the pair being perceived as cynical media manipulators rather than random followers of chaos. In a sense, this was always inevitable when they became successful because the public narrative believes that success comes from knowing what you are doing. The equally common phenomenon of stumbling upwards is rarely recognised.

From the same book.

Almost as much a condo as a car.

From this video about the Pontiac Stinger, found via Fosta. If anyone wants me to do a presentation about feature creep, I am ready now.