Working with Duende IdentityServer customers, we’ve noticed many developers adopting Serilog as their preferred logging framework, in addition to the ILogger
abstractions found in ASP.NET Core. There’s a lot to love about the simple yet powerful logging library built with powerful structured event data in mind: Easy installation, straightforward configuration, multiple target sinks, fantastic documentation, and a large community of .NET developers.
It’s so great that we ship it as part of our templates to help developers adopt what we view as a great approach to logging information, warnings, and, in rare cases, exceptions.
This post highlights an underrated feature of the Serilog family of extensions: Serilog Expressions.
How to use Serilog?
Before diving into the Serilog Expressions library, let’s first look at how you may incorporate Serilog into your existing ASP.NET Core applications, which is similar to how our Duende IdentityServer templates do as well.
You’ll first need to install the Serilog.AspNetCore
package to your web application project.
dotnet add package Serilog.AspNetCore
From here, we’ll want to start with a simple Serilog configuration in our appSettings.json
file to help set a baseline for all logged messages.
{
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.Authentication": "Debug",
"System": "Warning"
}
}
}
}
Your configuration needs a top-level Serilog
entry, which we’ll use in our Program.cs
file.
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((ctx, lc) => lc
.WriteTo.Console(
outputTemplate:
"[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
formatProvider: CultureInfo.InvariantCulture)
.Enrich.FromLogContext()
.ReadFrom.Configuration(ctx.Configuration));
With this configuration change, all ILogger
instances will now be instances of SerilogLogger
. You can change the registration and configuration to meet your application needs and suit your deployment use cases.
Now that we have Serilog, how do we filter messages based on expressions?
Serilog Expressions and Skip Logging Exceptions
Duende IdentityServer customers often deploy to public cloud environments, where their instances may intermittently experience transient errors. These errors are unavoidable for many reasons, including cloud providers experiencing sudden networking issues, bots crawling and disconnecting from public endpoints, and many other non-critical problems that cause bloat in your logs.
Reducing noise in your production logs can help you quickly find meaningful exceptions and take actions to fix those issues. With Serilog Expressions, you can filter out annoying exceptions using a mini-language.
To get started, you’ll first need to install the Serilog.Expressions
package.
dotnet add package Serilog.Expressions
Next, confirm that we will log exceptions by adding a new /oops
endpoint to our ASP.NET Core application.
app.MapGet("/oops", (ILogger<Program> logger) =>
{
var ex = new Exception("DUENDEOOPSIES: NOT A REAL ERROR");
logger.LogError(ex, "oops");
return Results.Ok("It's All Good!");
});
Running our instance of Duende IdentityServer, we should see the following exception in our console output.
[12:01:02 Error] Program
oops
System.Exception: DUENDEOOPSIES: NOT A REAL ERROR
Let’s filter this out by adding a Filter
section to our Serilog configuration. Don’t forget to add the Using
element, which allows Serilog to load the library dependency at runtime.
{
"Serilog": {
"Using": [ "Serilog.Expressions" ],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.Authentication": "Debug",
"System": "Warning"
}
},
"Filter": [
{
"Name": "ByExcluding",
"Args": {
"expression": "Inspect(@x).Message like 'DUENDEOOPSIES:%'"
}
}
]
}
}
The critical part is using the ByExcluding
filter method to pass an expression. The mini-language syntax provides an Inspect
keyword, which allows us to unwrap the exception and then compare the message to a known string and a suffixed wildcard.
After rerunning the application, you can see that while the code at the endpoint remains unchanged, the console output no longer displays the exception.
Conclusion
While the example in this post is an everyday use case for Duende IdentityServer customers, the Serilog Expression library has an impressive syntax that should help anyone adopt Serilog filter logging messages. For more information, we recommend checking out the Serilog Expressions language reference on the GitHub page.
If you already use Serilog and Serilog Expressions in your production environment, please comment below and let us know what other valuable expressions you’ve deployed.