Structural logging in .NET using Serilog as an example

We all know that logging is a very useful thing for a modern project. With it, you can quickly localize and fix an error in the product, restore the case that led to it, and view the history of user actions.

There are several types of logging, such as:

  1. classic – when the whole log is a set of lines, which is sometimes difficult to understand and analyze something in it.

_logger.LogInformation($"The magic number is {number}");
  1. Structural – when two log entries will be created for one event, one entry is a message output template, the second entry is an object that will be substituted into the template.

_logger.LogInformation("The magic number is {number}", number);

Structural logging opens up the possibility of storing and analyzing events in various storages, such as NoSql, Sql databases. For .NET, there are many third-party libraries for such logging, such as Serilog or NLog.
Let’s take a look at how to use one of these libraries and start logging properly😊

Classic logging

  1. Create a new project template ASP.NET Core Web API. By default, after creation, we will have the following solution structure:

Solution structure StructureLogging
Solution structure StructureLogging
  1. Let’s change the method Get controller weatherforecastreplacing it with the following code:

public IEnumerable<WeatherForecast> Get(string city, int day)
{
    _logger.LogInformation($"Requested weather for city {city} on {day} day");

    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();
}
  1. Run the application and make a GET request to http://localhost:7005/weatherforecast?city=Moscow&day=1 and see the result of execution in the console:

Logging result
Logging result

It can be seen that an event record is just a string, and wherever it goes (file, DBMS, event log) it will remain a string, from which something meaningful for analysis can only be pulled out using regular expressions, as an example. This option suits few people and comes to the rescue structural logging.

Structural logging

Now let’s change a little the project that we created above. To modify it, we will use the Serilog library.

  1. Install NuGet packages Serilog, Serilog.Sinks.Console and Serilog.Extensions.Hosting.

  2. In the Program.cs file, add the setting and add Serilog as a logger:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;

var builder = WebApplication.CreateBuilder(args);
/*
.....................................
*/

Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .CreateLogger();

builder.Host.ConfigureLogging(logging =>
{
    logging.AddSerilog();
    logging.SetMinimumLevel(LogLevel.Information);
})
.UseSerilog();

/*
.....................................
*/
  1. In the Get method of the WeatherForecast controller, I changed the log entry:

_logger.LogInformation("Requested weather for city {City} on {Day} day", city, day);

We have removed the string interpolation and now the city and day values ​​in order will be substituted for the {City} and {Day} anchors in the template.

  1. Run the application again and make a GET request to http://localhost:7005/weatherforecast?city=Moscow&day=1 and see the result of execution in the console:

Logging result
Logging result

It can be seen that our values ​​passed to the logger are colored in different colors. This is because they are no longer considered a simple string, but are objects that are substituted into the template.

You can also pass more complex objects, such as custom data types.
The only thing must be remembered for complex objects – when indicating the anchor is placed ahead @ {@Weather} to indicate to Serilog that the object is user-defined, or in the user-defined object, you need to override the method ToStringsupplementing it with your own implementation.

Let’s change the method code again Get controller weatherforecast:

public IEnumerable<WeatherForecast> Get(string city, int day)
{
    var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();

    _logger.LogInformation("Requested weather for city {City} on {Day} day. The weather is {@Weather}", city, day, result.First());

    return result;
}

Then run the application and make a GET request to http://localhost:7005/weatherforecast?city=Moscow&day=1 and see the result of execution in the console:

Logging result
Logging result

The complex object has “decomposed” according to its properties and is ready for the analysis of logs in the future (if we are now talking about something more complex than the console).
Next time we’ll see how to use structure logging in conjunction with elasticsearch and see how it allows us to analyze the logs of our application.

Conclusion

Structural logging has advantages, such as the ability to separate data from an event, with subsequent analysis, ease of setup and use.

Similar Posts

Leave a Reply