C# - Serilog, how to add or enrich log events with properties

This post describes and demonstrates how to enrich your serilog log events with your own arbitrary properties. The first part will be on enriching with properties for all logs and the second for different contexts, such as a http endpoint or a transaction. At the end there is a full example using the basic Weather App from Microsoft.

For these examples to work you will need the Serilog.AspNetCore nuget package, however most of it can be applied to the basic Serilog package as well.

Enriching all log events

You can enrich all log events with a property using the .Enrich.WithProperty method on your serilog loggerConfiguration:

Log.Logger = loggerConfiguration
    .Enrich.WithProperty("SomeProperty", "1") // this line!
    .WriteTo
    .Console()
    .CreateLogger();

Using the above we add the number 1 on SomeProperty for all log events. Adding properties for all log events can be useful if you want to add a service name, machine name or the like. In order to see the property we need to change the default outputTemplate for our console sink:

.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Properties:j}{Message:lj}{NewLine}{Exception}")

In the above we change the output template to show us: log level, properties, the message and eventual Exceptions. Now if we create a new log event:

Log.Logger.Information("Starting!");

We will see the following log entry:

[22:45:28 INF] {"SomeProperty": "1", "RequestId": "0HMSIPVNUI2H6:00000011", "RequestPath": "/weatherforecast"}Starting!

From the above we can see that our property SomeProperty with the value 1 is part of the log event. That is all there is to it. See further down the page for a full example!

Enriching specific log events

In order to add a property to a log event with your own properties we can use the LogContext.PushProperty method. But before we can do that, we need to enable it using .Enrich.FromLogContext() on our loggerConfiguration:

Log.Logger = loggerConfiguration
    .Enrich.FromLogContext() // This line
    .WriteTo
    .Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Properties:j}{Message:lj}{NewLine}{Exception}")
    .CreateLogger();

We can then use LogContext.PushProperty() in order to set a property. The first parameter is the name of the property the second is the value:

WeatherForecast[] forecast = GetForecast(summaries);
using (LogContext.PushProperty("LengthOfForecast", forecast.Length))
{
    Log.Logger.Information("Forecast created!");
}

This will create the following log event with the outputTemplate from above:

[22:45:28 INF] {"LengthOfForecast": 5, "RequestId": "0HMSIPVNUI2H6:00000011", "RequestPath": "/weatherforecast"}Forecast created!

You can also override already existing properties using WithProperty. Remember to pop properties from the context in the order in which they were added. It is stated by the serilog that lese "Behavior otherwise is undefined".

That is all

A full working example of the code can be found down below. It is based on the basic ASP.NET weather forecast app. The Serilog Enrich documentation can be found here. If you liked the post or have some recommendations for improvement, please put them down below in the comments section!

Full example (in one single program.cs)

using Serilog;
using Serilog.Context;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Host.UseSerilog();


var app = builder.Build();
var loggerConfiguration = new LoggerConfiguration();

Log.Logger = loggerConfiguration
    .Enrich.WithProperty("SomeProperty", "1")
    .Enrich.FromLogContext()
    .WriteTo
    .Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Properties:j}{Message:lj}{NewLine}{Exception}")
    .CreateLogger();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    Log.Logger.Information("Starting!");

    WeatherForecast[] forecast = GetForecast(summaries);

    using (LogContext.PushProperty("LengthOfForecast", forecast.Length))
    {
        Log.Logger.Information("Forecast created!");
    }

    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();

app.Run();

static WeatherForecast[] GetForecast(string[] summaries)
{
    return Enumerable.Range(1, 5).Select(index =>
    new WeatherForecast
    (
        DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        Random.Shared.Next(-20, 55),
        summaries[Random.Shared.Next(summaries.Length)]
    ))
    .ToArray();
}

internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}