LOGGING IN ASP.NET CORE MVC
USE THE LOGGING SERVICE
The logging service, which as usual we receive in the constructor of one of our components, allows us to write a line of text with parameters when events occur in our application that we find interesting. We decide what where and when to write in this log, information that will be useful when the application is put into the production server. The Logging service relies on providers to output the rows written in the log, if the provider supports Structured Logging we can filter the rows based on placeholders.
Let’s look at another class the ILoggerFactory. The CreateLogger method always creates an ILogger.
These are the various levels of Logging.
If we set in the appsettings.json file the level to Warning only those types of messages will be logged.
If I now set the environment to Production the two information type Log messages put in the code I should not see them, while setting the environment to Development I see them because I have this configuration in the appsettings.Development.json file.
CAPTURE UNHANDLED EXCEPTIONS AND DISPLAY RELEVANT ERROR MESSAGES
Sooner or later, our application will fail, either because the database is unreachable, or we introduced a bug that we did not discover, or the user provided incorrect input. In all these situations, a so-called unhandled exception could occur, that is, an error that is not caught by our code in a try catch block, and the user would get an error page full of technicalities not knowing whether he or our application was wrong. We can avoid this by configuring a Middleware in the Startup.cs class.
If we are in Production we use a new Middleware.
Suppose the user got the URL wrong and a /Courses/Detail/5000 request comes in. The request runs through all Middleware until the exception is raised. At this point ASP.NET Core instead of getting a blank page with a generic http 500 error makes a second request, this time stating in the URL /Error as configured by us and adding detailed information about the error. Request that this time comes to the user. With /Error we denote a Controller with its Action Index.
LOGGING SERVICE SUMMARY
Even if our application has been properly tested, unexpected situations may still occur when we go to put it into production. For example, an undiscovered bug or transient database connection problems will prevent the application from functioning normally.
It is important to keep a track of these occurrences and what occurred in the moments immediately before, so that we can diagnose the problem more easily and come up with countermeasures to prevent it from happening again in the future.
So, even after the development of an application has been “completed,” we still have a duty to study its behavior, identify critical issues, and minimize the disruptions caused to our users.
The ILogger logging service serves us precisely to leave a trace of events that we deem important in our application. As usual, we receive the service in the constructor of one of our components, such as the AdoNetCourseService application service.
- public class AdoNetCourseService : ICourseService.
- {
- private readonly ILogger<AdoNetCourseService> logger;
- //Receive service from the manufacturer
- public AdoNetCourseService(ILogger<AdoNetCourseService> logger)
- {
- //Conserve the service reference on a private field…
- this.logger = logger;
- }
- public async Task GetCoursesAsync()
- {
- //…then I write a message in the log to track the fact
- //that a user has requested a list of courses.
- //The message will be displayed in the console.
- logger.LogInformation(“Course list requested”);
- //TODO: I get the list of courses.
- }
- }
In the following image we can see that the name AdoNetCourseService that we indicated as the T-type parameter of the ILogger service will be used as the category, i.e., the source of the log message. In this way we can better determine the origin of each message.
Alternatively, if we want to arbitrarily decide the category name ourselves, we can use the ILoggerFactory service.
- public class AdoNetCourseService : ICourseService.
- {
- private readonly ILogger logger;
- //Receive service from the manufacturer
- public AdoNetCourseService(ILoggerFactory loggerFactory).
- {
- //Create an ILogger with an arbitrarily named category…
- this.logger = loggerFactory.CreateLogger(“Corsi”);
- }
- public async Task GetCoursesAsync()
- {
- //…then, as usual, I write a message in the log
- logger.LogInformation(“Course list requested”);
- //TODO: I get the list of courses.
- }
- }
LOG CRITICALITY LEVELS
The logging service provides us with several methods, each used to write messages at various levels of criticality. Here they are ordered from most important to least important:
- LogCritical: used to keep track of events that compromise the proper functioning of the entire application for all users, such as inability to write to disk;
- LogError: for errors that pertain to a specific situation, such as the inability to update a specific course;
- LogWarning: for error situations that do not necessarily depend on our application, such as opening a course that never existed in the database;
- LogInformation: for messages confirming the successful execution of an operation, such as a student’s purchase of a course;
- LogDebug: for messages that help us diagnose a problem in production when we cannot identify it by other means. We use this method to keep track of SQL queries, number of rows extracted from the database, and so on;
- LogTrace: to keep track of every single operational step taken by our application. It can be useful to have a transcript of the internal behavior of a method for the purpose of verifying its compliance with the specification.
Using layers well is important because log messages are written to the console, which is a resource contended by the various threads in our application. As a result, it can also be a bottleneck and deteriorate performance, especially when we are in production and have many concurrent users.
Therefore, it is important that in production only messages with a high level of criticality, such as from Warning on up, are written to the log. Therefore, we open the appsettings.json file and set this configuration.
- {
- “Logging”: {
- “LogLevel”: {
- “Default”: “Warning”
- }
- }
- }
Whereas, on the other hand, as long as we are in development, we have all the freedom to write even the least important messages in the log. Then we open the appsettings.Development.json file and add this configuration to redefine the layer for the Development environment.
- {
- “Logging”: {
- “LogLevel”: {
- “Default”: “Information”
- }
- }
- }
STRUCTURED LOGGING
Using the logging service, we can write structured messages, that is, messages that keep text separate from their values. In the following example, the message defines two placeholders called {query} and {error} that will be valued with the next two parameters provided to the LogError method.
- try
- {
- DataSet dataSet = await db.QueryAsync(query);
- //…
- }
- catch (Exception exc)
- {
- logger.LogError(“Error while executing query {query}: {error}”, query, exc.Message);
- }
This feature is especially useful if we use third-party logging providers such as Serilog that allow us to keep text and values well separated, so that then our logs can even be examined with analysis and reporting tools.
LINK TO CODE ON GITHUB
Download the section12 code or clone the GITHUB repository to have all sections available in your favorite editor.
Leave A Comment