My software development journey

Adding logging to .NET Core console application

October 09, 2021

There are many different logging frameworks available for .NET. Serilog and log4net being currently popular. .NET Core also ships with a default logging framework. While it is not as feature-rich as other frameworks, it is very extensible. It is also configured as a default when using WebHost setup or .NET Generic Host. Since the purpose of this post is to show how to add logging to a simple console application, we will configure logging ourselves. Let’s start by adding the required NuGet packages to our project file:

  • Microsoft.Extensions.Logging
  • Microsoft.Extensions.Logging.Console

In my sample application, I will be using version 5.0.0, and below is the code that configures it:

using var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddConsole();
});

var logger = loggerFactory.CreateLogger("MyCategoryName");
logger.LogInformation("Hello, world!");

To create a logger instance, we first create and configure a logger factory. This is done via the Create method. In our sample, we configure the factory with console output. Note that the AddConsole method is an extension method found in Microsoft.Extensions.Logging.Console NuGet package. Again, similar to the .NET Core configuration, if you ever find yourself searching for a method from online samples, it could be, that you are missing the appropriate NuGet package. Also, note that the logger factory is a disposable resource, so make sure it is disposed of after logging is not required anymore.

To create a logger, we need to provide a category name. It can be any string, but in practice, it is often fully qualified consuming class name. We will take a closer look at how to use categories later.

The final step is writing something to a log. This produces the following console output:

info: MyCategoryName[0]
      Hello, world!

In the output, we can see the log level info and category name MyCategoryName. The 0 is the event ID which I will mention later. Note that I chose to use the LogInformation extension method. Alternatively, we could have used the following:

logger.Log(LogLevel.Information, "Hello World!");

In practice, it is more typical to use the extension methods for a given log level. The only log level that doesn’t have an extension method is LogLevel.None. The reason being, that this log level is used to disable logging output. Let’s take a look at how to filter logging output next, but before we do that, let’s modify our example to facilitate the discussion. We will first add Microsoft.Extensions.Logging.Debug NuGet package, so that we can include Debug logging provider.

const string categoryName = "MyCategoryName";
using var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddConsole();
    builder.AddDebug();
});

var logger = loggerFactory.CreateLogger(categoryName);

logger.LogInformation("Some log information output");
logger.LogWarning("Some log warning output");
logger.LogError("Some log error output");

I have also added three different log outputs on which we will be filtering. Now let’s take a look at how to add filtering. We will start by adding filtering in code and later go to a more flexible variant of doing it via configuration. To add filtering rules in code, we need to modify how we create the logger factory:

using var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddConsole();
    builder.AddDebug();
    builder.AddFilter(l => l >= LogLevel.Warning);
    builder.AddFilter(categoryName, LogLevel.Information);
    builder.AddFilter<ConsoleLoggerProvider>(
      categoryName, LogLevel.Error);
});

In the code above, we defined three different rules. In general, the more specific rule will override the less specific one. Here the order of definition doesn’t matter. The first rule is the default rule, which is applied if no other rule is specified. The second rule is for the category name and the third for the category name on the console logger. Since a single category is used, only error messages are shown with this configuration.

Configuring filtering in code has the downside of requiring recompilation. So let us now have a look at how to achieve the same result using a configuration file. First, add an appsettings.json file, which will contain our configuration. Remember to set it as a copy always in your project file.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "MyCategoryName": "Information"
    },
    "Console": {
      "LogLevel": {
        "MyCategoryName": "Error"
      }
    }
  }
}

Then we need to add Microsoft.Extensions.Configuration.Binder and Microsoft.Extensions.Configuration.Json NuGet packages to our project. Now we can modify our code, so that configuration will be loaded:

var configBuilder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json",
      optional: true, reloadOnChange: true);

var configurationRoot = configBuilder.Build();
using var configDispose = configurationRoot as IDisposable;

const string categoryName = "MyCategoryName";
using var loggerFactory = LoggerFactory.Create(builder =>
{
    var loggingConfig = configurationRoot.GetSection("Logging");
    builder.AddConfiguration(loggingConfig);
    builder.AddConsole();
    builder.AddDebug();
});

First, we create our configuration from appsettings.json file. To configure logging take the "Logging" section and pass it to the AddConfiguration extension method. Note that we could have named our logging configuration section differently, but in default WebHost configurations "Logging" is used, so I chose to stick with that.

Resources: