A Step by Step Guide to Logging in ASP.NET Core 5
Tags: aspnetcore, logging, guide, diagnostics
Published: 2021-08-23 18:01:57

In this fast-paced era of building modern and complex web applications, it is very important to identify and fix bugs quickly and efficiently. Developers need to know the state of the application and the data it is processing at the time when something bad happened. One of the proven technique is to log information, data and the details about errors and exceptions occurred in the application at any given time. In this tutorial, I will give you a detailed overview of logging in an ASP.NET Core 5 web application.

## **Logging Framework in .NET Core**

Logging is an essential part of .NET Core applications and there are many built-in services and logging providers available that can not only be used to implement basic logging in your applications but they are also extensible to add custom logging capabilities. All abstract classes and interfaces related to logging infrastructure are available in a [NuGet](https://www.nuget.org/) package called **Microsoft.Extensions.Logging.Abstractions** and these classes are implemented by built-in classes available in **Microsoft.Extensions.Logging** package. If you want to log information in your controllers, services, components, etc. you need to inject the instance of either **ILogger** or **ILogger&lt;T&gt;** interface using the built-in [dependency injection](https://www.ezzylearning.net/tutorial/a-step-by-step-guide-to-asp-net-core-dependency-injection) framework.

If you want to learn dependency injection then read my [step by step guide about Dependency Injection in ASP.NET Core](https://www.ezzylearning.net/tutorial/a-step-by-step-guide-to-asp-net-core-dependency-injection).

## **Getting Started with Logging**

Create an ASP.NET Core MVC web application using .NET 5 and inject **ILogger&lt;HomeController&gt;** interface in the constructor of HomeController as shown in the following code snippet.
```
public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        _logger.LogInformation("Starting Index...");

        return View();
    }
}
```
DI framework will automatically inject ILogger instance in the controller class and we can log information using the **LogInformation** method. If you will run the application you will see the above message “Starting Index…” displayed in the Visual Studio output window.
```
AspNetCore5LoggingDemo.Controllers.HomeController: Information: Starting Index...
```
You can also use the built-in **ILoggerFactory** interface to create loggers on the fly and use those loggers to log the information. To create a logger using ILoggerFactory interface, you can use **CreateLogger(string categoryName)** method or CreateLogger&lt;T&gt; method. Here is an example of using ILoggerFactory interface.
```
public class HomeController : Controller
{
    private readonly ILoggerFactory _loggerFactory;

    public HomeController(ILoggerFactory loggerFactory)
    {
        _loggerFactory = loggerFactory;
    }

    public IActionResult Index()
    {
        var logger = _loggerFactory.CreateLogger<HomeController>();

        logger.LogInformation("Starting Index...");

        return View();
    }
}
```
## **Overview of ILogger interface**

ILogger is a generic and the most common interface available in .NET Core logging infrastructure. It has the following three methods available:

**BeginScope** – To logically group multiple related log messages

**IsEnabled** – To check if a particular log level is enabled or not

**Log** – To log a message with a specific log level, event id, and other optional parameters

Most of the time, developers don’t use the above methods in their code and this is because normally developers want to log simple string messages and exceptions. This is why we have the following useful extension methods which can log messages and exceptions with a specific log level.

- LogTrace
- LogDebug
- LogInformation
- LogWarning
- LogError
- LogCritical

Let’s use all of the above extension methods in the Index action of the HomeController and run your application.
```
public IActionResult Index()
{
    _logger.LogTrace("Logging Trace Message");
    _logger.LogDebug("Logging Debug Message");
    _logger.LogInformation("Logging Information Message");
    _logger.LogWarning("Logging Warning Message");
    _logger.LogError("Logging Error Message");
    _logger.LogCritical("Logging Critical Message");

    return View();
}
```
You will see only four of the above messages will be logged in the Visual Studio output window. This is because each one of the above log levels has a value associated with it and by default the minimum log level configured in your application is “Information” that’s why any message below the “Information” log level is not logged.
```
Information: Logging Information Message
Warning: Logging Warning Message
Error: Logging Error Message
Critical: Logging Critical Message
```
You can check and configure a different log level in your application **appsettings.json** file
```
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "\*"
}
```
The following table specifies different log levels, their values, description, and the corresponding extension methods.

| **LogLevel** | **Value** | **Method** | **Description** |
| --- | --- | --- | --- |
| [Trace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel) | 0 | [LogTrace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions.logtrace) | The messages logged with this level contains the most detailed information. These messages may contain sensitive app data and this is why this level should not be used in production. |
| [Debug](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel) | 1 | [LogDebug](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions.logdebug) | The messages logged with this level contains short term but useful information developer’s use for debugging during development. This level should not be used on production as it may log a high volume of messages. |
| [Information](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel) | 2 | [LogInformation](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions.loginformation) | The messages logged with this level can track the general flow of the application. These logs should have a long-term value. |
| [Warning](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel) | 3 | [LogWarning](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions.logwarning) | The messages logged with this level generally contain information about some abnormal or unexpected application behavior. These logs typically contain errors or conditions that don't cause the application to fail or stop the execution. |
| [Error](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel) | 4 | [LogError](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions.logerror) | This level logs messages about the errors and exceptions that cannot be handled and caused the application to stop. These messages generally indicate a failure in the current activity or request, not an application-wide failure. |
| [Critical](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel) | 5 | [LogCritical](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions.logcritical) | This level log failure that requires immediate attention e.g. data loss scenarios or out of disk space etc. |
| [None](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel) | 6 |  | Specifies that no messages should be logged. |

Many logging methods mentioned above also allow you to use message templates while logging information about the available data or information. These message templates may contain placeholders for which we can specify arguments. The following example shows how you can specify the placeholders and inject your data in logged messages.
```
string email = "james.peter@example.com";
_logger.LogInformation("A user with the email: {userEmail} logged in at {loginTime}", email, DateTime.Now);
```
The above line will output the following log message.
```
A user with the email: james.peter@example.com logged in at 12/18/2020 00:13:43
```
## **What is Logging Event ID**

Many logging methods accept an ‘Event Id’ parameter which is an event identified and it can be used to group related log messages. For example, you can create unique event identifiers in your application as follows.
```
internal static class AppLoggingEvents
{
    internal const int Create = 1000;
    internal const int Read = 1001;
    internal const int Update = 1002;
    internal const int Delete = 1003;

    internal const int Error = 3000;
    internal const int RecordNotFound = 4000;
}
```
Now all log messages related to creating objects or database records can be given a unique logging event id of 1000.
```
_logger.LogInformation(AppLoggingEvents.Create, "Creating Product");
```
The usage of event Ids is provider specific, some logging providers may display these event Ids while logging messages and others may simply ignore it. Some logging providers can even store these event Ids and can also provide a facility to filter log messages by event Ids. For example, you may want to view all log messages related to **AppLoggingEvents.Error**.

Event Ids can also be used while logging exceptions in your program. Almost all logging methods have overloads that accept exception objects along with Event Ids as shown in the code snippet below:
```
int a = 5;
int b = 0;

try
{
    int c = a / b;
}
catch (Exception ex)
{
    _logger.LogWarning(AppLoggingEvents.Error, ex, "Divide by Zero Exception: {p1}/{p2}", a, b);
}
```
## **.NET Logging Providers**

Logging provider is a class that implements the **ILoggerProvider** interface and is used to persists logs to some target. A Console logging provider is an exception that doesn’t persist logs but displays them on standard output. There are many built-in logging providers available in .NET and you can also download third party logging providers from [NuGet](https://www.nuget.org/). I have also written a post [Logging in ASP.NET Core 5 using Serilog](https://www.ezzylearning.net/tutorial/logging-in-asp-net-core-5-using-serilog) that will cover the most famous third party logging library in detail.

Here is the list of built-in logging providers available in .NET.

<table><tbody><tr><td><strong>Provider</strong></td><td><strong>NuGet package</strong></td><td><strong>Purpose</strong></td></tr><tr><td>Azure App Service</td><td>Microsoft.Extensions.Logging.AzureAppServices &nbsp;</td><td>Logs to Azure Blob storage or to a text file in an Azure App Service file system</td></tr><tr><td>Console</td><td>Microsoft.Extensions.Logging.Console</td><td>Logs output to the console</td></tr><tr><td>Debug</td><td>Microsoft.Extensions.Logging.Debug</td><td>Logs output using System.Diagnostics.Debug class WriteLine method</td></tr><tr><td>EventLog</td><td>Microsoft.Extensions.Logging.EventLog</td><td>Logs output to the Windows Event Log</td></tr><tr><td>EventSource</td><td>Microsoft.Extensions.Logging.EventSource</td><td>Logs output to a cross-platform event source using <strong>Event Tracing</strong> <strong>for Windows </strong>(<strong>ETW</strong>)</td></tr></tbody></table>

If you want to use any of the above or even a third party logging provider, you need to register it with the .NET logging framework. This registration can be done either in Startup.cs or Program.cs file. Registering logging provider in Program.cs has an added benefit of capturing and logging events occurred in your Startup.cs file. The normal practice is to clear the default logging providers registered by **Host.CreateDefaultBuilder** and then add the logging provider you want to register using one of the **AddXXX** methods of **ILoggingBuilder** class as shown in the code snippet below:
```
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(loggingBuilder =>
            {
                loggingBuilder.ClearProviders();
                loggingBuilder
                    .AddDebug()
                    .AddEventLog();
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}
```
In the above code snippet, I registered both **Debug** and **EventLog** logging providers which means my logging statements such as the following statement will be persisted in both Debug window as well as Windows Event logs.
```
public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        _logger.LogWarning("This is a warning message");

        return View();
    }
}
```
Here is the output window in Visual Studio showing the above message logged using the Debug logging provider.

![.NET Logging to Debug Window](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u1eir1ktbms33hixtdmx.png)

The same message is also logged by the Event Log logging provider in Windows Event Logs and you can view the logged messages using the Windows Event Viewer tool.

![.NET Logging to Windows Event Log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ewyx5kbgsc5ax7maltm.png)

There are some popular third-party logging providers such as [Serilog](https://serilog.net/), [Log4Net](https://logging.apache.org/log4net), [NLog](https://nlog-project.org/), [elmah.io](https://elmah.io/)

which provide many additional logging features such as structured or semantic logging. Some providers also provide the feature to persists logs to relational databases e.g. SQL Server or even persisting to a real-time cloud-based logs aggregation, monitoring, and analysis service such as [LogDNA](https://logdna.com/).  

## Summary

In this tutorial, I have given you an overview of basic .NET Core logging features. I have covered the ILogger interface in detail and also gave you an overview of some built-in logging providers. You have to explore all built-in and third-party logging providers to decide which logging provider is most suitable for your application needs. You can also write your own custom logging providers if no built-in or third party login provider meets your requirements. I have also written a tutorial [Logging in ASP.NET Core 5 using Serilog](https://www.ezzylearning.net/tutorial/logging-in-asp-net-core-5-using-serilog) that will cover one of the most popular third-party logging provider [Serilog](https://serilog.net/) in more detail.