First, the logging system is provider-based, meaning we must register one or more ILoggerProvider instances if we want our log entries to go somewhere. By default, when calling WebApplication.CreateBuilder(args), it registers the Console, Debug, EventSource, and EventLog (Windows only) providers, but we can modify this list. You can add and remove providers if you need to. The required dependencies for using the logging system are also registered as part of this process.Before we look at the code, let’s learn how to create log entries, which is the objective behind logging. To create an entry, we can use one of the following interfaces: ILogger, ILogger<T>, or ILoggerFactory. Let’s take a look at them in more detail:
Interface | Description |
ILogger | Base type that allows us to perform logging operations. |
ILogger<T> | Base type that allows us to perform logging operations. Inherit from the ILogger interface. The system uses the generic parameter T as the log entry’s category . |
ILoggerFactory | A factory interface that allows creating ILogger objects and specifying the category name manually as a string. |
Table 10.1: the logging interfaces.
The following code represents the most commonly used pattern, which consists of injecting an ILogger<T> interface and storing it in an ILogger field before using it, like this:
public class Service : IService
{
private readonly ILogger _logger;
public Service(ILogger<Service> logger)
{
_logger = logger;
}
public void Execute()
{
_logger.LogInformation(“Service.Execute()”);
}
}
The preceding Service class has a private _logger field. It takes an ILogger<Service> logger as a parameter and stores it in that field. It uses that field in the Execute method to write an information-level message to the log.The IService interface is very simple and only exposes a single Execute method for testing purposes:
public interface IService
{
void Execute();
}
I loaded a small library I created to test this out, providing additional logging providers for testing purposes. With that, we are creating a generic host (IHost) since we don’t need a WebApplication in our tests, then we configure it:
namespace Logging;
public class BaseAbstractions
{
[Fact]
public void Should_log_the_Service_Execute_line()
{
// Arrange
var lines = new List<string>();
var host = Host.CreateDefaultBuilder()
.ConfigureLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddAssertableLogger(lines);
})
.ConfigureServices(services =>
{
services.AddSingleton<Service>();
})
.Build();
var service = host.Services.GetRequiredService<Service>();
// Act
service.Execute();
// Assert
Assert.Collection(lines,
line => Assert.Equal(“Service.Execute()”, line)
);
}
// Omitted other members
}
In the Arrange phase of the test, we create some variables, configure IHost, and get an instance of the Service class that we want to use to test the logging capabilities that we programmed.The highlighted code removes all providers using the ClearProviders method. Then it uses the AddAssertableLogger extension to add a new provider. The extension method comes from the library that we loaded. We could have added a new provider if we wanted, but I wanted to show you how to remove existing providers so we can start from a clean slate. That’s something you might need someday.
The library that I loaded is available on NuGet and is named ForEvolve.Testing.Logging, but you do not need to understand any of this to understand logging abstractions and examples.