Tracking Website Errors with Google Analytics

Is your website compatible with every device? Is your checkout process working from start to finish for every single smartphone or tablet on customers use to shop online? Are you sure you are not missing revenue due to a broken Proceed to Checkout page, and sending traffic to your competitor’s website instead? Are those errors causing fatal crashes, or do they just cause minor inconveniences?

The truth is, you don’t know.

Of course, you test your website regularly and all its most important features have been validated on the largest set of real devices you can afford. But you can’t cover the entire set of devices readily available to the public, and deadlines are always too short.

How, then, can you be sure that website errors don’t prevent your customers from completing their transactions?

There are services available to log your JavaScript errors to a local or remote server, but these solutions cost money are require interfacing with external systems. This is usually not something stakeholders are willing to pay for — after all, there are no bugs in the application, right?

A Simple and Free Solution

Few people know about this, but the free version of Google Analytics supports exception logging. It might be caused by the fact that Google Analytics does not provide any built-in Report surfacing that information. Fortunately, that does not prevent you from making your own.

First, you will need to either:

/**!
 * Send JavaScript error information to Google Analytics.
 *
 * @param {object|undefined} options An object containing optional "applicationName" and
 * "applicationVersion" strings.
 *
 * @return {void}
 * @author Philippe Sawicki (https://github.com/philsawicki)
 * @copyright Copyright 2015 Philippe Sawicki (https://philippesawicki.com)
 */
(options => {
    // Retain a reference to the previous global error handler, in case it has been set:
    const originalWindowErrorCallback = window.onerror;

    /**
     * Log any script error to Google Analytics.
     *
     * Third-party scripts without CORS will only provide "Script Error." as an error message.
     * 
     * @param {string} errorMessage Error message.
     * @param {string} url URL where error was raised.
     * @param {number} lineNumber Line number where error was raised.
     * @param {number|undefined} columnNumber Column number for the line where the error occurred.
     * @param {object|undefined} errorObject Error Object.
     * @return {boolean} When the function returns true, this prevents the 
     * firing of the default event handler.
     */
    window.onerror = function customErrorHandler(errorMessage, url, lineNumber, columnNumber, errorObject) {
        // Send error details to Google Analytics, if the library is already available:
        if (typeof ga === 'function') {
            // In case the "errorObject" is available, use its data, else fallback 
            // on the default "errorMessage" provided:
            let exDescription = errorMessage;
            if (typeof errorObject !== 'undefined' && typeof errorObject.message !== 'undefined') {
                exDescription = errorObject.message;
            }

            // Format the message to log to Analytics (might also use "errorObject.stack" if defined):
            exDescription += ` @ ${url}:${lineNumber}:${columnNumber}`;

            // Data Object to send to Google Analytics:
            const exOptions = {
                exDescription,
                exFatal: false // Some Error types might be considered as fatal.
            };

            // Format additional Data Object Properties, if any option given:
            if (typeof options !== 'undefined') {
                if (typeof options.applicationName !== 'undefined') {
                    exOptions.appName = options.applicationName;
                }
                if (typeof options.applicationVersion !== 'undefined') {
                    exOptions.appVersion = options.applicationVersion;
                }
            }

            // Send Data Object to Google Analytics:
            ga('send', 'exception', exOptions);
        }

        // If the previous "window.onerror" callback can be called, pass it the data:
        if (typeof originalWindowErrorCallback === 'function') {
            return originalWindowErrorCallback(errorMessage, url, lineNumber, columnNumber, errorObject);
        } else {
            // Otherwise, let the default handler run:
            return false;
        }
    };
})({
    applicationName: 'Application_Name', // Optional "Application Name" parameter (set your own).
    applicationVersion: '1.0'            // Optional "Application Version" parameter (set your own).
});

// Generate an error, for demonstration purposes:
//throw new Error('Crash!');

You can test that the everything is properly set up by opening your browser’s developer console and typing throw new Error('Crash!'), then checking your Custom Report a few minutes later to make sure your Error is logged.

Using the Data

Be advised, though, that debugging minified production code might not be trivial, and that the errors logged do not necessarily mean that something terrible has happened — it only collects data that would otherwise be impossible for you to get.

The ultimate goal, of course, is to have an indication that something has changed on the website, and being able to monitor its health after rolling out an update. That could easily be done using a Timeline Chart to plot the number of errors per day, or a Table listing the most frequent errors and the Device Model on which it occurs, with the Page URL and Timestamp of each error.

Free “Website Errors” Dashboard

There are a lot of options available for Custom Dashboards and Reports, and your needs will dictate how exactly you need that data to be surfaced. For the most common uses, you can always import this free template I prepared into any of your Views.

Free "Website Errors" Dashboard

Bonus

As a bonus, you could also set up Automatic Email Alerts to be notified if Error Rates or Numbers exceed a baseline threshold.