View Full Version : Exception Handling class
harley.333
16 Oct 2008, 6:10 AM
We've written the following error handling class, and it works pretty well.
I'm submitting it here for anyone to play with. I'm hoping the you guys can help me catch more types of errors :)
For example, I'm not catching image tags that throw errors.
To use the class:
ErrorHandler.init();
// the following is a global, singleton class
ErrorHandler = function() {
return {
init: function() {
window.onerror = !window.onerror ? ErrorHandler.handleError : window.onerror.createSequence(ErrorHandler.handleError);
},
getFormattedMessage: function(args) {
var lines = ["The following error has occured:"];
if (args[0] instanceof Error) { // Error object thrown in try...catch
var err = args[0];
lines[lines.length] = "Message: (" + err.name + ") " + err.message;
lines[lines.length] = "Error number: " + (err.number & 0xFFFF); //Apply binary arithmetic for IE number, firefox returns message string in element array element 0
lines[lines.length] = "Description: " + err.description;
} else if ((args.length == 3) && (typeof(args[2]) == "number")) { // Check the signature for a match with an unhandled exception
lines[lines.length] = "Message: " + args[0];
lines[lines.length] = "URL: " + args[1];
lines[lines.length] = "Line Number: " + args[2];
} else {
lines = ["An unknown error has occured."]; // purposely rebuild lines
lines[lines.length] = "The following information may be useful:"
for (var x = 0; x < args.length; x++) {
lines[lines.length] = Ext.encode(args[x]);
}
}
return lines.join("\n");
},
displayError: function(args) {
// purposely creating a new window for each exception (to handle concurrent exceptions)
var errWindow = new Ext.Window({
autoScroll: true,
bodyStyle: {padding: 5},
height: 150,
html: this.getFormattedMessage(args).replace(/\n/g, "<br />").replace(/\t/g, " "),
modal: true,
title: "An error has occurred",
width: 400
});
errWindow.show();
},
logToServer: function(args) {
Ext.Ajax.request({
params: {
a: "PostErrorInfo",
error: Ext.encode(args)
},
url: "Default.aspx"
});
},
handleError: function() {
var args = [];
for (var x = 0; x < arguments.length; x++) {
args[x] = arguments[x];
}
try {
this.displayError(args);
this.logToServer(args);
} catch(e) {
// if the errorHandler is broken, let the user see the browser's error handler
return false;
}
return true;
}
};
}();
// the following line ensures that the handleError method always executes in the scope of ErrorHandler
ErrorHandler.handleError = ErrorHandler.handleError.createDelegate(ErrorHandler);
harley.333
16 Oct 2008, 6:16 AM
Here are the test-cases I've been using. You'll notice that proper try...catch style code has better results.
// ErrorHandler-Test1 = success
var test = null;
test.arg = 5;
// ErrorHandler-Test2 = success
throw (new Error("Hello"));
// ErrorHandler-Test3 = success (however, thrown information was not retained)
throw "Hello again";
// ErrorHandler-Test4 = success (however, thrown information was not retained)
throw {
myMessage: "stuff",
customProperty: 5,
anArray: [1, 2, 3]
};
// ErrorHandler-Test5 = success
try {
var test2 = null;
test2.arg = 5;
} catch(e) {
ErrorHandler.handleError(e);
}
// ErrorHandler-Test6 = success
try {
throw (new Error("Goodbye"));
} catch(e) {
ErrorHandler.handleError(e);
}
// ErrorHandler-Test7 = success
try {
throw "Goodbye again";
} catch(e) {
ErrorHandler.handleError(e);
}
// ErrorHandler-Test8 = success
try {
throw {
myMessage: "stuff",
customProperty: 5,
anArray: [1, 2, 3]
};
} catch(e) {
ErrorHandler.handleError(e);
}
Like I said earilier, the following will show the alert, but it won't bubble up to my ErrorHandler:
<img src="fake.gif" onerror="alert(0);" />
loeppky
17 Oct 2008, 11:45 AM
harley.333: very nice work. I like your design, and good stuff with the test cases.
I was wondering if there was any way to extract more information from the error that occurred?
harley.333
22 May 2009, 6:58 AM
@nake1: Sorry I missed your remark (I've been in server-side land for the past few months).
I've improved upon and formalized this error handling class.
http://extjs-ux.org/docs/index.html?class=Ext.ux.ErrorHandler
I'm disappointed that Ext 3.0 doesn't have a class like this. Exception Handling should be a top priority for all developers.
If you look at the docs for the "error" event, you'll see all the information I am capable of gathering from the error that occurred. Let me know if there's something else that you'd like to see.
mjlecomte
22 May 2009, 7:11 AM
Did you notice the Ext.Error class in Ext 3? I doubt as robust as your version.
If you'd like to improve upon that class I might suggest creating a new thread with that title, perhaps suggestions would be folded into it?
mschwartz
22 May 2009, 9:02 AM
What Ext.Error class in 3.0?
I grep through the sources and 'Ext.Error' is not found.
mjlecomte
22 May 2009, 9:45 AM
Looks like it's still svn only.
harley.333
22 May 2009, 3:51 PM
@mjlecomte: No, I haven't seen the Ext.Error class (I don't have SVN access). But Jack and the boys are welcome to steal it and make it their own. :)
christocracy
25 May 2009, 10:55 AM
Hey, Harley. I put a class called Ext.Error into SVN. It's pretty simplistic currently and doesn't really have a home yet (it currently resides in data/Api.js in SVN).
I extend it in certain important widget class like Store and DataProxy.
Eg:
Ext.data.Store.Error = Ext.extend(Ext.Error, {...});
Ext.data.DataProxy.Error = Ext.extend(Ext.Error, {...});
/**
* @class Ext.Error
* @extends Object
* <p>The Ext.Error class wraps the native Javascript Error class similar to how Ext.Element wraps a DomNode.
* To display an error to the client call the {@link #toConsole} method which will check for the
* existence of Firebug.</p>
*
* TODO: Move to Ext.js?
*
<code><pre>
try {
generateError({
foo: 'bar'
});
}
catch (e) {
e.toConsole();
}
function generateError(data) {
throw new Ext.Error('foo-error', 'Foo.js', data);
}
</pre></code>
* @param {String} id A simple label for the error for lookup.
* @param {String} file The file where the error occurred.
* @param {Mixed} data context-data.
*/
Ext.Error = function(id, file, data) {
this.message = this.render.apply(this, arguments);
this.error = new Error(this.message, file);
this.error.name = this.cls;
this.id = id;
}
Ext.Error.prototype = {
/**
* The ClassName of this Error.
* @property cls
* @type String
*/
cls: 'Ext.Error',
/**
* The id of the error.
* @property id
* @type String
*/
id : undefined,
/**
* Abstract method to render error message. All Error extensions should override this method.
*/
render : function(id, file, data) {
return this.cls + ' ' + id;
},
/**
* Attempts to output the exception info on FireBug console if exists.
*/
toConsole : function() {
if (typeof(console) == 'object' && typeof(console.error) == 'function') {
console.error(this.error);
}
else {
alert("Error: " + this.cls + ' ' + this.message); // <-- ugh. fix this before official release.
}
},
/**
* toString
*/
toString : function() {
return this.error.toString();
}
};
/**
* Error class for Ext.data.Api errors.
*/
Ext.data.Api.Error = Ext.extend(Ext.Error, {
cls: 'Ext.data.Api',
render : function(name, file, data) {
switch (name) {
case 'action-url-undefined':
return 'No fallback url defined for action "' + data + '". When defining a DataProxy api, please be sure to define an url for each CRUD action in Ext.data.Api.actions or define a default url in addition to your api-configuration.';
case 'invalid':
// make sure data is an array so we can call join on it.
data = (!Ext.isArray(data)) ? [data] : data;
return 'received an invalid API-configuration "' + data.join(', ') + '". Please ensure your proxy API-configuration contains only the actions defined in Ext.data.Api.actions';
case 'invalid-url':
return 'Invalid url "' + data + '". Please review your proxy configuration.';
case 'execute':
return 'Attempted to execute an unknown action "' + data + '". Valid API actions are defined in Ext.data.Api.actions"';
default:
return 'Unknown Error "' + name + '"';
}
}
});
harley.333
26 May 2009, 8:02 AM
Hey Chris, I think this type of class (a generic and simple Error class) is sorely needed in ALL modern Javascript frameworks. And I applaud Ext JS for giving it a serious attempt. I see my ErrorHandler class being used as a separate mechanism. And I like keeping the Errors and the ErrorHandler separated.
However, why aren't you inheriting from the browser's Error class? Granted, the built-in Error class offers very little; but, that's what makes it a great base-class. Plus, there are already a handful of useful subclasses (see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error). I don't see the benefit for an Ext.Error class which wraps (instead of inheriting from) an Error.
Within my ErrorHandler.error event, I provide a custom object which wraps the Error. But I do this to provide a consistent cross-browser API to all type of Error objects (custom, unhandled, whatever).
I think Ext.Error and Ext.ux.ErrorHandler should be used together. Something like:
Ext.ux.ErrorHandler.init(); // to take advantage of window.onerror (if available)
Ext.ux.ErrorHandler.on("error", function(ex) {
// a simple handler to use the console
if (ex.raw instanceof Ext.Error && ex.raw.toConsole) {
ex.raw.toConsole();
} else {
if (typeof(console) == "object" && typeof(console.error) == "function") {
console.error(this.error);
} else {
alert("(" + this.name + ") " + this.message);
}
}
});
try {
generateError({
foo: "bar"
});
} catch (ex) {
Ext.ux.ErrorHandler.handleError(ex);
}
function generateError(data) {
throw new Ext.Error("foo-error", "Foo.js", data);
}
If you inherit from Error, you can remove the "id" property (just use "name" instead) and the "toString" method. Personally, I'd also remove the "render" and the "toConsole" methods. Both of these methods pertain to how to handle the error, which really isn't the error's problem. I think error objects should be super-simple with almost no methods. Also, I don't think the "message" property should change based on "data" (as seen in your Ext.data.Api.Error example). In fact, I don't think a generic "data" property should be used at all. If a sub-class of Error needs a "data" property, that sub-class should provide it. And the ErrorHandler can display that property as deemed necessary.
christocracy
26 May 2009, 10:07 AM
I see my ErrorHandler class being used as a separate mechanism. And I like keeping the Errors and the ErrorHandler separated.
Yes, I've thought about implementing something like your ErrorHandler class a bit.
However, why aren't you inheriting from the browser's Error class?
Have you tried extending the Error class? I experimented with a number of times in the past -- there's something wonky about it that I haven't found the answer for.
Try running this simple snippet yourself:
// Define a custom Error extension. Seems natural enough...
var MyError = Ext.extend(Error, {});
try {
throw new MyError("MyError");
} catch(e) {
console.dir(e);
}
try {
throw new Error('Native Error')
} catch(e) {
console.dir(e);
}
Notice that the extension loses the ability to automatically tag the properties fileName, lineNumber, name and message. stack is completely missing.
Let me know if you can figure out how to make that work.
christocracy
26 May 2009, 10:47 AM
I see YAHOO's extending Error in yui:
/**
* AssertionError is thrown whenever an assertion fails. It provides methods
* to more easily get at error information and also provides a base class
* from which more specific assertion errors can be derived.
*
* @param {String} message The message to display when the error occurs.
* @namespace YAHOO.util
* @class AssertionError
* @extends Error
* @constructor
*/
YAHOO.util.AssertionError = function (message /*:String*/){
//call superclass
arguments.callee.superclass.constructor.call(this, message);
/*
* Error message. Must be duplicated to ensure browser receives it.
* @type String
* @property message
*/
this.message /*:String*/ = message;
/**
* The name of the error that occurred.
* @type String
* @property name
*/
this.name /*:String*/ = "AssertionError";
};
//inherit methods
YAHOO.lang.extend(YAHOO.util.AssertionError, Error, {
/**
* Returns a fully formatted error for an assertion failure. This should
* be overridden by all subclasses to provide specific information.
* @method getMessage
* @return {String} A string describing the error.
*/
getMessage : function () /*:String*/ {
return this.message;
},
/**
* Returns a string representation of the error.
* @method toString
* @return {String} A string representation of the error.
*/
toString : function () /*:String*/ {
return this.name + ": " + this.getMessage();
},
/**
* Returns a primitive value version of the error. Same as toString().
* @method valueOf
* @return {String} A primitive value version of the error.
*/
valueOf : function () /*:String*/ {
return this.toString();
}
});
christocracy
26 May 2009, 11:40 AM
Ok, I've finally found the solution. So simple.
http://www.nczonline.net/blog/2009/03/10/the-art-of-throwing-javascript-errors-part-2/
var BaseError = function(message) {
this.message = "BaseError says: " + message;
}
BaseError.prototype = new Error();
var CustomError = Ext.extend(BaseError, {
name: 'CustomError'
});
christocracy
26 May 2009, 12:23 PM
I think its a good idea to be able to localize the Error messages for different languages, rather than hard-coding long error-sentences in your code.
/**
* Error language, i18n-able
*/
Ext.ns('lang', 'lang.error');
lang.error = {
CustomError : {
'foo-error' : 'CustomError Foo',
'bar-error' : 'CustomError Bar'
},
BaseError : {
'foo-error' : 'BaseError Foo',
'bar-error' : 'BaseError Bar'
}
};
/**
* BaseError
*/
var BaseError = function(message) {
this.message = lang.error[this.name][message];
}
BaseError.prototype = new Error();
Ext.apply(BaseError.prototype, {
name: 'BaseError',
getMessage : function() {
return this.message;
}
});
/**
* CustomError
*/
var CustomError = function(message, obj) {
this.obj = obj; // <-- custom context-object
BaseError.call(this, message); // <-- calling super
}
Ext.extend(CustomError, BaseError, {
name: 'CustomError'
});
try {
throw new BaseError("foo-error");
} catch(e) {
console.error(e);
}
try {
throw new BaseError("bar-error");
} catch(e) {
console.error(e);
}
try {
throw new CustomError("foo-error", {foo: 'foo'});
} catch (e) {
console.error(e);
}
try {
throw new CustomError("bar-error", {foo: 'bar'});
} catch (e) {
console.error(e);
}
harley.333
26 May 2009, 12:36 PM
Very cool. I've been struggling with this for about an hour. However, I don't understand why the following doesn't work:
BaseError = Ext.extend(Error, {
constructor: function(message) {
BaseError.superclass.constructor.apply(this, arguments);
this.message = message;
}
});
harley.333
26 May 2009, 12:51 PM
As far as localization goes, we're talking about programatical exceptions here. These errors shouldn't be exposed to end-users (in my opinion). These errors are just for developers. With that said, I don't see alot of value added by localizing the messages.
However, you've made it so brain-dead simple, I can't really argue against it either :)
harley.333
26 May 2009, 1:04 PM
BTW, do you know of a way to read from the browser's console? Specifically, I'm thinking of of emulating window.onerror for those browser's that don't have it.
For example, Google Chrome throws errors silently, but those errors are written to the console. If I had access to the console, I could look for new messages and handle them.
What do you think?
christocracy
26 May 2009, 2:03 PM
However, why aren't you inheriting from the browser's Error class? Granted, the built-in Error class offers very little;
I disagree the built-in Error class offers very little. It automates the process of gathering lineNumber, fileName and stack-trace.
christocracy
26 May 2009, 2:12 PM
BTW, do you know of a way to read from the browser's console?
I don't, sorry.
christocracy
26 May 2009, 2:34 PM
As far as localization goes, we're talking about programatical exceptions here. These errors shouldn't be exposed to end-users (in my opinion). These errors are just for developers. With that said, I don't see alot of value added by localizing the messages.
It's not just about localization, it's about documentation as well. Collecting the language together allows for the possibility of being parsed into the docs.
Helps keep the code-base clean too.
christocracy
28 May 2009, 8:07 AM
@Harley: I've had a good look at your Ext.ux.ErrorHandler. It's lean and clean.
You've done alot of groundwork testing it out in different browsers and I'd rather not re-invent that, of course :) What do you think having it live inside the framework?
I'd like to call it "Ext.ErrorMgr"
mschwartz
28 May 2009, 8:42 AM
Not sure you want to throw up alert dialogs with cryptic messages in a production environment. IMO, that should be a config option of some kind.
mystix
28 May 2009, 8:45 AM
Not sure you want to throw up alert dialogs with cryptic messages in a production environment. IMO, that should be a config option of some kind.
agreed. i would think that would go into the same package as debug.js, so no issues there where jsbuilder is concerned.
christocracy
28 May 2009, 9:07 AM
In a production env, I'm sure the ErrorMgr could be configured to send exceptions to the server via Ajax request.
mystix
28 May 2009, 10:18 AM
In a production env, I'm sure the ErrorMgr could be configured to send exceptions to the server via Ajax request.
Ooooh... *lightbulb*
Something like this?
http://damnit.jupiterit.com
christocracy
28 May 2009, 10:31 AM
Sure, something like that could be done.
With Harley's Ext.ux.ErrorHandler, one might do something like this:
Ext.ux.ErrorHandler.on("error", function(ex) {
if (App.debug === true) {
// alert the user with a friendly message
Ext.Msg.alert("Exception: " + ex.name, ex.toString());
} else {
// send information to server for logging or to email to the developer
Ext.Ajax.request({
url: '/errors',
params: ex.toHash()
});
}
});
mystix
28 May 2009, 10:37 AM
i say +1 to that =D>
mschwartz
28 May 2009, 11:04 AM
Sure, something like that could be done.
With Harley's Ext.ux.ErrorHandler, one might do something like this:
Ext.ux.ErrorHandler.on("error", function(ex) {
if (App.debug === true) {
// alert the user with a friendly message
Ext.Msg.alert("Exception: " + ex.name, ex.toString());
} else {
// send information to server for logging or to email to the developer
Ext.Ajax.request({
url: '/errors',
params: ex.toHash()
});
}
});
The server side of that is:
<?php
mailto('support@extjs.com', "I don't have a bug, you do!");
christocracy
28 May 2009, 11:10 AM
Yea, and if you set a nice big timeout for the request, maybe we can *actually* fix the bug and send back a revised function for you Ext.apply onto some class :)
harley.333
28 May 2009, 5:10 PM
@chris:
You are welcome to the code and rename it whatever you like. I will say, however, at this point in time, there are NO ALERTs IN MY CODE :)
Feel free to change the code to fit your needs as well. Also, I request that this class (and Ext.Error) be put into Ext Core. That's totally your call, but I consider Exception Handling to be fundamental to any type of code.
christocracy
28 May 2009, 5:38 PM
Thanks very much, Harley.
Another option is that the framework just exposes a hook to plug *any* Exception-handler into it.
Eg:
Ext.registerErrorHandler(Ext.ux.ErrorHandler);
harley.333
28 May 2009, 6:25 PM
I'm not sure the best way to handle this :)
I don't really like the idea of "registerErrorHandler." Can you register more than one errorHandler? If so, can you later remove them? If only one is allowed, does the first handler registered win, or the last? Also, what is the default behavior if I don't register a handler (Ext.emptyFn would result in all exceptions being ignored)?
My original intention was to have a simple, global solution to catch all exceptions. I figured a handler based on Ext.util.Observable offered the best solution. Using Observable, multiple handlers could be added to listen for specific errors:
Ext.ErrorMgr.on("error", function(ex) {
if (ex instanceof Ext.ux.MissingAppletException) {
// alert user to install the missing Applet
}
});
...
Ext.ErrorMgr.on("error", function(ex) {
if (ex instanceof Ext.ux.BeerLiquorException) {
// you can't drink beer first!
}
});
Also, the handlers could be removed if necessary (proper clean-up and all that). Plus, you get all the addListener bonuses for free (defer, single, etc).
Perhaps add a mechanism to prevent the exception from bubbling up to other handlers would be necessary? (I don't really like this idea, but I guess I don't have to use it.)
Ext.ErrorMgr.on("error", function(ex) {
if (ex instanceof MyException) {
return false; // this is MyException, nobody else can have it!
}
});
Or, perhaps add a mechanism for listening to certain types of exceptions:
Ext.ErrorMgr.onErrors([array of Ext.Errors], handler);
// I'm not sure how you'd remove this handler.
neofraktal
3 Feb 2011, 3:12 AM
Hi,
When will be implemented the error handler?
Will be implemented in the next version of ExtJS 4?
Thanks
NatVik
19 Aug 2011, 4:54 AM
Hi! it is really necessary thing. Is there something like this in Ext Js 4?
harley.333
28 Sep 2011, 6:37 PM
Attached is my version updated for Ext4.
Here's the code I use to actually use the ErrorHandler:
function setupErrorHandlers() { //1. Setup Global Error Handler
UX.ErrorHandler.init();
UX.ErrorHandler.on("error", function (err) {
// build some user-friendly text
var msg, lines = ["The following error has occurred :"];
lines[lines.length] = "Message: (" + err.name + ") " + err.message;
if (err.url) {
lines[lines.length] = "URL: " + err.url;
}
if (err.lineNumber) {
lines[lines.length] = "Line Number: " + err.lineNumber;
}
msg = lines.join("<br />").replace(/\t/g, " ");
// display the text to the user
// we're purposely creating a new window for each exception (to handle concurrent exceptions)
var errWindow = new Ext.Window({
autoScroll: true,
bodyStyle: { padding: 5 },
height: 200,
html: msg,
modal: true,
title: "An error has occurred",
width: 500
});
errWindow.show();
// log to the server
Ext.Ajax.request({
params: {
operation: "PostErrorInfo",
controller: "Default",
error: Ext.encode(err)
},
url: "ControllerFactory.aspx"
});
});
//2. Setup Ext Error Handler
Ext.Error.handle = UX.ErrorHandler.handleError;
//3. Setup Server-Connection Error Handler
Ext.util.Observable.observe(Ext.data.Connection, {
requestcomplete: function (conn, response, options) {
if (options.isUpload && response.responseText.indexOf("{errors:") == 0) {
handleServerException(response, options);
}
},
requestexception: function (conn, response, options) {
handleServerException(response, options);
}
});
}
evaldasw
17 Nov 2011, 11:36 PM
Hi, is there Ext3 latest version? Because the one on first page doesn't have the "error" event and can't find the src anywhere. Thanks!
Powered by vBulletin® Version 4.1.5 Copyright © 2012 vBulletin Solutions, Inc. All rights reserved.