jasonbutz.info

Tracking Unhandled JavaScript Errors with Plausible.io

plausible.io, analytics

I recently tried out Plausible as an alternative to running my own Countly. With Plausible I was able to reduce the effort I needed to put in as well as my costs, the only trade-off was giving up some advanced capabilities I didn’t use. I like Plausible’s commitment to privacy, which is part of the driver behind avoiding using a SaaS service up to this point, as well as their price point. One thing I appreciated about Countly and some of the platforms I use at work is the ability to track and report on unhandled JavaScript errors. We test our code, but sometimes we miss something.

Plausible supports custom events, and so with a little effort, I was able to add the capability to capture unhandled errors.

First I needed a snippet from Plausible’s documentation on custom event goals:

window.plausible =
  window.plausible ||
  function () {
    (window.plausible.q = window.plausible.q || []).push(arguments);
  };

This snippet ensures there is a function on the Window object called plausible that pushes the arguments it receives into an array. This is all so that the Plausible script already added to the page can pick up those values and send the event to be processed.

Next, I needed a way to capture unhandled JavaScript errors. Here I was able to take advantage of how events bubble up the DOM and attach an event listener to the window object. In the callback, I use the plausible function added by the previous snippet to report the custom event and the custom properties. If you want to read more about event bubbling check out the Mozilla Developer Network (MDN) Introduction to events page, and if you want to know where I figured out the properties to expect on the event object check out MDN’s page on the ErrorEvent object.

window.addEventListener('error', (e) => {
  plausible('JavaScript Error', {
    props: {
      message: e.message,
      filename: e.filename,
      lineno: e.lineno,
      colno: e.colno,
      url: window.location.href,
    },
  });
});

If you want to additionally capture unhandled Promise rejections you’ll need another event listener that handles a different kind of event. This time you listen for an unhandledrejection event and receive a PromiseRejectionEvent. Due to the different type of event, we aren’t able to use all the same properties.

window.addEventListener('unhandledrejection', function (e) {
  plausible('JavaScript Error', {
    props: {
      message:
        'Unhandled rejection (reason: ' +
        (e.reason && e.reason.stack ? e.reason.stack : e.reason) +
        ').',
      url: window.location.href,
    },
  });
});

Now we have our three snippets and need to put them together to form a single script. I chose to wrap them inside a try-catch block to ensure they don’t break any pages. With a try-catch block, you do need to include either a catch or finally block. I included a catch block and added a “no-op” or “no operation” comment to show nothing was included purposefully. This is a short-hand I was taught back in college and have been using whenever things like this come up.

try {
  window.plausible =
    window.plausible ||
    function () {
      (window.plausible.q = window.plausible.q || []).push(arguments);
    };

  window.addEventListener('error', (e) => {
    plausible('JavaScript Error', {
      props: {
        message: e.message,
        filename: e.filename,
        lineno: e.lineno,
        colno: e.colno,
        url: window.location.href,
      },
    });
  });

  window.addEventListener('unhandledrejection', function (e) {
    plausible('JavaScript Error', {
      props: {
        message:
          'Unhandled rejection (reason: ' +
          (e.reason && e.reason.stack ? e.reason.stack : e.reason) +
          ').',
        url: window.location.href,
      },
    });
  });
} catch (e) {
  /* NOOP */
}

You can add this JavaScript inline in a <script> tag or include it through a JavaScript file, but that is all it takes. You do need to ensure you add the JavaScript Error goal to your site in Plausible but there isn’t much to this. If you’re not using Plausible and looking for a small challenge you could build yourself a small API to collect this kind of information.

By keeping in mind the event-based nature of analytics you can easily add a variety of custom tracking functionality to many common analytics tools.