[Home](../) › [Blog](../blog) › Adding Serilog to ASP.NET Core: a practical guide

# Adding Serilog to ASP.NET Core: a practical guide

> How to add Serilog to your ASP.NET Core project and why you should do it.

Published: 2024-07-18 · Author: Metalama Team · Canonical: https://postsharp.net/blog/serilog-aspnetcore

Serilog is a logging library for .NET. It can be used on its own, but it is also compatible with `Microsoft.Extensions.Logging`, making it ideal for ASP.NET Core applications. In this article, we'll explain why you should use Serilog in your ASP.NET Core application and demonstrate how to integrate it into your project. We'll also cover ASP.NET middleware as a way to enrich your logs with contextual properties, and techniques to add high-verbosity logging without boilerplate code.

## Why use Serilog in ASP.NET Core?

By default, ASP.NET Core apps have a built-in logging system thanks to the `Microsoft.Extensions.Logging` namespace. However, it lacks many useful features. And while it is extensible, someone has to implement and maintain those extensions. Therefore, it often makes sense to use a third-party library like Serilog, which is already feature-rich and well-maintained.

Here are the features that make Serilog a good choice for ASP.NET Core applications:

* **Contexts and enrichers:** While `Microsoft.Extensions.Logging` supports scopes, they are quite limited and verbose. Serilog contexts make it much easier to add custom contextual information to your logs. Enrichers provide automatic ways of adding commonly used information, such as the correlation ID.
* **Sinks, formatting, and filtering:** `Microsoft.Extensions.Logging` supports a limited number of logging providers, while Serilog offers [about a hundred different sinks](http://github.com/serilog/serilog/wiki/Provided-Sinks), including logging to a file and various cloud services and databases. Serilog also supports advanced formatting and filtering options.
* **Structured logging:** `Microsoft.Extensions.Logging` has only very limited support for structured logging, while Serilog is built around structured logging. This makes it easier to query and analyze logs, especially when you have a large number of logs.

Using Serilog is not an all-or-nothing decision. As long as you use only Serilog sinks, you can, for example, combine `Microsoft.Extensions.Logging` scopes and Serilog contexts.

## Adding Serilog to your ASP.NET Core project

### Step 1. Add the Serilog packages

To add Serilog to your ASP.NET Core project, install [the `Serilog.AspNetCore` NuGet package](https://www.nuget.org/packages/Serilog.AspNetCore):

```bash
dotnet add package Serilog.AspNetCore
```

This package includes support for registering Serilog with your application and optionally enabling request logging.

### Step 2. Add the Serilog service

To configure Serilog, add code like the following to your `Program.cs` file:

```csharp
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger();

builder.Services.AddSerilog();
```

The full source code of examples in this article is available on [GitHub](https://github.com/postsharp/TimelessDotNetEngineer/tree/main/src/logging/serilog-aspnetcore).

You can also delete the now unnecessary `Microsoft.Extensions.Logging` configuration from your `appsettings.json` file.

The console output from an ASP.NET Core Web API application will now look something like this:

<div class="highlighter-rouge"><pre class="highlight" style="color:#bbb;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word"><span style="color:#555555">[</span><span style="color:#aaaaaa">15:56:46</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Now listening on: </span><span style="color:#00d7ff">http://localhost:5106</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">15:56:46</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Application started. Press Ctrl+C to shut down.</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">15:56:46</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Hosting environment: </span><span style="color:#00d7ff">Development</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">15:56:46</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Content root path: </span><span style="color:#00d7ff">/mnt/c/src/TimelessDotNetEngineer/src/logging/serilog-aspnetcore/SerilogInAspNetCore</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">15:56:53</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Request starting </span><span style="color:#00d7ff">HTTP/1.1</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">GET</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">http</span><span style="color:#ffffff">://</span><span style="color:#00d7ff">localhost:5106</span><span style="color:#00d7ff"></span><span style="color:#00d7ff">/swagger/index.html</span><span style="color:#00d7ff"></span><span style="color:#ffffff"> - </span><span style="color:#005fff">null</span><span style="color:#ffffff"> </span><span style="color:#005fff">null</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">15:56:53</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Request finished </span><span style="color:#00d7ff">HTTP/1.1</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">GET</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">http</span><span style="color:#ffffff">://</span><span style="color:#00d7ff">localhost:5106</span><span style="color:#00d7ff"></span><span style="color:#00d7ff">/swagger/index.html</span><span style="color:#00d7ff"></span><span style="color:#ffffff"> - </span><span style="color:#ff00d7">200</span><span style="color:#ffffff"> </span><span style="color:#005fff">null</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">text/html;charset=utf-8</span><span style="color:#ffffff"> </span><span style="color:#ff00d7">71.1747</span><span style="color:#ffffff">ms</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">15:56:53</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Request starting </span><span style="color:#00d7ff">HTTP/1.1</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">GET</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">http</span><span style="color:#ffffff">://</span><span style="color:#00d7ff">localhost:5106</span><span style="color:#00d7ff"></span><span style="color:#00d7ff">/swagger/v1/swagger.json</span><span style="color:#00d7ff"></span><span style="color:#ffffff"> - </span><span style="color:#005fff">null</span><span style="color:#ffffff"> </span><span style="color:#005fff">null</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">15:56:54</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Request finished </span><span style="color:#00d7ff">HTTP/1.1</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">GET</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">http</span><span style="color:#ffffff">://</span><span style="color:#00d7ff">localhost:5106</span><span style="color:#00d7ff"></span><span style="color:#00d7ff">/swagger/v1/swagger.json</span><span style="color:#00d7ff"></span><span style="color:#ffffff"> - </span><span style="color:#ff00d7">200</span><span style="color:#ffffff"> </span><span style="color:#005fff">null</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">application/json;charset=utf-8</span><span style="color:#ffffff"> </span><span style="color:#ff00d7">70.2124</span><span style="color:#ffffff">ms</span>
</pre></div>

### Step 3. Enable HTTP request logging

Since the default ASP.NET Core logging is very noisy, it's a good practice to reduce its verbosity (using `.MinimumLevel.Override(...)`) and replace it with Serilog request logging by adding a call to `app.UseSerilogRequestLogging()` in your `Program.cs`:

```csharp
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .MinimumLevel.Verbose()
    .MinimumLevel.Override( "Microsoft.AspNetCore", LogEventLevel.Warning )
    .MinimumLevel.Override( "Microsoft.Extensions.Hosting", LogEventLevel.Information )
    .MinimumLevel.Override( "Microsoft.Hosting", LogEventLevel.Information )
    .CreateLogger();

builder.Services.AddSerilog();
var app = builder.Build();
app.UseSerilogRequestLogging();
```

Using that, the output will be nicer:

<div class="highlighter-rouge"><pre class="highlight" style="color:#bbb;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word"><span style="color:#555555">[</span><span style="color:#aaaaaa">16:18:03</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Now listening on: </span><span style="color:#00d7ff">http://localhost:5106</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">16:18:03</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Application started. Press Ctrl+C to shut down.</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">16:18:03</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Hosting environment: </span><span style="color:#00d7ff">Development</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">16:18:03</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">Content root path: </span><span style="color:#00d7ff">/mnt/c/src/TimelessDotNetEngineer/src/logging/serilog-aspnetcore/SerilogInAspNetCore</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">16:18:07</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">HTTP </span><span style="color:#00d7ff">GET</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">/swagger/index.html</span><span style="color:#ffffff"> responded </span><span style="color:#ff00d7">200</span><span style="color:#ffffff"> in </span><span style="color:#ff00d7">57.3368</span><span style="color:#ffffff"> ms</span>
<span style="color:#555555">[</span><span style="color:#aaaaaa">16:18:07</span><span style="color:#555555"> </span><span style="color:#ffffff">INF</span><span style="color:#555555">] </span><span style="color:#ffffff">HTTP </span><span style="color:#00d7ff">GET</span><span style="color:#ffffff"> </span><span style="color:#00d7ff">/swagger/v1/swagger.json</span><span style="color:#ffffff"> responded </span><span style="color:#ff00d7">200</span><span style="color:#ffffff"> in </span><span style="color:#ff00d7">68.1877</span><span style="color:#ffffff"> ms</span>
</pre></div>

## Adding logging to your code

Now that Serilog is properly configured, let's talk about how and when to add logging to your code.

### Step 1. Pull the ILogger service

The first step is to pull the logging interface from the dependency injection container. It's recommended to use the regular `Microsoft.Extensions.Logging.ILogger` interface instead of the product-specific `Serilog.ILogger` one because it will make your code more standard and portable. However, there are no significant differences between these interfaces.

Instead of pulling the non-generic `ILogger` interface, pull the generic `ILogger<T>` interface where `T` is the current class. This trick will allow you to adjust the verbosity of the logging for this specific class and easily filter or search messages reported by this specific class.

```csharp
[ApiController]
[Route( "[controller]" )]
public class WeatherForecastController( ILogger<WeatherForecastController> logger ) : ControllerBase
```

### Step 2. Add logging instructions

The general rules are not specific to Serilog: you should log whenever something happens that might be useful to troubleshoot a problem in production. Use the `LogError`, `LogWarning`, `LogInformation`, and `LogVerbose` methods of the `ILogger` interface to write messages of different severities.

By default, Serilog will use the `ToString()` method to format parameters to text. If you want Serilog to include the JSON representation of the log parameter instead, you can use the `@` destructuring operator. This will work even if you use the regular `Microsoft.Extensions.Logging.ILogger` interface.

```csharp
logger.LogDebug(
    "Returning weather forecast for the {days} days after today: {@forecast}",
    days,
    forecast );
```

This will produce a log message like this:
<div class="highlighter-rouge"><pre class="highlight" style="color:#bbb;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word"><span style="color:#555555">[</span><span style="color:#aaaaaa">19:09:56</span><span style="color:#555555"> </span><span style="color:#aaaaaa">DBG</span><span style="color:#555555">] </span><span style="color:#ffffff">Returning weather forecast for the </span><span style="color:#ff00d7">5</span><span style="color:#ffffff"> days after today: </span><span style="color:#555555">[</span><span style="color:#555555">{</span><span style="color:#aaaaaa">"Date"</span><span style="color:#555555">: </span><span style="color:#5fffaf">"2024-07-10"</span><span style="color:#555555">, </span><span style="color:#aaaaaa">"Temperature"</span><span style="color:#555555">: </span><span style="color:#ff00d7">31</span><span style="color:#555555">, </span><span style="color:#aaaaaa">"$type"</span><span style="color:#555555">: </span><span style="color:#00d7ff">"WeatherForecast"</span><span style="color:#555555">}</span><span style="color:#555555">, </span><span style="color:#555555">{</span><span style="color:#aaaaaa">"Date"</span><span style="color:#555555">: </span><span style="color:#5fffaf">"2024-07-11"</span><span style="color:#555555">, </span><span style="color:#aaaaaa">"Temperature"</span><span style="color:#555555">: </span><span style="color:#ff00d7">26</span><span style="color:#555555">, </span><span style="color:#aaaaaa">"$type"</span><span style="color:#555555">: </span><span style="color:#00d7ff">"WeatherForecast"</span><span style="color:#555555">}</span><span style="color:#555555">, </span><span style="color:#555555">{</span><span style="color:#aaaaaa">"Date"</span><span style="color:#555555">: </span><span style="color:#5fffaf">"2024-07-12"</span><span style="color:#555555">, </span><span style="color:#aaaaaa">"Temperature"</span><span style="color:#555555">: </span><span style="color:#ff00d7">31</span><span style="color:#555555">, </span><span style="color:#aaaaaa">"$type"</span><span style="color:#555555">: </span><span style="color:#00d7ff">"WeatherForecast"</span><span style="color:#555555">}</span><span style="color:#555555">, </span><span style="color:#555555">{</span><span style="color:#aaaaaa">"Date"</span><span style="color:#555555">: </span><span style="color:#5fffaf">"2024-07-13"</span><span style="color:#555555">, </span><span style="color:#aaaaaa">"Temperature"</span><span style="color:#555555">: </span><span style="color:#ff00d7">19</span><span style="color:#555555">, </span><span style="color:#aaaaaa">"$type"</span><span style="color:#555555">: </span><span style="color:#00d7ff">"WeatherForecast"</span><span style="color:#555555">}</span><span style="color:#555555">, </span><span style="color:#555555">{</span><span style="color:#aaaaaa">"Date"</span><span style="color:#555555">: </span><span style="color:#5fffaf">"2024-07-14"</span><span style="color=#555555">, </span><span style="color:#aaaaaa">"Temperature"</span><span style="color:#555555">: </span><span style="color:#ff00d7">25</span><span style="color=#555555">, </span><span style="color:#aaaaaa">"$type"</span><span style="color=#555555">: </span><span style="color:#00d7ff">"WeatherForecast"</span><span style="color:#555555">}</span><span style="color:#555555">]</span>
</pre></div>

### Step 3. Optionally, add scope information

In the examples above, we wrote the logs as text to the console. Serilog supports several _semantic_ backends like Elasticsearch or the .NET Aspire dashboard, where Serilog messages are stored as JSON objects. This allows you to easily search and filter messages based on object queries rather than text. This is what is meant by _semantic logging_.

Serilog supports several mechanisms to add properties to the JSON payload. Often, you'll want to search according to the _execution context_ (the current username, client IP, operation, etc.), so it's a good idea to _enrich_ messages with these properties.

To enable this feature, the first thing to do is add a call to `.Enrich.FromLogContext()` in your Serilog configuration code.

If you want to visualize properties in the console or text output, you also need to override the console message template.

These two steps are achieved by the following snippet:

```csharp
Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .WriteTo.Console(
        outputTemplate:
        "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}" )
```

We can now add contextual information to our logging.

The first and standard way is to use the `BeginScope` method of the `Microsoft.Extensions.Logging.ILogger` interface:

```csharp
using ( logger.BeginScope( "Getting weather forecast for {ScopeDays} days", days ) )
```

This approach defines a _text_ property named `Scope`, which is not very semantically searchable.

Serilog's proprietary API offers a better solution: adding properties to the current execution context using the `LogContext.PushProperty` static method. You can use it from anywhere. If you want to enrich the logs with information about the current HTTP request, it's best to create an [ASP.NET Middleware](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware). A middleware is a C# class with a single method `InvokeAsync` that gets called in the request pipeline, _before_ any controller or API is called. Here is an example that pushes two properties: `Client` and `RequestId`.

```csharp
public sealed class PushPropertiesMiddleware : IMiddleware
{
    public async Task InvokeAsync( HttpContext context, RequestDelegate next )
    {
        var requestId = Guid.NewGuid().ToString();

        using ( LogContext.PushProperty( "Client", context.Request.Host ) )
        using ( LogContext.PushProperty( "RequestId", requestId ) )
        {
            await next( context );
        }
    }
}
```

To add the middleware to the pipeline, you must first add it to the service collection:

```csharp
builder.Services.AddSingleton<PushPropertiesMiddleware>();
```

Then, call the `UseMiddleware` method.

```csharp
app.UseMiddleware<PushPropertiesMiddleware>();
```

Keep in mind that using a middleware is just one of the options. You can call `LogContext.PushProperty` from anywhere.

## Automatically adding verbose logging to your code

Adding logging to your code by hand makes sense for low-volume, high-relevance pieces of information such as errors, warnings, and important messages. However, it would be a severe waste of time for more noisy messages. If all you want is better request tracing, you can write messages from your custom [middleware](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0). If you want logging deep inside your application and still avoid boilerplate, you can use a free and open-source code generation tool like [Metalama](https://www.postsharp.net/metalama).

With Metalama, you can create a _template_ (called an _aspect_) that teaches the compiler how to implement logging, then apply the templates to all required methods.

First, install the [Metalama.Extensions.DependencyInjection](https://www.nuget.org/packages/Metalama.Extensions.DependencyInjection). Most of the magic is done by the [Metalama.Framework](https://www.nuget.org/packages/Metalama.Framework) package, which is implicitly added.

Then, create an [aspect](https://www.postsharp.net/solutions/aspect-oriented-programming) to fit your logging needs. Here's a simple example:

```csharp
using Metalama.Extensions.DependencyInjection;
using Metalama.Framework.Aspects;
using Microsoft.Extensions.Logging;

public class LogAttribute : OverrideMethodAspect
{
    [IntroduceDependency]
    private readonly ILogger _logger;

    public override dynamic? OverrideMethod()
    {
        var formatString = BuildFormatString();

        var isLoggingEnabled = this._logger.IsEnabled( LogLevel.Trace );

        try
        {
            if ( isLoggingEnabled )
            {
                this._logger.LogTrace(
                    formatString + " started.",
                    (object[]) meta.Target.Parameters.ToValueArray() );
            }

            return meta.Proceed();
        }
        finally
        {
            if ( isLoggingEnabled )
            {
                this._logger.LogTrace(
                    formatString + " finished.",
                    (object[]) meta.Target.Parameters.ToValueArray() );
            }
        }
    }

    [CompileTime]
    private static string BuildFormatString()
    {
        var parameters = meta.Target.Parameters
            .Where( x => x.RefKind != RefKind.Out )
            .Select( p => $"{p.Name}: {{{p.Name}}}" );

        return $"{meta.Target.Type}.{meta.Target.Method.Name}({string.Join( ", ", parameters )})";
    }
}
```

You can now add the aspect to all methods you want to log. You can mark methods one at a time using the `[Log]` attribute. Another option is to use [a Metalama fabric](https://doc.postsharp.net/metalama/conceptual/using/fabrics) (a commercial feature) to add logging more broadly using a single piece of code.

```csharp
using Metalama.Framework.Code;
using Metalama.Framework.Fabrics;

internal class Fabric : ProjectFabric
{
    public override void AmendProject( IProjectAmender amender )
    {
        amender
            .Select( c => c.GlobalNamespace.GetDescendant( "SerilogInAspNetCore.Controllers" )! )
            .SelectMany( c => c.Types )
            .Where( t => t.Accessibility == Accessibility.Public )
            .SelectMany( c => c.Methods )
            .Where( m => m.Accessibility == Accessibility.Public )
            .AddAspectIfEligible<LogAttribute>();
    }
}
```

When applied to a `WeatherForecastController.Get(int days)` method (whether using an attribute or a fabric), the aspect generates code equivalent to the following at the start of the method:

```csharp
var isLoggingEnabled = this._logger.IsEnabled( LogLevel.Trace );

try
{
    if ( isLoggingEnabled )
    {
        this._logger.LogTrace( "WeatherForecastController.Get(days: {days}) started.", days );
    }

    // Method body here.
}
finally
{
    if ( isLoggingEnabled )
    {
       this._logger.LogTrace( "WeatherForecastController.Get(days: {days}) finished.", days );
    }
}
```

## Summary

Serilog is an excellent semantic logging provider and it's fully compatible with ASP.NET Core. In this article, we discussed how to set up Serilog, and how to use it in your code in different scenarios, including the use of an ASP.NET middleware. We mentioned Metalama, the free and open-source meta-programming framework, as a possible tool to add verbose logging to your code without boilerplate.

---

## Site navigation

- Index of the whole site: [llms.txt](../llms.txt)

