Angular 2+ exception handling made simple with logging
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, Angular provides a catch-all exception handler.” Angular exception handling does identify new errors, and you should log them.
Although choosing not to log seems like the easiest route, will you be notified if some component of the application breaks down? Will Angular 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 relative simplicity of logging with Angular and existing open source modules and libraries, 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.
Default catch-all Angular exception handling
Angular has a built-in
ErrorHandler class as part of @angular/core TypeScript module that handles exceptions that are not caught anywhere else within the application. By default, it is not enabled and set up in your Angular app (a bit odd, since the first version of AngularJS had a ready-to-use $log), so you need to import it and set it up in the providers array of your @NgModule decorator. 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 the default
ErrorHandler (output to console) within your application’s code, it is advisable to create your own global error handler class, since it provides a more consistent and flexible location to handle exceptions. The following is an example of how to set up a global, catch-all error handler.
If you want the
ErrorHandler to do more than simply pass messages to the console, you can extend the
ErrorHandler to provide more functionality (without removing the original functionality if you so choose). For example, let’s say you want to notify users via a pop-up in the UI for every exception that is passed to
ErrorHandler. Here is an example of how to accomplish this.
We choose to extend the
ErrorHandler by a custom class called super.handleError(error) since we want to keep its default functionality of logging the message to the console. As seen in this example, we call handleError, which in turn calls
alert. Ideally this would be a more elaborate component for user notification but we use it here for example brevity and simplicity.
How to catch Angular errors and log exceptions
In this section we will consider the several ways to address Angular exception handling. 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. The following subsections describe several ways in which you might choose to handle exceptions.
Catch but don’t log
If an exception occurs within the application, is it enough to just notify the user via the user interface and ask him or her to try again? This is where many developers stop when it comes to Angular error handling—they don’t actually log and store the exception or they log very little information, often not enough to understand the occurring problem and its environment. Not saving a stack 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 previous example, the user is notified when an error occurs via alert, but the error is not logged anywhere, which is quite useless, provides bad user experience, and should be changed.
Catch and log general Angular errors
Another method is to handle several possible types of errors all within the same global error handler 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 via Angular’s HttpClient. If you simply log a general message when an error occurs in the catch handler, you won’t know specifically what caused the error. Was the URL endpoint removed? Is the server down? Is there a permission issue? Here is an example of how to handle an error further up the stack where it occurs and where you can log a lot of relevant contextual data. Use Angular’s HttpErrorResponse to do that.
Note also that you can use a global
ErrorHandler to handle the exception, but you must make sure that you rethrow or pack a non-error object to Error in your Observables or promises. For example, HttpErrorResponse is not an instance of Error; therefore, you must pack it in error and rethrow it if you wish to handle it in the global error handler. Otherwise it might pass unnoticed.
Be aware that aside from Angular, other exceptions might occur in your application, for instance, from a third-party library or some other source. It’s a good practice to also listen for those exceptions and log them for further analyses. You can use the window.onerror and window.onunhandledrejection (if you or your libraries use Promises) to hook into these, gather the information required, and log it.
Catch and log specific Angular 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.
A more viable method of handling a specific error is to either create your own custom error classes that extend the built-in
Error class and make them contain properties and messages that you would like to log or use the
instanceof operator to check for specific error classes and do logging according to your logging needs.
The Angular approach to hold this logic and do any exception data transformation before logging (end perhaps even the logging itself) is via a dedicated service. The service needs to be registered, and then it can be used inside the global
ErrorHandler. Here is an example of such a service that checks errors and based on their types, adjusts the data being logged.
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 to use best practices and 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, Angular architecture is flexible enough to allow you to give that information simply by adjusting the code in your global error handler or in the logging service if you use one. Let’s update our service from the previous example to contain date information.
Logging to an external log provider
Just logging to console is rarely enough to satisfy any needs of a modern Angular-powered application. Often we need a good aggregator of logs and an easy UI to inspect it to draw some helpful conclusions about the exceptions your application’s users experienced. Here is a concrete example that shows how you can utilize an external provider like Loggly to store your exceptions logs for easy analyses and inspection.
Note that you should protect any tokens or other security information that is easily obtainable in fronted applications.
Angular exception handling simplifies the process of logging exceptions within your application by providing a highly customizable error handler provider, as well as other useful logging utilities. It is also easy to use and extend using the power of TypeScript and rich environment of helpful libraries. Now that you have learned how to catch, log, and store errors (and how not to), as well as Angular error handling best practices, the next step is choosing what information to store to make the most of the robust architecture that Angular provides. Let me know in the comments if you had fun reading this article, found it helpful, or have some Angular logging “war” stories to share.