This post is going to cover logging and how to implement logging in an Angular app. The implementation of this is based on how I implemented continuous delivery for a client.
The purpose of logging is to get detailed information of how the system is running, both runtime information of how the system has been used, but also in case of errors for easy error detection and error handling. I have often seen proper front-end logging being neglected in front-end applications because the team felt that logging the backend was sufficient. What a shame! Not having logging in the front-end give you huge blind spots in your application awareness as you are not logging the UI that the client is actually interacting with. That means if the application is failing and not contacting the back-end, you would never know anything has gone wrong. Yirks!
Logging vs Monitoring
Logging and monitoring are often used interchangeably, but even though one could argue they have some overlap, they serve different purposes.
Logging is about being aware of certain events in the system, such as errors and specific actions where monitoring is about continuously checking the health of the system. If a server is running out of resources or is throwing errors on the OS level, you might not notice this with logging but only with monitoring of the application.
What to log?
In general, you want to log specific events in the system and errors, such as HTTP requests and exceptions.
Often you want to hook in logging to all HTTP request and log request times and payloads, as these give you important metrics about how long your users are waiting for requests. Also, you want to be aware of when your users are experiencing errors and log these as well, including information about actions and application state that caused the error, for easier error tracing.
The ELK stack
A common technology stack for logging is the ELK stack consisting of ElasticSearch, Logstash, and Kibana. ElasticSearch is for storing and querying logs, Logstash is for processing logs before they are stored in ElasticSearch and Kibana is the UI interface for querying logs.
Implementing logging in an Angular app
Let’s go through how to implement logging in your Angular Application. We are going to start with creating a logging service that can log to ElasticSearch and then hook it into global error handling and HTTP requests. This is implemented on my logging demo app.
Create logging service
The first step is to implement a logging service that is able to send logs to ElasticSearch.
For actually performing the logging we are going to create an Angular agnostic ts class for logging called Logger:
This logger class got a method log for logging a message. A buffer using debounceTime is minimizing the amount of logs send to elasticSearch by only sending logs if log hasn’t been called in 5 seconds. Here it will flush the buffer, create the log payload and combine the buffer using the reduce operator.
After this we are just doing a good old XMLHttpRequest sending the log payload to the log endpoint. This endpoint should be set in the environment file.
This class is used in LogService which is wrapping the logger class for doing the actual logging in the Angular app:
This service contains three ways to log: logHttpInfo, logError and logInfo. Feel free to add more log levels like debug and warning but these are the log levels we are using in this demo.
Global error handler logging
Now that we have a service that can log to ElasticSearch, let’s set it up to log errors by hooking it into Angular’s global error handler provider.
First, we create a service called global error handler:
This will catch errors, create an error string with stack trace, log using the logging service and rethrow the error so it shows in the console.
Note how it is logging as warning or error depending on if error is comming from the application code (it contains /src/app/) and
a sentencesForWarningLogging string array, a list of sentences that will make the error be logged as warning if they are included in the error message.
It is hooked into the app with:
HTTP request logging
When handling HTTP calls in Angular I recommend using an adapter pattern. This means that instead of working with Angular’s HttpClient overall in your code base you wrap the Angular HttpClient in your own HttpClient adapter. From here you can apply custom logic when doing logging, such as log information about the HTTP requests and handle HTTP status codes and your application will use the same HTTP client interface regardless if Angular changes its HttpClient (like it did when Angular went from doing http calls with Http to HttpClient).
In this post, we learned why logging is important in the front-end and how it serves a different purpose than monitoring. The also saw how to setup logging in your Angular app by creating a logging service, that can log to ElasticSearch (or what you use for storing logs) and how to hook this service into global error handling and HTTP requests.