A Professional ASP.NET Core API - Serilog
Serilog
is an alternative logging implementation that plugs into ASP.NET Core. It supports the same structured logging APIs, and receives log events from the ASP.NET Core framework class libraries, but adds a stack of features that make it a more appealing choice for some kinds of apps and environments.
Install the below packages
1 |
|
Serilog Enrichers
To enable Structured Logging and to unleash the full potential of Serilog
, we use enrichers
. These enrichers give you additional details like Machine Name, ProcessId, Thread Id when the log event had occurred for better diagnostics. It makes a developer’s life quite simpler..
Serilog Sinks
Serilog Sinks
in simpler words relate to destinations for logging the data. In the packages that we are going to install to our ASP.NET Core application, Sinks for Console
and File
are included out of the box. That means we can write logs to Console and File System without adding any extra packages. Serilog supports various other destinations like MSSQL
, SQLite
, SEQ
and more.
Logger Initialization
Exceptions thrown during start-up are some of the most disruptive errors your application might face, and so the very first line of code in our Serilog-enabled app will set up logging and make sure any nasties are caught and recorded.
1 |
|
Cleaning the default logger
The “Logging” section that you’ll find in appsettings.json
isn’t used by Serilog
, and can be removed:
1 |
|
After cleaning up here, the configuration looks like this:
1 |
|
Writing your own log events
You should use ILogger<T>
to log your own events as following
1 |
|
Log Levels
I also wanted you to know about the various Logging Levels. This is the fundamental concept of logging. When we wrote _logger.LogInformation("Hello, {Name}!", name);
, we mentioned to the application that this is a log with the log-level set to Information
. Log levels make sense because it allows you to define the type of log. Is it a critical log? just a debug message? a warning message?
There are 7 log-levels included :
Trace
: Detailed messages with sensitive app data.Debug
: Useful for the development environment.Information
: General messages, like the way we mentioned earlier.Warning
: For unexpected events.Error
: For exceptions and errors.Critical
: For failures that may need immediate attention.
Streamlined request logging
Our log output will now be rather quiet. We didn’t want a dozen log events per request, but chances are, we’ll need to know what requests the app is handling in order to do most diagnostic analysis.
To switch request logging back on, we’ll add Serilog
to the app’s middleware pipeline over in Startup.cs
. You’ll find a Configure()
method in there like the following:
1 |
|
We’ll be a bit tactical about where we add Serilog
into the pipeline. I tend not to want request logging for static files, so I add UseSerilogRequestLogging()
later in the pipeline, as shown above.
Read from configuration
Change Program.Main()
as following
1 |
|
Setting up Serilog
Add Serilog
section to appsettings.json
1 |
|
Useful Enrichers
Here’s how to add some of the most useful enrichers.
1 |
|
Change your Program.Main()
as following
1 |
|
For Serilog.Enrichers.CorrelationId
, you need to add AddHttpContextAccessor
too.
1 |
|
Enrich from global properties
You can also specify properties globally. In some cases, we need to calculate properties on startup, which can be done using the Fluent API:
1 |
|
Timings
Serilog’s support for structured data makes it a great way to collect timing information. It’s easy to get started with in development, because the timings are printed to the same output as other log messages (the console, files, etc.) so a metrics server doesn’t have to be available all the time.
Serilog Timings
is built with some specific requirements in mind:
One operation produces exactly one log event (events are raised at the completion of an operation)
Natural and fully-templated messages
Events for a single operation have a single event type, across both success and failure cases (only the logging level and Outcome properties change)
This keeps noise in the log to a minimum, and makes it easy to extract and manipulate timing information on a per-operation basis.
Install below packages
1 |
|
The simplest use case is to time an operation, without explicitly recording success/failure:
1 |
|
At the completion of the using block, a message will be written to the log like:
1 |
|
Operations that can either succeed
or fail
, or that produce a result, can be created with Operation.Begin()
:
1 |
|
Using op.Complete()
will produce the same kind of result as in the first example:
1 |
|
If the operation is not completed by calling Complete()
, it is assumed to have failed and a warning-level event will be written to the log instead:
1 |
|
In this case the Outcome property will be “abandoned”.
To suppress this message, for example when an operation turns out to be inapplicable, use op.Cancel()
. Once Cancel()
has been called, no event will be written by the operation on either completion
or abandonment
.
Levelling
Timings are most useful in production, so timing events are recorded at the Information level and higher, which should generally be collected all the time.
If you truly need Verbose
- or Debug
-level timings, you can trigger them with Operation.At()
or the OperationAt()
extension method on ILogger
:
1 |
|
When a level is specified, both completion and abandonment events will use it. To configure a different abandonment level, pass the second optional parameter to the At()
method.
Serilog & Kibana
Kibana
is an open source data visualization user interface for ElasticSearch. Think of ElasticSearch as the database and Kibana as the web user interface which you can use to build graphs and query data in ElasticSearch.
Installation
- Get Elasticsearch
- Get Kibana
- Start Elasticsearch:
bin/elasticsearch
- Start Kibana:
bin/kibana
- Open Kibana:
http://localhost:5601
ElasticSearch Sink
Install below package
1 |
|
After installation, add the following code:
1 |
|
And also in appsettings.json
1 |
|
Define index to Kibana
If this is your first run you will see Create index pattern
page so go to step 3 but if you had any index before, start form beggining:
- Run your application, Then browse the
Kibana
viahttp://localhost:5601/app/home/
. - Find
Connect to your Elasticsearch index
link and click on it. - After that, Click on
Create index pattern
and introduce our index format like below1
ASSEMBLYNAME-ENVIROMENT-* => myapp-development-*
- Click on
Next step
- Choose
@timestamp
, then click onCreate index pattern
- Now, You can go to
Discover
page and find your logs!
Reference(s)
Most of the information in this article has gathered from various references.
- https://nblumhardt.com/2019/10/serilog-in-aspnetcore-3/
- https://www.codewithmukesh.com/blog/serilog-in-aspnet-core-3-1/
- https://dejanstojanovic.net/aspnet/2018/october/extending-serilog-with-additional-values-to-log/
- https://github.com/nblumhardt/serilog-timings
- https://benfoster.io/blog/serilog-best-practices/
- https://esg.dev/posts/serilog-dos-and-donts/
- https://www.humankode.com/asp-net-core/logging-with-elasticsearch-kibana-asp-net-core-and-docker