Errors

The Boomerang Errors plugin automatically captures JavaScript and other errors from your web application.

Sources of errors

When the Errors plugin is enabled, the following sources of errors are captured:

These are all enabled by default, and can be manually turned off.

Supported browsers

The Errors plugin can be enabled for all browsers, though some older browsers may not be able to capture the full breadth of sources of errors.

Notable browsers:

  • Internet Explorer <= 8: Does not support capturing XMLHttpRequest errors.

Manually sending errors

Besides automatically capturing errors from onerror, XMLHttpRequest, console.error or event handlers such as setTimeout, you can also manually send errors.

There are three ways of doing this as follows:

Dealing with script error

When looking at JavaScript errors, you will likely come across the generic error message: Script error.

Script Error. is the message that browsers send to the window.onerror global exception handler when the error was triggered by a script loaded from a different (cross) origin. window.onerror
is used by Boomerang so that it gets notified of all unhandled exceptions.

The Script Error. string is given instead of the real error message and does not contain any useful information about what caused the error. In addition, there is no stack associated with the message, so it's impossible to know where or why the error occurred.

Browsers mask the real error message for cross-origin scripts due to security and privacy concerns - they don't want to leak sensitive information in the message or stack. The only thing that window.onerror knows for cross-origin scripts is that an error occurred, not where or why.

Example

For an example of where you'd see Script Error., consider the following code that lives on website.com:

<html>
    <head>
        <title>website.com</title>
    </head>
    <body>
        <script>
            window.onerror = function(message, url, line, column, error) {
                console.log("window.onerror: " + message);
                console.log((error && error.stack) ? error.stack : "(no stack)");
            };
        </script>
        <script src="my-script.js"></script>
        <script src="https://anothersite.com/my-script.js"></script>
    </body>
</html>

Assume my-script.js is the same file being served from both website.com and anothersite.com:

function runCode() {
    a = b + 1;
}

runCode();

When my-script.js is loaded from website.com, it will be executed twice:

  1. First on the same-origin, where we'll see the full error message followed by the stack:

    window.onerror: Uncaught ReferenceError: b is not defined
    
    ReferenceError: b is not defined
        at runCode (my-script.js:2)
        at my-script.js:5
    
  2. Then, it will be loaded from https://anothersite.com/my-script.js, which will be considered cross-origin and only Script Error. will be logged:

    window.onerror: Script error.
    
    (no stack)
    

As you can see, browser shares the full details of the exception when it's served from the same origin as the website, but if it's served from any other origin, it will be considered cross-origin and no details will be shared.

Note that while the browser only shares Script Error. to window.onerror for cross-origin scripts, if you have browser developer tools open, the browser will show you the full error message in the Console. This is
because there aren't any security or privacy concerns for a developer looking at their own machine's information.

The screenshot below shows what the Chrome Developer tools look like for the above
code:

  1. The first (same-origin) error message is written to the console.log()
    (in black) followed by the browser developer tools logging the same error (in red).

  2. The second (cross-origin) error only shows Script error. in the console.log()
    (in black) with no stack, but the browser developer tools show the full message
    and stack (in red):

When You'll See Script Error

Unfortunately Script Error. will be shown in many legitimate use-cases,
such as:

  1. When serving your website's JavaScript from a CDN (since it will be coming
    from a different origin)

  2. When loading a library such as jQuery or Angular from their CDN, i.e. Google's Hosted Libraries or cdnjs

  3. When a third-party script loads from another domain

The good news is that in many of these cases, there are changes you can make to ensure the full error message and stack are shared with window.onerror.

Fixing Script Error

To ensure a cross-origin script shares full error details with window.onerror, you'll need to do two things:

  1. Add crossorigin="anonymous" to the <script> tag

    The crossorigin="anonymous" attribute tells the browser that the script should be fetched without sending any cookies or HTTP authentication

  2. Add the Access-Control-Allow-Origin (ACAO) header to the JavaScript file's response.

    The Access-Control-Allow-Origin header is part of the Cross Origin Resource Sharing (CORS) standard.

    The ACAO header must be set in the JavaScript's HTTP response headers.

    An example header that sets ACAO for all calling origins would be:

    Access-Control-Allow-Origin: *

If both conditions are true, cross-origin JavaScript files will report errors to window.onerror with the correct error message and full stack.

The biggest challenge to getting this working is that (1) is within the site's control while (2) can only be configured by the owner of the JavaScript. If you're loading JavaScript from a third-party, you will need to encourage them to add the ACAO header if it's not already set. The good news is that many CDNs and third-parties set the ACAO header already.

Workarounds for Third Parties that aren't sending ACAO

One way to help monitor for errors coming from third-party scripts that aren't setting ACAO (and aren't within your control) is by manually wrapping calls to any of the third-party script's functions in a try {} catch {}.

try {
    // calls a cross-origin script that doesn't have ACAO
    runThirdPartyCode();
} catch (e) {
    // report on error with e.message and e.stack
}

If runThirdPartyCode() causes any errors, the catch {} handler will get the full details of the exception.

Unfortunately this won't work for functions that are executed in the third-party script as a result of browser events or callbacks (since you're not wrapping them).

When using Boomerang to monitor JavaScript errors, Boomerang automatically wraps some of the built-in browser APIs such as setTimeout, setInterval and addEventListener with a minimal-overhead wrapper. It does this to help ensure as many cross-origin exceptions as possible have full stack details. You may also do this manually via BOOMR.plugin.Errors.wrap(function).

Why Boomerang is in the error stack

When looking at error reports, you may find errors that have a function in boomerang.js (or /boomerang/) on the stack. Why is that? Is Boomerang causing errors on your site?

One of the ways that Boomerang is able to monitor and measure your site's performance is by wrapping itself around some of the core browser APIs. Boomerang only does this in a few places, if absolutely necessary -- namely, when the browser doesn't provide a native "monitoring" interface for something that needs to be tracked.

One example is for XMLHttpRequests, as there are no browser APIs to monitor when XHRs load. To monitor XHRs, Boomerang swaps in its own window.XMLHttpRequest object, wrapping around the native methods. When an XHR is created (via .open()), the lightweight Boomerang wrapper is executed first so it can log a start timestamp. When the XHR finishes (via a readyState change), Boomerang can log the end timestamp and report on the XHR's performance.

Examples of Boomerang wrapping native methods include:

  • XMLHttpRequest if the XHR instrumentation is turned on
  • setTimeout and setInterval if error tracking is turned on
  • console.error if error tracking is turned on
  • addEventListener and removeEventListener if error tracking is turned on

All of these wrapped functions come into play when you see an error stack with a boomerang.js function in it.

Often, the boomerang.js function will be at the bottom of the stack (the first function called). This does not mean Boomerang caused the error, merely that the monitoring code was running before the error occurred. The actual
error happens towards the top of the stack -- the function that ran and threw the exception.

Let's look at some examples:

Cannot read property 'foo' of undefined at thirdPartyTwo (https://thirdparty.com/core.js:1:100)
at thirdPartyOne (https://thirdparty.com/core.js:1:101)
at runThirdParty (https://thirdparty.com/core.js:1:102)
at xhrCallback (http://website.com/site.js:2:200)
at XMLHttpRequest.send (https://c.go-mpulse.net/boomerang/XXXXX-XXXXX-XXXXX-XXXXX-XXXXX:3:300)

In the above example, Boomerang is monitoring XMLHttpRequests. An XHR was loaded on the site, and during the XHR callback, an exception was thrown. Even though /boomerang/ is listed here, the error was caused by code in the XHR callback (xhrCallback eventually calling thirdPartyTwo).

Here's a second example:

Reference error: a is not defined at setTimeout (http://website.com/site.js:1:200) at BOOMR_plugins_errors_wrap (http://c.go-mpulse.net/boomerang/XXXXX-XXXXX-XXXXX-XXXXX-XXXXX:3:300) at onclick (http://website.com/site.js:1:100)

In the above example, JavaScript Error Reporting is enabled and an exception was thrown in a setTimeout() on the website. You can see the BOOMR_plugins_errors_wrap function is near the top of the stack, but this is merely the error tracking code. All it did was wrap setTimeout to help ensure that cross-origin exceptions are caught. It was not the actual cause of the site's error.

Here's a third example:

Error: missing argument 1 at BOOMR.window.console.error (https://c.go-mpulse.net/boomerang/XXXXX-XXXXX-XXXXX-XXXXX-XXXXX:3:300)
at u/< (https://website.com/site.js:1:100)
at tp/this.$get</< (https://website.com/site.js:1:200)
at $digest (https://website.com/site.js:1:300)
at $apply (https://website.com/site.js:1:400)
at ut (https://website.com/site.js:1:500)
at it (https://website.com/site.js:1:600)
at vp/</k.onload (https://website.com/site.js:1:700)

In the above example, JavaScript Error Reporting is enabled and has wrapped console.error. The minified function u/< must be logging a console.error, which executes the Boomerang wrapper code, reporting the error.

In summary, if you see Boomerang functions in error stacks similar to any of the ones listed below, it's probable that you're just seeing a side-effect of the monitoring code:

  • BOOMR_addError
  • BOOMR_plugins_errors_onerror
  • BOOMR_plugins_errors_onxhrerror
  • BOOMR_plugins_errors_console_error
  • BOOMR_plugins_errors_wrap
  • BOOMR.window.console.error

Configuration options

Enabling

Check the Collect Javascript Errors checkbox in the mPulse Configure Web App dialog box on the Beacons tab.

Enabling with config overrides

The Errors plugin is disabled by default, but can be enabled via Config Overrides:

window.BOOMR_config = window.BOOMR_config || {};
BOOMR_config.Errors = {
   enabled: true,
   monitorTimeout: false,
   monitorEvents: false
};

Sources

Each of the sources of errors can be turned off manually via the following monitor* options:

For example:

window.BOOMR_config = window.BOOMR_config || {};
BOOMR_config.Errors = {
    monitorGlobal: true,  // onerror
    monitorNetwork: true, // XHRs
    monitorConsole: true, // window.console.error
    monitorEvents: true,  // addEventListener
    monitorTimeout: true, // setTimeout, setInterval
};

Error callback

You can specify an onError function that the Errors plugin will call any time an error is captured on the page.

If your onError function returns true, the error will be captured. If your onError function does not return true, the error will be ignored.

For example:

window.BOOMR_config = window.BOOMR_config || {};
BOOMR_config.Errors = {
    onError: function(err) {
        if (err.message && err.message.indexOf("internally handled")) {
            return false;
        }
        return true;
    }
};

When to send errors

By default, errors captured during the page load will be sent along with the page load beacon. Errors that happen after the page load will not be captured or sent.

To enable this feature, check the Send Error Beacon if JavaScript Errors happen after the Page Load checkbox under the Collect Javascript Errors feature in the mPulse Configure Web App dialog box on the Beacons tab. The Send Interval (in milliseconds) text box controls the interval that errors occurring after the page load are sent on a new beacon.

Alternatively, to enable capturing of errors after page load via Config Overrides, set sendAfterOnload to true. If set, errors that happen after the page load will be sent at most once every sendInterval (which defaults to 1000 milliseconds) on a new beacon.

For example:

window.BOOMR_config = window.BOOMR_config || {};
BOOMR_config.Errors = {
    sendAfterOnload: true,
    sendInterval: 5000
};

How many errors to capture

The Errors plugin will only capture up to maxErrors (defaults to 10) distinct errors on the page.

Please note that duplicate errors (those with the same function name, stack, and so on) are tracked as single distinct error, with a count of how many times it was seen.

Enter the desired value in the Maximum Number of Unique Errors to Track per Page text box under the Collect Javascript Errors feature in the mPulse Configure Web App dialog box on the Beacons tab.

Alternatively, maxErrors can be increased (or decreased) via Config Overrides. For example:

window.BOOMR_config = window.BOOMR_config || {};
BOOMR_config.Errors = {
    maxErrors: 2
};