Serilog

Serilog is an alternative logging implementation that plugs into ASP.NET Core.

“Like many other libraries for .NET, Serilog provides diagnostic logging to files, the console, and elsewhere. It is easy to set up, has a clean API, and is portable between recent .NET platforms.”

Setup for .Net Core 3

  1. Install packages
1
dotnet add package Serilog.AspNetCore
  1. Initialization with top-level try/catch block in Program.cs
  • Doing it this way catches any start up errors and closes high level streams with CloseAndFlush
  • Log.x static instances can also be used through-out the application however Im a fan of rather using dependancy injection so the coupling is to the ILogger interface.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();

try
{
Log.Information("START UP");
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "APPLICATION EXEPTION AT START UP");
}
finally
{
Log.CloseAndFlush();
}
}
  1. Remove LoggerConfiguration instantiation from Main in Program.cs and rather use UseSerilog() in CreateHostBuilder(). This comes from Serilog.Settings.Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void Main(string[] args)
{
try
{
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "APPLICATION EXEPTION AT START UP");
}
finally
{
Log.CloseAndFlush();
}
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(ConfigureAppLogging) // Serilog configuration is done in `ConfigureAppLogging` and not the `webBuilder`
.UseSerilog() // Sets Serilog as the logging provider.
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>(); // You dont have to do any manual DI in `Startup`, `UseSerilog()` above creates an instance of `ILogger<T>` for you
});

4.1 The Serilog configuration can then either be read from appSettings.json in the Serilog: key/value

1
2
3
4
5
6
private static void ConfigureAppLogging(IConfigurationBuilder config)
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(config.Build())
.CreateLogger();
}

This default config is from https://github.com/serilog/serilog-settings-configuration with some added configuration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
"MinimumLevel": "Debug",
"WriteTo": [ {
"Name": "Console",
"Args": {
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact",
}
},
{ "Name": "File", "Args": { "path": "Logs/log.txt" } }
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Destructure": [
{ "Name": "With", "Args": { "policy": "Sample.CustomPolicy, Sample" } },
{ "Name": "ToMaximumDepth", "Args": { "maximumDestructuringDepth": 4 } },
{ "Name": "ToMaximumStringLength", "Args": { "maximumStringLength": 100 } },
{ "Name": "ToMaximumCollectionCount", "Args": { "maximumCollectionCount": 10 } }
],
"Properties": {
"Application": "Sample"
}
}

Its a good idea to also include MinimumLevel -> Override overrides

1
2
3
"Override": {
"Microsoft": "Warning",
"System": "Error",

4.2 Alternatively if you dont have different configurations per environment you can just use Serilog‘s fluent api and configure the logging in code.

1
2
3
4
5
6
7
private static void ConfigureAppLogging(IConfigurationBuilder config)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console(new CompactJsonFormatter())
.WriteTo.File(@"Logs\log.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
}
  1. Delete all remnants of the default logger in appSettings.json, this is Logging key/values shown below.
1
2
3
4
5
6
7
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
  1. Add Serilog to the applications middleware pipeline in Startup.cs for the Configure() method, this will enable log events per request.
1
app.UseSerilogRequestLogging();
  1. You can then use the ILogger<T> instance using constructor injection and resolve with using Microsoft.Extensions.Logging;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FooController : Controller
{
private readonly ILogger<FooController> _logger;

public FooController(ILogger<FooController> logger)
{
_logger = logger;
}

public IActionResult Index()
{
_logger.LogInformation("FOO INDEX RUN!");
return View();
}
}

Exclude Logging

Tools like kubernetes need to know if the container is alive, the simplest way to do this is though a ping endpoint that doesnt have any authentication.

Logging these requests is just noise, so you can exclude this in your serilog settings configuration file using the Filter node and Serilog.Expressions.

1
2
3
4
5
6
7
8
9
"Serilog": {
"Using": ["Serilog.Expressions"],
"Filter": [
{
"Name": "ByExcluding",
"Args": {
"expression": "RequestPath like '/ping'"
}
}],

References