Client-Side JavaScript Error Logging, Part II

By Karolis Dzeja, UX Developer

This is the second part of a series of articles describing the best practices to log client-side JavaScript errors. Originally posted on my blog, read Part I.

We left off in Part I sending our error messages to Google Analytics and we discussed its limitations. It wasn’t real-time, meaning we can’t rely on it to respond immediately to issues and there are limits to the data we can track.

To send the data to our server to be recorded, we could try something like this:

window.onerror = function(message, url, linenumber) {
  if (window.XMLHttpRequest) {
    var xhr = new XMLHttpRequest();
    var server = "http://server.example.com/errorlog";
    var log = linenumber + message + url;
    xhr.open("POST", server);
    xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
    xhr.send(log);
  }
  return true;
}

But we’ll encounter cross-origin restrictions if our server is on another domain. So, if we can’t use client-side JavaScript to POST data to another domain, how does Google Analytics do it? They use a tracking pixel. Google’s script does a GET request for a 1×1 image and puts the data in the query parameters after the file name, which the server can read:

<img src="http://server.example.com/pixel.gif?message=value&url=value2&line=value3">

Besides sending the error message, url of the file, and linenumber, what else would we like to know about the user who experienced this error? Grabbing the user agent will tell us the browser and operating system. We can get the screen dimensions and device-pixel-ratio to get a better understanding of the device. We could get a full feature profile using Modernizr, but that’s probably overkill.

Let’s structure our data with JSON:

{
  "message": message,
  "url": url,
  "linenumber": linenumber,
  "page": location.href,
  "userAgent": navigator.userAgent,
  "resolution": screen.width + "x" + screen.height,
  "devicePixelRatio": window.devicePixelRatio
}

That’ll give us enough to start debugging errors. We’ll be able to get a good idea of the context of the error. Further ideas of information to include things like user type (ex. logged in vs not logged in, admin or regular user).

Let’s make an array like Google Analytics does and collect errors in it:

<!DOCTYPE html>
<html>
  <head>
    <script>
      // define the array to hold errors
      var _err = _err || [];

      // push errors to the array
      window.onerror = function(message, url, linenumber) {
        _err.push({
          "message": message,
          "url": url,
          "linenumber": linenumber,
          "page": location.href,
          "userAgent": navigator.userAgent,
          "resolution": screen.width + "x" + screen.height,
          "devicePixelRatio": window.devicePixelRatio
        });
      }      

      // create a pixel from an array item
      function sendPixel(data) {
        var img = document.createElement("img");
        var src = "http://server.example.com/pixel.gif?id=1";
        for (key in data) {
          src += "&" + key + "=" + encodeURIComponent(data[key]);
        }
        img.src = src;
        document.body.appendChild(img);
      }

      // send all queued pixels, listen for new pixels
      function initErr() {
        _err.forEach(function(item) {
          sendPixel(item);
        });
        _err = [];
        _err.push = function(data) {
          sendPixel(data);
        }
      }

      // wait for body to load to put pixels in it
      var _err_timer = setInterval(function() {
        if (document.body != null) {
          document.body.addEventListener("load", initErr(), false);
          clearInterval(_err_timer);
        }
      }, 10);
    </script>
  <!-- rest of page -->

Now that we have the client-side script, we’ll have to make a server for it and test it out.

Back to Part I