Structured Logging In ASP.NET Core With Serilog
Structured logging is a practice where you apply the same message format to all of your application logs. The end result is that all your logs will have a similar structure, allowing them to be easily searched and analyzed.
Serilog is a popular logging library in .NET, packed with many features. It provides logging to files, logging to the console, and elsewhere.
However, why Serilog is unique is because it comes with support for structured logging out of the box.
Let's see how we can install Serilog and configure it an ASP.NET Core application.
Installing Serilog
To install Serilog in ASP.NET Core you can add the following NuGet package:
Install-Package Serilog.AspNetCore
This NuGet packages comes with a simple API to integrate Serilog into your application. You can call the UseSerilog
method on the HostBuilder
instance to provide a lambda method to configure Serilog.
I think the most flexible way to configure Serilog is through application settings, which is achieved by calling ReadFrom.Configuration()
.
You can also call the UseSerilogRequestLogging()
method to introduce automatic HTTP request logging in your API.
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((context, configuration) =>
configuration.ReadFrom.Configuration(context.Configuration));
var app = builder.Build();
app.UseSerilogRequestLogging();
app.Run();
The next question is how do you provide the actual configuration values to Serilog?
Configuring Serilog With appsettings.json
You need to add a Serilog
section in your appsettings.json
file.
Here you can configure, among other things:
Which sinks to use with Serilog
Override default and minimum log levels
Configure file logging arguments
In this example, we're adding the Console
and File
sinks to Serilog. And we're adding some additional configuration for the File
sink in the Serilog.WriteTo
configuration section. We can configure the output path for the log files, the naming format, which formatter to use for the logs and so on.
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": {
"path": "/logs/log-.txt",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
}
You can get a more detailed overview of what's supported with the Serilog.Configuration
library in the documentation.
Using Serilog In ASP.NET Core
We managed to successfully install and configure Serilog. But how do we actually use it?
Serilog integrates with the ILogger
interaface coming from the Microsoft.Extensions.Logging
namespace. If you're already using ILogger
for logging, everything will continue working correctly.
Here's a simple example of logging inside of a Minimal API endpoint:
app.MapGet("/serilog-is-cool", (ILogger logger) =>
{
logger.LogInformation("This is a log inside of the Minimal API endpoint.");
return Results.Ok(new { Message = "success" });
});
You just inject an ILogger
or ILogger<T>
instance and Serilog will provide its own implementation at runtime.
Structured Logging Syntax
The idea behind structured logging is that you can introduce additional contextual information inside of your logs. Serilog does this using a message template syntax, where you can specify named parameters and then pass in their values separately.
Here's an example of what this message template would look like. You specify parameters inside of curly bracers and provide a name, for example {NamedParameter}
. The value provided for the parameter will be serialized as a property inside of the corresponding structured log.
var book = new { Author = "Domain-Driven Design", Title = "Eric Evans" };
var orderNumber = 1;
log.LogInformation(
"Processing book {@Book}, order number = {@OrderNumber}",
book,
orderNumber);
There are a few things to unpack here:
{@Book}
parameter which accepts an object{OrderNumber}
parameter which accepts a scalar value
The @
operator in front of Book
tells Serilog to serialize the object passed in, instead of converting it using ToString()
.
Benefits Of Structured Logging
Lastly, I want to highlight what are some of the benefits of structured logging and why you should be using it.
As I said at the beginning, the main idea with structured logging is that all log message follow the same structure. This structure can be a JSON document for example, or a row in a relational table. Since structured logs are in a machine-readable format, it's much easier to search through them for specific information.
When an error occurs, structured logs can provide more context and details about the error, making it easier to identify the root cause and fix the problem.
It's very easy to start doing structured logging with Serilog, and I hope you'll give it a try.
See you next week, and have an excellent Saturday.
P.S. Whenever you’re ready, there are 2 ways I can help you:
Pragmatic Clean Architecture: This comprehensive course will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture. Join 950+ students here.
Patreon Community: Think like a senior software engineer with access to the source code I use in my YouTube videos and exclusive discounts for my courses. Join 820+ engineers here.
Subscribe to my newsletter
Read articles from Milan Jovanović directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Milan Jovanović
Milan Jovanović
I'm a seasoned software architect and Microsoft MVP for Developer Technologies. I talk about all things .NET and post new YouTube videos every week.