Problem
How to work with structured logging in ASP.NET Core and Serilog.
Solution
Starting from the previous post on logging, add NuGet packages:
Serilog.AspNetCore
Serilog.Sinks.Literate
Serilog.Sinks.Seq
In Program.cs, configure Serilog using its LoggerConfiguration
class and storing an instance of ILogger
(returned by CreateLogger
) in Serilog’s static
Log
class:
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.LiterateConsole()
.CreateLogger();
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup><startup>()
.UseSerilog()
.Build();
</startup>
Using the ILogger
is the same as our previous post, however, with Serilog, we can do structured logging:
public async Task Invoke(HttpContext context)
{
var message = new
{
GreetingTo = "James Bond",
GreetingTime = "Morning",
GreetingType = "Good"
};
this.logger.LogInformation("Inoke executing {@message}", message);
await context.Response.WriteAsync("Hello Logging!");
this.logger.LogInformation(
"Inoke executed by {developer} at {time}", "Tahir", DateTime.Now);
}
Running the application will show messages in Console window:
Discussion
Structured logging is a technique to include semantic information as part of the messages being logged. This helps ‘machine readability’ of these messages and tools can be written to analyse raw log messages and produce interesting information.
Serilog uses message template, similar to string.Format()
in .NET. Few interesting aspects of template syntax are:
- Use
{}
to enclose property names e.g. {developer}
in the above solution. These will be stored as metadata and can be queried using structured data storage (e.g. Seq, Azure). - Use
@
to preserve object structure, e.g., in the solution above, the anonymous object is serialised into JSON representation.
Enrichers
In Serilog, enrichers are used to attach information with every log event that can then be used by structured data storage (e.g. Seq, Azure) for viewing and filtering. A simple way to do this is by using .Enrich.WithProperty()
when configuring Serilog:
Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("ApiVersion", "1.2.5000")
.WriteTo.LiterateConsole()
.CreateLogger();
Context
As we saw in the previous post, a category can be attached to the logged messages, which normally is the fully qualified name of the class. This information could be used by structured data storage (e.g. Seq, Azure). Serilog provides this mechanism by attaching Context via ForContext()
method:
Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("ApiVersion", "1.2.5000")
.WriteTo.LiterateConsole()
.CreateLogger()
.ForContext<HelloLoggingMiddleware>();
Sinks
Sinks in Serilog refer to destination of log messages, e.g., file, database or console (in our example). There are several sinks available (refer to the link below). I’ll use Seq as an example sink to show how all the metadata we’ve added is available in a structured storage:
Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("ApiVersion", "1.2.5000")
.WriteTo.LiterateConsole()
.WriteTo.Seq("http://localhost:5341")
.CreateLogger()
.ForContext<HelloLoggingMiddleware>();
Notice how data we added via enricher, context and custom object appears as key/value pairs. This can now be used for filtering data and creating dashboards within Seq.
Note: Refer to Seq website (link below) for installation instructions (it’s very simple!).
Update (04-Sep-2017): I've updated the article and source code to use the new Serilog.AspNetCore
package for ASP.NET Core 2.0 (Thank you Nicholas Blumhardt for bringing this to my attention).
Useful Links