JavaScript global event mechanism
Asked Answered
V

9

395

I would like to catch every undefined function error thrown. Is there a global error handling facility in JavaScript? The use case is catching function calls from flash that are not defined.

Vessel answered 4/6, 2009 at 16:53 Comment(3)
What do you want to do with an error once you catch it? Do you just need to log it so you can create the missing function, or are you looking to stop exceptions from breaking your code?Squashy
I would like to get the name of the missing function called and based on presence of some string call my own function. Any call to a function with the string 'close' would call my close() for example. I would also like to trap the error at that point.Vessel
exceptionsjs.com provides this functionality and can be taylored to only catch errors related to undefined functionality with its "guard" functionality.Joselyn
R
206

Does this help you:

<script>
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

Render answered 4/6, 2009 at 16:57 Comment(5)
With the addition of msg, file_loc, line_no params this should do it for me. Thanks!Vessel
Do I understand correctly that this code overrides any existing error handlers?Angleworm
@MarsRobertson No.Maryannamaryanne
@atilkanwindow.onerror = function() { alert(42) }; now the code in the answer: window.onerror = function() { alert("Error caught"); }; not overriden, I'm still unsure..Angleworm
@MarsRobertson I understand. It probably overwrites. Yes, it does, just tested. Using addEventListener would be better.Maryannamaryanne
F
678

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:

Freeze answered 11/5, 2012 at 18:31 Comment(14)
worth mentionning firefox doesn't give back the error message when throw is made manually. #15036665Juno
Great answer. You can implement the "Report this error via ajax" with a package like JSNLog, which does the ajax and server side logging for you.Bronze
In addition to this answer, I added the err object so I could get the stack trace. https://mcmap.net/q/87831/-get-the-actual-javascript-error-object-with-window-onerror. Now I can develop with more feedback because my dev errors appear as a box at the top of the page (as I have created it).Sonora
Wouldn't it be better to pack your onerror implementation in a try-catch(ignore) block, preventing your onerror() to throw even more errors? (not that it's the case here, but just to be sure)Elwaine
@Sam, How can you catch only runtime errors not compile time errors?Conan
@Conan You can use error instanceof SyntaxError. See MDN instanceof docs and MDN SyntaxError. Not sure what the older browser support is for this, but MDN suggests it's supported across the board.Freeze
As you say, this handles unhandled errors. If a library is catching an error and making an alert or something, the error won't make it to window.onerror.Enenstein
it does not work now as Nov 2017, error in latest Chrome and IE 11 does not get captured by global error handlerMillymilman
@Millymilman Can you provide a jsfiddle? I don't think browsers would introduce a breaking change where the onerror event handler stops firing events. You can try this jsfiddle to see the onerror handler working: jsfiddle.net/nzfvm44d This still works for me in Chrome version 62.0.3202.94 (Official Build) (64-bit).Freeze
What is the syntax if I want a named function? window.onerror=MyErrorFunctionCommentative
@Freeze about the browser support section of your great answer, what was your source, even linked MDN page is not same as yours.Duggins
@MehdiDehghani it looks like I got it from dev.opera.com/articles/… . Generally speaking I go with the docs in MDN, but I'm certain IE had support for this before IE9. It's shocking to me that the docs for this event aren't better. Hopefully they're better now than they were when I answered this 8 yrs ago.Freeze
This does not work for errors fired inside a dynamically populated iframe, any workaround for that?Admission
@Admission You need to capture the error within the iframe using a global error handler like this post discusses. Once you capture the error in the iframe, it's up to you what you want to do with it. You can ajax the error to capture it on a server or communicate the error to the main window (example #9153945).Freeze
R
206

Does this help you:

<script>
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

Render answered 4/6, 2009 at 16:57 Comment(5)
With the addition of msg, file_loc, line_no params this should do it for me. Thanks!Vessel
Do I understand correctly that this code overrides any existing error handlers?Angleworm
@MarsRobertson No.Maryannamaryanne
@atilkanwindow.onerror = function() { alert(42) }; now the code in the answer: window.onerror = function() { alert("Error caught"); }; not overriden, I'm still unsure..Angleworm
@MarsRobertson I understand. It probably overwrites. Yes, it does, just tested. Using addEventListener would be better.Maryannamaryanne
D
41

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.

Diplosis answered 30/7, 2011 at 18:17 Comment(4)
Or a more fail-safe way would be to use try-catch around the handleError method.Catalyst
You can't use try catch if you have ansynchronous call in you'r error handling. So he's solution remain e good solutionCaprification
@EmrysMyrooin Still it will cause a sync or an async loop by error handling and probably crash without usable stack info.Shanly
I think the flag should be reset at some point, right? I believe we need a try/catch around if big if, and make sure the flag is reset right before leaving the onerror method. Otherwise, only one error will be handled.Gaffe
R
28

It seems that window.onerror doesn't provide access to all possible errors. Specifically it ignores:

  1. <img> loading errors (response >= 400).
  2. <script> loading errors (response >= 400).
  3. global errors if you have many other libraries in your app also manipulating window.onerror in an unknown way (jquery, angular, etc.).
  4. probably many cases I haven't run into after exploring this now (iframes, stack overflow, etc.).

Here is the start of a script that catches many of these errors, so that you may add more robust debugging to your app during development.

(function(){

/**
 * Capture error data for debugging in web console.
 */

var captures = [];

/**
 * Wait until `window.onload`, so any external scripts
 * you might load have a chance to set their own error handlers,
 * which we don't want to override.
 */

window.addEventListener('load', onload);

/**
 * Custom global function to standardize 
 * window.onerror so it works like you'd think.
 *
 * @see http://www.quirksmode.org/dom/events/error.html
 */

window.onanyerror = window.onanyerror || onanyerrorx;

/**
 * Hook up all error handlers after window loads.
 */

function onload() {
  handleGlobal();
  handleXMLHttp();
  handleImage();
  handleScript();
  handleEvents();
}

/**
 * Handle global window events.
 */

function handleGlobal() {
  var onerrorx = window.onerror;
  window.addEventListener('error', onerror);

  function onerror(msg, url, line, col, error) {
    window.onanyerror.apply(this, arguments);
    if (onerrorx) return onerrorx.apply(null, arguments);
  }
}

/**
 * Handle ajax request errors.
 */

function handleXMLHttp() {
  var sendx = XMLHttpRequest.prototype.send;
  window.XMLHttpRequest.prototype.send = function(){
    handleAsync(this);
    return sendx.apply(this, arguments);
  };
}

/**
 * Handle image errors.
 */

function handleImage() {
  var ImageOriginal = window.Image;
  window.Image = ImageOverride;

  /**
   * New `Image` constructor. Might cause some problems,
   * but not sure yet. This is at least a start, and works on chrome.
   */

  function ImageOverride() {
    var img = new ImageOriginal;
    onnext(function(){ handleAsync(img); });
    return img;
  }
}

/**
 * Handle script errors.
 */

function handleScript() {
  var HTMLScriptElementOriginal = window.HTMLScriptElement;
  window.HTMLScriptElement = HTMLScriptElementOverride;

  /**
   * New `HTMLScriptElement` constructor.
   *
   * Allows us to globally override onload.
   * Not ideal to override stuff, but it helps with debugging.
   */

  function HTMLScriptElementOverride() {
    var script = new HTMLScriptElement;
    onnext(function(){ handleAsync(script); });
    return script;
  }
}

/**
 * Handle errors in events.
 *
 * @see https://mcmap.net/q/86618/-javascript-global-event-mechanism/31750604#31750604
 */

function handleEvents() {
  var addEventListenerx = window.EventTarget.prototype.addEventListener;
  window.EventTarget.prototype.addEventListener = addEventListener;
  var removeEventListenerx = window.EventTarget.prototype.removeEventListener;
  window.EventTarget.prototype.removeEventListener = removeEventListener;

  function addEventListener(event, handler, bubble) {
    var handlerx = wrap(handler);
    return addEventListenerx.call(this, event, handlerx, bubble);
  }

  function removeEventListener(event, handler, bubble) {
    handler = handler._witherror || handler;
    removeEventListenerx.call(this, event, handler, bubble);
  }

  function wrap(fn) {
    fn._witherror = witherror;

    function witherror() {
      try {
        fn.apply(this, arguments);
      } catch(e) {
        window.onanyerror.apply(this, e);
        throw e;
      }
    }
    return fn;
  }
}

/**
 * Handle image/ajax request errors generically.
 */

function handleAsync(obj) {
  var onerrorx = obj.onerror;
  obj.onerror = onerror;
  var onabortx = obj.onabort;
  obj.onabort = onabort;
  var onloadx = obj.onload;
  obj.onload = onload;

  /**
   * Handle `onerror`.
   */

  function onerror(error) {
    window.onanyerror.call(this, error);
    if (onerrorx) return onerrorx.apply(this, arguments);
  };

  /**
   * Handle `onabort`.
   */

  function onabort(error) {
    window.onanyerror.call(this, error);
    if (onabortx) return onabortx.apply(this, arguments);
  };

  /**
   * Handle `onload`.
   *
   * For images, you can get a 403 response error,
   * but this isn't triggered as a global on error.
   * This sort of standardizes it.
   *
   * "there is no way to get the HTTP status from a 
   * request made by an img tag in JavaScript."
   * @see https://mcmap.net/q/87834/-how-to-get-http-status-code-of-lt-img-gt-tags/8108646#8108646
   */

  function onload(request) {
    if (request.status && request.status >= 400) {
      window.onanyerror.call(this, request);
    }
    if (onloadx) return onloadx.apply(this, arguments);
  }
}

/**
 * Generic error handler.
 *
 * This shows the basic implementation, 
 * which you could override in your app.
 */

function onanyerrorx(entity) {
  var display = entity;

  // ajax request
  if (entity instanceof XMLHttpRequest) {
    // 400: http://example.com/image.png
    display = entity.status + ' ' + entity.responseURL;
  } else if (entity instanceof Event) {
    // global window events, or image events
    var target = entity.currentTarget;
    display = target;
  } else {
    // not sure if there are others
  }

  capture(entity);
  console.log('[onanyerror]', display, entity);
}

/**
 * Capture stuff for debugging purposes.
 *
 * Keep them in memory so you can reference them
 * in the chrome debugger as `onanyerror0` up to `onanyerror99`.
 */

function capture(entity) {
  captures.push(entity);
  if (captures.length > 100) captures.unshift();

  // keep the last ones around
  var i = captures.length;
  while (--i) {
    var x = captures[i];
    window['onanyerror' + i] = x;
  }
}

/**
 * Wait til next code execution cycle as fast as possible.
 */

function onnext(fn) {
  setTimeout(fn, 0);
}

})();

It could be used like this:

window.onanyerror = function(entity){
  console.log('some error', entity);
};

The full script has a default implementation that tries to print out a semi-readable "display" version of the entity/error that it receives. Can be used for inspiration for an app-specific error handler. The default implementation also keeps a reference to the last 100 error entities, so you can inspect them in the web console after they occur like:

window.onanyerror0
window.onanyerror1
...
window.onanyerror99

Note: This works by overriding methods on several browser/native constructors. This can have unintended side-effects. However, it has been useful to use during development, to figure out where errors are occurring, to send logs to services like NewRelic or Sentry during development so we can measure errors during development, and on staging so we can debug what is going on at a deeper level. It can then be turned off in production.

Hope this helps.

Recriminate answered 30/3, 2016 at 18:55 Comment(2)
Apparently images trigger the error event: https://mcmap.net/q/87835/-alter-the-prototype-of-an-image-tagShanly
This doesn't seem to be working for me for <script> tags that are added to the DOM using javascript. I haven't tried it in other situations.Detached
M
27

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;
}
Mazman answered 31/7, 2015 at 16:8 Comment(2)
What is yourserver.com/jserror ? REST, Web Service, Wcf Service ? Any simple about backend?Heap
If you want to send js errors from user browser to your server. you have to write your backend(http://yourserver.com) to receive and store. If you choose atatus.com, you dont need to do anything. Just include two lines of script in your page.Mazman
P
6
// display error messages for a page, but never more than 3 errors
window.onerror = function(msg, url, line) {
if (onerror.num++ < onerror.max) {
alert("ERROR: " + msg + "\n" + url + ":" + line);
return true;
}
}
onerror.max = 3;
onerror.num = 0;
Penninite answered 27/9, 2014 at 10:26 Comment(0)
D
6

If you want unified way to handle both uncaught errors and unhandled promise rejections you may have a look on uncaught library.

EDIT

<script type="text/javascript" src=".../uncaught/lib/index.js"></script>

<script type="text/javascript">
    uncaught.start();
    uncaught.addListener(function (error) {
        console.log('Uncaught error or rejection: ', error.message);
    });
</script>

It listens window.unhandledrejection in addition to window.onerror.

Dunghill answered 23/11, 2016 at 13:27 Comment(2)
Lance Pollard in the answer above mentioned a few errors, that normal onerror doesn’t handle. Does you library handle them? If some of them, please specify which?Admix
@MiFreidgeimSO-stopbeingevil Just checked the source code - no unfortunately it doesn't. Aleksandr Oleynikov: Would be awesome if you can support this - I'll then turn my downvote to an upvote ;-)Indigested
S
5

One should preserve the previously associated onerror callback as well

<script type="text/javascript">

(function() {
    var errorCallback = window.onerror;
    window.onerror = function () {
        // handle error condition
        errorCallback && errorCallback.apply(this, arguments);
    };
})();

</script>
Showker answered 30/5, 2016 at 13:19 Comment(0)
A
2

I would recommend giving Trackjs a try.

It's error logging as a service.

It's amazingly simple to set up. Just add one <script> line to each page and that's it. This also means it will be amazingly simple to remove if you decide you don't like it.

There are other services like Sentry (which is open-source if you can host your own server), but it doesn't do what Trackjs does. Trackjs records the user's interaction between their browser and your webserver so that you can actually trace the user steps that led to the error, as opposed to just a file and line number reference (and maybe stack trace).

Advection answered 15/1, 2015 at 1:52 Comment(4)
TrackJS doesn't seem to have a free tier anymore, though there is a free trial.Libbi
This is fine for tracking and being alerted to errors but doesn't really solve the handling part. Ideally I think the asker is looking for a way to handle these errors so that the rest of the code still runs.Unpretentious
What if you catch the error event and add an xhr call to the logger with the stack trace and the application state? How is trackjs better?Shanly
@Shanly it's more than a stack trace, which just tracks the beginning of the error. TrackJS will track the entire user session so you can see what led up to it. here's a screenshot as an example trackjs.com/assets/images/screenshot.pngAdvection

© 2022 - 2024 — McMap. All rights reserved.