BOOMR.plugins. Errors


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

This plugin has a corresponding Header Snippets that helps capture errors prior to Boomerang loading.

For information on how to include this plugin, see the Building tutorial.

Sources of Errors

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

All of the options above 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. Due to the lack of error detail on some older browsers, some errors may be reported more than once.

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:

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.

Example:

BOOMR.init({
  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 capturing of errors after page load, you need to set sendAfterOnload to true. If set, errors that happen after the page load will be sent at most once every sendInterval (which defaults to 1 second) on a new beacon.

Example:

BOOMR.init({
  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.

You can increase (or decrease) maxErrors. For example:

BOOMR.init({
  Errors: {
    maxErrors: 20
  }
});

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.

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" attrib 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 (via the monitorTimeout option) and addEventListener (via the monitorEvents option) 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).

Note that enabling monitorTimeout or monitorEvents can have side-effects and has caused compatibility issues with JavaScript code on some sites. Enabling those options is only recommended after verifying there are no problems.

Why is Boomerang in my 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
  • console.error if error tracking is turned on
  • setTimeout and setInterval(if error tracking is turned on (with monitorTimeout)
  • addEventListener and removeEventListener (if error tracking is turned on (with monitorEvents)

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://mysite.com/boomerang.js: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://mysite.com/boomerang.js: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://mysite.com/boomerang.js: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
  • BOOMR_plugins_errors_onrejection

Side Effects

Enabling wrapping through monitorEvents and monitorTimeout may trigger some side effects:

  • Boomerang's monitoring code will be run first for every callback, which will add minimal (though non-zero) overhead.
  • In browser console logs, errors that are triggered by other libraries that have been wrapped will now look like they come from Boomerang instead, as Boomerang is now on the bottom of the call stack.
  • Browser developer tools such as Chrome's Performance and Profiler tabs may be confused about JavaScript CPU attribution. In other words, they may think Boomerang is the cause of more work than it is.
  • Chrome Lighthouse may be confused about JavaScript CPU attribution, due to the same reasons as above.
  • WebPagetest may be confused about JavaScript CPU attribution, due to the same reasons as above.
  • There are some cases where JavaScript applications may have compatibility issues with the wrapping. Some notable cases include:
    • Use of the global window.event object, see issue.
    • Other libraries that wrap setTimeout, addEventListener, etc such as history.js.
    • Pages that use the <base href="..."> tag.

For more details, you can read this article.

Beacon Parameters

  • err: The compressed error data structure
  • http.initiator = error (if not part of a Page Load beacon)

The compressed error data structure is a JSURL encoded JSON object.

Each element in the array is a compressed representation of a JavaScript error:

  • n: Count (if the error was seen more than once)
  • f[]: An array of frames
    • f[].l: Line number
    • f[].c: Colum number
    • f[].f: Function name
    • f[].w: File name (if origin differs from root page)
    • f[].wo: File name without origin (if same as root page)
  • s: Source:
    • 1: Error was triggered by the application
    • 2: Error was triggered by Boomerang
  • v: Via
    • 1: Application (BOOMR.plugins.Errors.send)
    • 2: Global exception handler (window.onerror)
    • 3: Network (XHR) error
    • 4: Console (console.error)
    • 5: Event handler (addEventListener)
    • 6: setTimeout or setInterval
  • t: Type (e.g. SyntaxError or ReferenceError)
  • c: Code (for network errors)
  • m: Error messag
  • x: Extra data
  • d: Timestamp (base 36)

Methods


init(config)

Initializes the plugin.

Parameters:

Name Type Description
config object

Configuration

Properties
Name Type Argument Description
Errors.onError function <optional>

Callback to fire when an error occurs

Errors.monitorGlobal boolean <optional>

Monitor window.onerror

Errors.monitorNetwork boolean <optional>

Monitor XHR errors

Errors.monitorConsole boolean <optional>

Monitor console.error

Errors.monitorEvents boolean <optional>

Monitor event callbacks (from addEventListener). NOTE: Enabling this may cause compatibility issues with certain sites. Verifications should be run before enabling in production.

Errors.monitorTimeout boolean <optional>

Monitor setTimout and setInterval. NOTE: Enabling this may cause compatibility issues with certain sites. Verifications should be run before enabling in production.

Errors.monitorRejections boolean <optional>

Monitor unhandled promise rejections.

Errors.monitorReporting boolean <optional>

Monitor Reporting API warnings.

Errors.sendAfterOnload boolean <optional>

Whether or not to send errors after the page load beacon. If set to false, only errors that happened up to the page load beacon will be captured.

Errors.sendInterval number <optional>

If sendAfterOnload is true, how often to send the latest batch of errors.

Errors.sendIntervalDuringLoad number <optional>

How often to send a beacon during onload if autorun=false

Errors.maxErrors number <optional>

Maximum number of errors to track per page.

Returns:

BOOMR.plugins.Errors The Errors plugin for chaining


is_complete()

This plugin is always complete (ready to send a beacon)

Returns:

Type: boolean

true


is_supported()

Determines if Error tracking is initialized.

Returns:

Type: boolean

true


send(error)

Sends an error

Parameters:

Name Type Description
error Error | String

Error object or message


test(fn, that, args)

Runs the function, watching for exceptions

Parameters:

Name Type Description
fn function

Function

that object

Target object

args Array.<object>

Arguments


wrap(fn, that, via)

Wraps the function in an exception handler that will automatically report exceptions.

Parameters:

Name Type Description
fn function

Function

that object

Target object

via number

Via (optional)

Returns:

Type: function

Wrapped function