Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript global event mechanism

How to Catch Unhandled Javascript Errors

Assign the window.onerror event to an event handler like:

<script type="text/javascript">
window.onerror = function(msg, url, line, col, error) {
   // Note that col & error are new to the HTML 5 spec and may not be 
   // supported in every browser.  It worked for me in Chrome.
   var extra = !col ? '' : '\ncolumn: ' + col;
   extra += !error ? '' : '\nerror: ' + error;

   // You can view the information in an alert to see things working like this:
   alert("Error: " + msg + "\nurl: " + url + "\nline: " + line + extra);

   // TODO: Report this error via ajax so you can keep track
   //       of what pages have JS issues

   var suppressErrorAlert = true;
   // If you return true, then error alerts (like in older versions of 
   // Internet Explorer) will be suppressed.
   return suppressErrorAlert;
};
</script>

As commented in the code, if the return value of window.onerror is true then the browser should suppress showing an alert dialog.

When does the window.onerror Event Fire?

In a nutshell, the event is raised when either 1.) there is an uncaught exception or 2.) a compile time error occurs.

uncaught exceptions

  • throw "some messages"
  • call_something_undefined();
  • cross_origin_iframe.contentWindow.document;, a security exception

compile error

  • <script>{</script>
  • <script>for(;)</script>
  • <script>"oops</script>
  • setTimeout("{", 10);, it will attempt to compile the first argument as a script

Browsers supporting window.onerror

  • Chrome 13+
  • Firefox 6.0+
  • Internet Explorer 5.5+
  • Opera 11.60+
  • Safari 5.1+

Screenshot:

Example of the onerror code above in action after adding this to a test page:

<script type="text/javascript">
call_something_undefined();
</script>

Javascript alert showing error information detailed by the window.onerror event

Example for AJAX error reporting

var error_data = {
    url: document.location.href,
};

if(error != null) {
    error_data['name'] = error.name; // e.g. ReferenceError
    error_data['message'] = error.line;
    error_data['stack'] = error.stack;
} else {
    error_data['msg'] = msg;
    error_data['filename'] = filename;
    error_data['line'] = line;
    error_data['col'] = col;
}

var xhr = new XMLHttpRequest();

xhr.open('POST', '/ajax/log_javascript_error');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
    if (xhr.status === 200) {
        console.log('JS error logged');
    } else if (xhr.status !== 200) {
        console.error('Failed to log JS error.');
        console.error(xhr);
        console.error(xhr.status);
        console.error(xhr.responseText);
    }
};
xhr.send(JSON.stringify(error_data));

JSFiddle:

https://jsfiddle.net/nzfvm44d/

References:

  • Mozilla Developer Network :: window.onerror
  • MSDN :: Handling and Avoiding Web Page Errors Part 2: Run-Time Errors
  • Back to Basics – JavaScript onerror Event
  • DEV.OPERA :: Better error handling with window.onerror
  • Window onError Event
  • Using the onerror event to suppress JavaScript errors
  • SO :: window.onerror not firing in Firefox

Does this help you:

<script type="text/javascript">
window.onerror = function() {
    alert("Error caught");
};

xxx();
</script>

I'm not sure how it handles Flash errors though...

Update: it doesn't work in Opera, but I'm hacking Dragonfly right now to see what it gets. Suggestion about hacking Dragonfly came from this question:

Mimic Window. onerror in Opera using javascript


sophisticated error handling

If your error handling is very sophisticated and therefore might throw an error itself, it is useful to add a flag indicating if you are already in "errorHandling-Mode". Like so:

var appIsHandlingError = false;

window.onerror = function() {
    if (!appIsHandlingError) {
        appIsHandlingError = true;
        handleError();
    }
};

function handleError() {
    // graceful error handling
    // if successful: appIsHandlingError = false;
}

Otherwise you could find yourself in an infinite loop.


Try Atatus which provides Advanced Error Tracking and Real User Monitoring for modern web apps.

https://www.atatus.com/

Let me explain how to get stacktraces that are reasonably complete in all browsers.

Error handling in JavaScript

Modern Chrome and Opera fully support the HTML 5 draft spec for ErrorEvent and window.onerror. In both of these browsers you can either use window.onerror, or bind to the 'error' event properly:

// Only Chrome & Opera pass the error object.
window.onerror = function (message, file, line, col, error) {
    console.log(message, "from", error.stack);
    // You can send data to your server
    // sendError(data);
};
// Only Chrome & Opera have an error attribute on the event.
window.addEventListener("error", function (e) {
    console.log(e.error.message, "from", e.error.stack);
    // You can send data to your server
    // sendError(data);
})

Unfortunately Firefox, Safari and IE are still around and we have to support them too. As the stacktrace is not available in window.onerror we have to do a little bit more work.

It turns out that the only thing we can do to get stacktraces from errors is to wrap all of our code in a try{ }catch(e){ } block and then look at e.stack. We can make the process somewhat easier with a function called wrap that takes a function and returns a new function with good error handling.

function wrap(func) {
    // Ensure we only wrap the function once.
    if (!func._wrapped) {
        func._wrapped = function () {
            try{
                func.apply(this, arguments);
            } catch(e) {
                console.log(e.message, "from", e.stack);
                // You can send data to your server
                // sendError(data);
                throw e;
            }
        }
    }
    return func._wrapped;
};

This works. Any function that you wrap manually will have good error handling, but it turns out that we can actually do it for you automatically in most cases.

By changing the global definition of addEventListener so that it automatically wraps the callback we can automatically insert try{ }catch(e){ } around most code. This lets existing code continue to work, but adds high-quality exception tracking.

var addEventListener = window.EventTarget.prototype.addEventListener;
window.EventTarget.prototype.addEventListener = function (event, callback, bubble) {
    addEventListener.call(this, event, wrap(callback), bubble);
}

We also need to make sure that removeEventListener keeps working. At the moment it won't because the argument to addEventListener is changed. Again we only need to fix this on the prototype object:

var removeEventListener = window.EventTarget.prototype.removeEventListener;
window.EventTarget.prototype.removeEventListener = function (event, callback, bubble) {
    removeEventListener.call(this, event, callback._wrapped || callback, bubble);
}

Transmit error data to your backend

You can send error data using image tag as follows

function sendError(data) {
    var img = newImage(),
        src = 'http://yourserver.com/jserror&data=' + encodeURIComponent(JSON.stringify(data));

    img.crossOrigin = 'anonymous';
    img.onload = function success() {
        console.log('success', data);
    };
    img.onerror = img.onabort = function failure() {
        console.error('failure', data);
    };
    img.src = src;
}

Disclaimer: I am a web developer at https://www.atatus.com/.