When your application is running in production you need feedback, how will you otherwise know that your system is running as anticipated? This also has the added benefit of allowing you to backtrack which steps ended up in throwing and error.

This article shows how to setup Serilog in a ASP.NET Core Web App (Model-View-Controller) with .NET 6.0.

Packages

The Serilog logging framework is split into a couple of areas.

Serilog.AspNetCore - With this we can specify that our application should log through Serilog.

This package comes with a couple of important dependencies. Serilog defines its outputs as Sinks, so when we want to write to a file, we will use the Serilog.Sinks.File package. For a list of available sinks, consult the Serilog wiki. Just like the Serilog.Sinks, the package also includes Serilog.Settings.Configurations. Serilog logging can by default be configured programatically, but it is often prefered to specify logging behavior through your appsettings file, since your settings and locations can differ between environments.

Configuration

We want to introduce logging as early in the startup as possible, so that we can catch any initialization that could occur.

1. Initialize Serilog

At the top of Program.cs initialize the Logger:

using Serilog;

var configuration = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json")
        .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", true)
        .Build();

Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(configuration)
    .CreateLogger();

Log.Information("Initializing application");

We fetch the configuration from our appsettings.json file, and a potential environment specific appsettings.environment.json file.

2. Add try/catch/finally around the configuration of the Web Application

try {
    var builder = WebApplication.CreateBuilder(args);
    ...
    app.Run();
}
catch (Exception e)
{
    Log.Fatal(ex, "Host terminated unexpectedly");
}
finally {
    Log.CloseAndFlush();
}

3. Add Serilog to the WebApplicationBuilder

builder.Host.UseSerilog();

4. Specify logging target in appsettings.json

When a new Web Application is created, some default logging is already configured.

  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }

This can be replaced with the following

"Serilog": {
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "log.txt"
        }
      }
    ]
}

Serilog is now configured on your application. When you run it, a new file will be created corresponding to the path provided in the previous codeblock.

2022-11-22 21:08:29.509 +01:00 [INF] Initializing application
2022-11-22 21:08:34.213 +01:00 [INF] User profile is available. Using 'C:\Users\Selenium\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2022-11-22 21:08:34.286 +01:00 [INF] Creating key {2c23f4de-2f39-4f38-a34e-ff53ab00d1b8} with creation date 2022-11-22 20:08:34Z, activation date 2022-11-22 20:08:34Z, and expiration date 2023-02-20 20:08:34Z.
2022-11-22 21:08:34.297 +01:00 [INF] Writing data to file 'C:\Users\Selenium\AppData\Local\ASP.NET\DataProtection-Keys\key-2c23f4de-2f39-4f38-a34e-ff53ab00d1b8.xml'.
2022-11-22 21:08:34.825 +01:00 [INF] Now listening on: https://localhost:7257
2022-11-22 21:08:34.825 +01:00 [INF] Now listening on: http://localhost:5257
2022-11-22 21:08:34.829 +01:00 [INF] Application started. Press Ctrl+C to shut down.
2022-11-22 21:08:34.829 +01:00 [INF] Hosting environment: Development
2022-11-22 21:08:34.829 +01:00 [INF] Content root path: C:\git\elbodevops.code\dotnet-logging-with-serilog\LoggingWithSerilog\

While this is easy for a user to read through, it can be hard to search through. The Log output can be written as Json by adding "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog" to your File sink:

"Serilog": {
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "log.txt",
          "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
        }
      }
    ]
}

The Log output will now be in json, like so:

{"Timestamp":"2022-11-22T21:12:40.7511247+01:00","Level":"Information","MessageTemplate":"Initializing application"}
{"Timestamp":"2022-11-22T21:12:42.0916865+01:00","Level":"Information","MessageTemplate":"User profile is available. Using '{FullName}' as key repository and Windows DPAPI to encrypt keys at rest.","Properties":{"FullName":"C:\\Users\\Selenium\\AppData\\Local\\ASP.NET\\DataProtection-Keys","EventId":{"Id":63,"Name":"UsingProfileAsKeyRepositoryWithDPAPI"},"SourceContext":"Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager"}}
{"Timestamp":"2022-11-22T21:12:42.4111322+01:00","Level":"Information","MessageTemplate":"Now listening on: {address}","Properties":{"address":"https://localhost:7257","EventId":{"Id":14,"Name":"ListeningOnAddress"},"SourceContext":"Microsoft.Hosting.Lifetime"}}
{"Timestamp":"2022-11-22T21:12:42.4112357+01:00","Level":"Information","MessageTemplate":"Now listening on: {address}","Properties":{"address":"http://localhost:5257","EventId":{"Id":14,"Name":"ListeningOnAddress"},"SourceContext":"Microsoft.Hosting.Lifetime"}}
{"Timestamp":"2022-11-22T21:12:42.4136555+01:00","Level":"Information","MessageTemplate":"Application started. Press Ctrl+C to shut down.","Properties":{"SourceContext":"Microsoft.Hosting.Lifetime"}}
{"Timestamp":"2022-11-22T21:12:42.4137414+01:00","Level":"Information","MessageTemplate":"Hosting environment: {envName}","Properties":{"envName":"Development","SourceContext":"Microsoft.Hosting.Lifetime"}}
{"Timestamp":"2022-11-22T21:12:42.4137816+01:00","Level":"Information","MessageTemplate":"Content root path: {contentRoot}","Properties":{"contentRoot":"C:\\git\\elbodevops.code\\dotnet-logging-with-serilog\\LoggingWithSerilog\\","SourceContext":"Microsoft.Hosting.Lifetime"}}

It can now be fed into a separate system for scanning and monitoring.

Lastly, it will be cumbersome to have one big log for our application, spanning days, maybe even weeks. We can set a rolling interval, so a new file is created each day with "rollingInterval": "Day"

"Serilog": {
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "log.txt",
          "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
          "rollingInterval: "Day"
        }
      }
    ]
}

The Log output file will now have a date appended to its filename.