Hi! Welcome back to another post of the Structured Logging with ASP.NET Series. This time we’ll see how to write the code to integrate Serilog in our applications and how to search for the logs in Loki.

Last time we talked about the necessary infrastructure to host our log entries in a proper way. And by “proper” I mean something that lets us search, run queries and possibly even create charts and graphs.

So today we’ll start immediately with the code. Let’s start with how we can plug Serilog into our system.

First of all, make sure to add these two NuGet packages to your ASP.NET Web API project:

<ItemGroup>    
    <PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
    <PackageReference Include="Serilog.Sinks.Loki" Version="2.0.0" />
  </ItemGroup>

Then open your Program.cs and update the Host creation like this:

Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }).UseSerilog((ctx, cfg) =>
                {
                    var credentials = new NoAuthCredentials(ctx.Configuration.GetConnectionString("loki"));

                    cfg.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
                        .Enrich.FromLogContext()
                        .Enrich.WithProperty("Application", ctx.HostingEnvironment.ApplicationName)
                        .Enrich.WithProperty("Environment", ctx.HostingEnvironment.EnvironmentName)
                        .WriteTo.LokiHttp(credentials);

                   if(ctx.HostingEnvironment.IsDevelopment())
                       cfg.WriteTo.Console(new RenderedCompactJsonFormatter());
                });

Few things to note here. First of all, I’m logging to both Console and Loki. It’s up to you, feel free to disable it.

I am also “enriching” all the log entries with few additional properties, like “Application” and “Environment“. They’ll make our life easier later on when querying.

Secondly, I’m overriding the configuration for the “Microsoft” namespace to “Warning”. The reason is pretty simple: the API pipeline is quite “chatty” and generates a lot of log entries. Now, to be clear, those definitely have some value, but in most of the cases you want your log entries to show up, not the ones from the libraries your system depends on.

Just to give you an idea of the difference, here’s a screenshot of what would happen if we log everything:

This instead is what happens when we filter Microsoft logs:

The next step is to inject the Serilog middleware into the pipeline. Open the file Startup.cs and update it like this:

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
    if (env.IsDevelopment()){
        app.UseDeveloperExceptionPage();
     }
     app.UseSerilogRequestLogging();
     // add all the other middlewares here
}

Serilog should be plugged in before most of the other middlewares, otherwise it won’t be able to capture their entries.

And we’re done with the initialization. Now it comes the fun part: writing our logs!

Technically speaking, it doesn’t change much. I mean, you’ll keep using the same methods but in a slightly different manner.

Let’s take a look at CustomerDetailsHandler.cs (code omitted for brevity):

public class CustomerDetailsHandler {
    public async Task Handle(EventReceived<CustomerCreated> @event, CancellationToken cancellationToken)
    {
        _logger.LogInformation("creating customer details for {AggregateId} ...", @event.Event.AggregateId);

We basically assigned a label to the value of @event.Event.AggregateId , this way we can later on leverage the Loki’s querying capabilities and search for all the log entries for a given Aggregate:

And this is the real power of Structured Logging, and it all resides in attaching a local context and the proper labels to the entries.