Now that you’ve launched your new killer Web 2.0 website, how do you detect errors in your deployed Javascript?
Using onerror
The standard approach is to hook into the Window object’s onerror event handler and use Ajax to log your requests to the server. There are some good tutorials on the web on how to do this, but here is one approach:
// Register global error handler window.onerror = function(message, uri, line) { var fullMessage = message + "\n at " + uri + ": " + line remoteLogger.log(fullMessage) // Let the browser take it from here return false }
Notice that you have to explicity set window.onerror using old-style event handlers, attachEvent and addEventListener don’t seem to do the trick. Also, Internet Explorer always stupidly sets the URI parameter to the URI of the current page, not the file that caused the problem. Thus you have to hunt through all the linked Javascript files looking at the specified line number and make a best guess as to which file caused the problem.
Getting a Stack Trace
Often times knowing where an error occurred is not enough – what you really need is the full stack trace. If your user has a Gecko based browser (eg, Firefox), you’re in luck.
Notice the stack parameter in the code above? Gecko has a poorly documented stack property on the global Error object that provides just what we need.
Let’s say you have code that looks like this:
function first() { second() } second = function() { blowUp() } function blowUp() { try { foo.bar() } catch (e) { handleException(e) } }
If you call the first method, say from onload like in this example, the resulting stack trace is:
blowUp()@file:///C:/temp/error.js:17 ()@file:///C:/temp/error.js:10 first()@file:///C:/temp/error.js: onload([object Event])@file:///C:/temp/error.html:1 @:0
Each line has the following format:
<method_name>(<arguments>)@<uri>:<line_number>
Notice that the method_name will be blank for anonymous functions, which are quite common if you follow the prototype javascript style.
A Simple Logger
Now let’s take a look at some code. First, here is a simple logger that posts errors to a server via Ajax (note this code assumes your are using the prototype library):
RemoteLogger = function RemoteLogger() { this.logging = false } Object.extend(RemoteLogger.prototype, { log: function(message, stack) { if (!this.logging) { this.logging = true var parameters = 'resource=error' var postBody = 'error=' + encodeURIComponent(message) + '&stack=' + encodeURIComponent(stack) var requestOptions = { method: 'post', parameters: parameters, postBody: postBody, onSuccess: function(transport) { this.logging = false }.bind(this) } new Ajax.Request('/logger', requestOptions) } } })
Handling Exceptions
Next, let’s define the handleException method we used above. This method extracts out useful information from an Error object and uses the logger above to post the results to a server.
function handleException(exception) { /* In FF exception can be a string if it happens when opening the xmlHttpRequest. Gah! */ if (typeof exception == 'string') exception = new Error(exception) /* If a xmlhttp request is happening in Mozilla and the user navigates to another page, then when the first request returns a NS_ERROR_NOT_AVAILABLE error will be thrown. So just ignore it. */ if (exception.name == 'NS_ERROR_NOT_AVAILABLE') return var fullMessage = '' var uri = '' var stack = '' var line = '' try { /* Don't use exception.toString since the JS spec does not require it to provide the error name or message (haven't tested to see if it matters though across browsers) */ fullMessage = exception.name + ': ' + exception.message uri = exception.fileName stack = exception.stack // Firefox sometimes blows up here line = exception.lineNumber } catch (e) { } fullMessage += "\n at " + uri + ": " + line console.info(fullMessage) console.info(stack) remoteLogger.log(fullMessage, stack) }
Annoying Gotchas
There are few annoying gotchas to know about:
- Stack traces are only available from Error objects thrown by exceptions and thus are not available from the onerror method.
- Uncaught exceptions are not hanlded by onerror.
- There is no global exception handler in Javascript, so you have to be very careful in the way you right your code. On the positive side, its easy to implement a global error handler for methods that are invoked due to the results of an Ajax request. On the other hand, its much harder to do this for methods invoked due to normal events generated by a user interacting with the browser.
Anyway, the more information you can get about browser errors the better – you’ll often be surprised by the results!