How ASP.NET Core logging works with ILogger and LogLevel

Published: Sunday 13 March 2022

Logging is an essential part of ASP.NET Core applications. Logs can be written with the extension methods that are available in the ILogger interface.

In-order to use logs, we can use logging providers which inherit off the ILoggerProvider interface. ASP.NET Core has a number of logging custom providers which are built in. Alternatively, a custom provider can be created which implements the ILoggerProvider method abstractions.

Both the ILogger and ILoggerProvider interfaces are in the Microsoft.Extensions.Logging namespace, and can be used by installing the Microsoft.Extensions.Logging.Abstractions NuGet package.

We will have a look at how ASP.NET Core logging works, the different log levels that are available and how to implement logging in an ASP.NET Core application.

How logging works

When creating an ASP.NET Core project, an appsettings.json file is created that looks similar to this:

// appsettings.json
{
	"Logging": {
		"LogLevel": {
		  "Default": "Information",
		  "Microsoft": "Warning",
		  "Microsoft.Hosting.Lifetime": "Information"
		}
	}
}

Inside the Logging object, there is a LogLevel object. Within that, there are a number of JSON tokens contained within it. The token's key represents the namespace of the log level, and the value represents at what level do we start writing logs.

In-terms of log levels, it uses the LogLevel enum which is included in the Microsoft.Extensions.Logging namespace. These are the LogLevel enum fields that are available, including their name, value and the ILogger extension method we can use to write a log:

Enum name Enum value ILogger extension method
Trace 0 LogTrace
Debug 1 LogDebug
Information 2 LogInformation
Warning 3 LogWarning
Error 4 LogError
Critical 5 LogCritical
None 6

The way it works in appsettings.json is that the enum value represents the severity of the log. When we specify a LogLevel, it will write a log at a LogLevel inside that assembly that is equal or higher to it's enum value. (excluding the None field). So if we write a log with a Warning level, it would also write logs that are written with the Error and Critical levels.

It's also worth noting that a deeper namespace would take priority. In this example, a log would be written if it was inside the Microsoft.Hosting.Lifetime with the Information level.

"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"

However, if the log event occurred inside the Microsoft.Hosting namespace, the log would not be written. This is because we have specified not to write logs inside the Microsoft assembly unless it's level is set to Warning or higher.

Adding log events

We can use dependency injection to inject the ILogger interface as a parameter into a class like an MVC controller.

The ILogger interface has a generic type, and we use the generic type to specify the class type that we are writing logs to.

Here is an example of how we can inject the ILogger type instance into an MVC controller:

// HomeController.cs
public class HomeController : Controller
{
	...
	protected readonly ILogger<HomeController> _logger;

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

As previously stated, the ILogger interface has an extension method available for each log level. Here is an example of how to use ILogger to write a log set with the Information level.

// HomeController.cs
public class HomeController : Controller
{
	protected readonly ILogger<HomeController> _logger;

	public HomeController(ILogger<HomeController> logger)
	{
		_logger = logger;
	}
	 
	public IActionResult Index() {
		_logger.LogInformation("This is the homepage");
		 
		return View();
	}    
}

Built-in logging providers

ASP.NET Core has a number of built-in logging providers available which are:

  • Console
  • Debug
  • EventSource
  • EventLog

With appsettings.json, we can specify different log levels depending on the logging provider that we are using. Here is an example of setting the log level for all providers, and how to override it for the Console provider.

// appsettings.json
{
	"Logging": {
		"LogLevel": { // All providers
			"Default": "Information",
			"Microsoft": "Warning"
		}
		"Console": {
			"LogLevel": { // Console provider
				"Default": "Information", // Overrides preceding LogLevel:Default setting.
				"Microsoft": "Trace" // Writes at Trace Level inside the Microsoft assembly
			}        
		}
	}
}

By default, the Microsoft namespace has been set so all logging providers write a log where the LogLevel is set with the Warning field or higher. However, with the Console logging provider, we have overridden this so Trace level logs are written.

In-addition, we are able to write our own logging provider by creating a custom provider. Check out the logging provider we created to write logs to text files.

See it in action

Watch our video where we demonstrate how ASP.NET Core logging works and how to configure it in the appsettings.json file.

Moving forward with logging

Now that we know how ASP.NET Core logging works, we can start to think about what we want to log and how we can go about storing them. Logging is very important, particularly if something happens in the background. It makes it a lot easier to debug an issue with our application, and to ensure that are application is running as it should be.