AngularJS exception logging made simple
Exception logging is one of the most important, yet overlooked parts of application development. Developers often think, “If the application works, why spend extra time manually handling and logging exceptions? Besides, AngularJS provides a catch-all exception handler.”
Although choosing not to log seems like the easiest route, will you be notified if some component of the application breaks down? Will AngularJS catch the exception in every situation? Even if it does catch an exception, will the default message contain enough information to help debug the problem? The time required to analyze poor exception messages (or not having any messages at all) could very well take more time than setting up logging in the first place! This is one example of the importance of proper logging within your application.
Thanks to the simplicity of logging with AngularJS, you have no real reason not to log exceptions. There are two important considerations when it comes to exception logging:
- How to catch and log exceptions
- What specifically should be logged.
In this post, we will take a look at both.
The Default Catch-All Exception Handler
$exceptionHandler handles Angular expression exceptions that are not caught anywhere else within the application. By default, it will simply delegate uncaught exception messages to
$log.error. You can also send custom exception messages to
$exceptionHandler. You should do so if you cannot handle the exception directly where it occurred, and you want it to be handled using the default exception handler. Also, rather than using
$log.error to log exceptions within your application’s code, it is advisable to pass the messages to
$exceptionHandler instead, since it provides a more consistent location to handle exceptions. The following is an example of how to send a custom error message to the default exception handler.
$exceptionHandler("Cannot connect to database " + databaseName);
If you want the
$exceptionHandler to do more than simply delegate messages to
$log.error, you can decorate the exception handler to provide more functionality (without removing the original functionality). For example, let’s say you want to notify users via a pop-up in the UI for every exception that is passed to
$exceptionHandler. Here is an example of how to accomplish this.
We choose to decorate the
$exceptionHandler rather than overwrite it since we want to keep its default functionality of logging the message while at the same time adding the new feature. As seen in this example, we call
addExceptionAlert, which represents a custom method that will handle the exception in some way (you would need to create this method). We then call
$delegate which simply tells
$exceptionHandler to perform its default functionality now that we have completed our own custom functionality.
How to Catch and Log Exceptions
In this section we will consider the several ways to catch and log exceptions. It is important that your application handles each specific exception that might occur within the application only one time (wherever it is best handled). If the exception cannot be handled with enough detail at a specific point, or it makes more sense to handle it further up the stack, then you should ignore the exception wherever it directly occurred and instead handle the error further up the stack. For example, several similar exceptions should all be handled further up the stack in one location. When throwing custom exceptions, it is best practice to throw an object (i.e., “throw new Error(‘…’)”) rather than a string (i.e. “throw ‘…’”). Objects are easier to work with and contain additional information about the error that occurred. The following subsections describe several ways in which you might choose to handle exceptions. The examples below use promises with a
$http request; however, the same ideas apply to try-catch statements within your application’s code.
Catch but Don’t Log
If an exception occurs within the application, is it enough to just notify the user via the UI and ask him or her to try again? This is where many developers stop when it comes to error handling — they don’t actually log the exception or they log very little information. Not saving a trace of exceptions that occur sets the stage for issues in the future, since there is no way of being notified that a service failed, except seeing the error occur yourself. Or, you are left to wait for a user to let you know about the issue, which is clearly a bad idea. In the following example, the user is notified when an error occurs, but the error is not logged within the
$http request callback, which should be changed.
Catch and Log General Errors
Another method is to handle several possible types of exceptions all within the same error-handling function, and log a general error message for all of the exception types. However, are these general log messages really going to be helpful to debug how the exception occurred? For example, consider an
$http request. If you simply log a general message when an error occurs, you won’t know specifically what caused the error. Was the URL endpoint removed? Is the server down? Is there a permissions issue? Here is an example of what not to do.
As seen in the line
$exceptionHandler("An error has occurred.");, we are passing a generic message to the default exception handler, which is useless for helping to debug the specific exception that occurred. Note also the use of
$exceptionHandler to pass the exception up to be handled by the default exception handler. For more information, see the section entitled “Angular’s Catch-All Exception Handler.”
Catch and Log Specific Errors
The best way to log exceptions is to provide a specific log message for each possible exception. Always ensure that sufficient information is being logged and that nothing important is being excluded. This will be discussed in more detail later. Continuing with the
$http example, a more viable method for logging exceptions would be to log the HTTP status code and error message.
You can also create a catch-all
$http error response handler by configuring
$httpProvider similar to the following.
$httpProvider is configured to push errors to the created interceptor factory named
errorHttpInterceptor. This factory will send the error information to the default
$exceptionHandler. To implement this within your application, you need to include the
ErrorCatcher module into your application’s module. Here is an example.
var module = angular.module('app', ['ErrorCatcher']);
Don’t Log Too Little!
I’m sure that you’re convinced about the importance of logging exceptions that occur within your application. Now the question is, how much information should you log? Make sure that you include as much relevant information as possible, such as the trace and cause of the error. Excluding this information results in logs that may not be helpful to debug exceptions. Also, if you don’t know when an exception occurred, analyzing the problem can be much tougher. Did the error occur yesterday? Was it just a few seconds ago? Thankfully, AngularJS contains a feature called a decorator that allows you to customize the functionality of an existing service without overriding its original behavior. In the case of logging, you can extend
$log’s existing functionality by prepending the current date and time to log entries. The following example will modify each call to
$log.error to include the current date and time. Keep in mind that the default
$exceptionHandler delegates messages to
$log.error, so this will also affect the default exception messages that are logged.
(Example adapted from http://jsfiddle.net/jccrosby/d7P5C/light/)
AngularJS simplifies the process of logging exceptions within your application by providing a highly customizable exception handler as well as other useful logging utilities. Now that you have learned how to catch and log errors (and how not to) as well as what information should be logged, the next step is choosing how to store the logs, rather than simply having them output to the browser console. For an example of how to send your logs to Loggly, please visit https://www.loggly.com/docs/angular-js-logs/