/Sample.Serilog

Some sample projects showing how to use Serilog

Primary LanguageC#MIT LicenseMIT

Serilog Samples

This repo contains a solution with a number of projects showing how to configure Serilog in them.

Writing logs

Serilog works like most other logging frameworks, with one extra feature that it can serialize objects and display their public properties; private properties and public/private fields are not serialized and written to the log though.

There are a couple things to keep in mind:

  1. If you want an object to be serialized so that it's public properties are written (rather than ToString() being called on the instance itself), prefix the name with @ or $.
  2. Do not use string interpolation (i.e. Log.Debug($"The time is {DateTime.Now}.") or manual string concatenation (i.e. Log.Debug("The time is " + DateTime.Now)) as it can severely hurt performance. Always use a template string and pass the variables in the params parameter (e.g. Log.Debug("The time is {Now}", DateTime.Now))

Here's an example of the various log levels and how you can log primitives and objects.

var structuredData = new StructuredData();
var simpleData = "This is a string.";

Log.Verbose("Here's a Verbose message.");
Log.Debug("Here's a Debug message. Only Public Properties (not fields) are shown on structured data. Structured data: {@sampleData}. Simple data: {simpleData}.", structuredData, simpleData);
Log.Information(new Exception("Exceptions can be put on all log levels"), "Here's an Info message.");
Log.Warning("Here's a Warning message.");
Log.Error(new Exception("This is an exception."), "Here's an Error message.");
Log.Fatal("Here's a Fatal message.");

For more information, see the official docs.

NuGet packages required

To be able to define the Serilog configuration in a json file, rather than hard-coding it, use:

  • Microsoft.Extensions.Configuration.Json (to read from appsettings.json file)
  • Serilog.Settings.Configuration (to read from Microsoft.Extensions.Configuration)

Then add NuGet packages for whatever sinks you want to use:

These NuGet packages may automatically be included by others. For example, the Serilog.AspNetCore package depends on and will automatically include all of the packages mentioned above, except for Serilog.Sinks.Async, so you only need to add it.

NOTE: If you forget to add the sinks NuGet packages, no logs will be written to any sinks.

Enricher to automatically attach additional data to log entries

The Enrichers NuGet packages are optional and only required if you want to automatically enrich all of your log entries with additional data.

There are many other Enricher NuGet packages for all sorts of things.

You can use the LogContext Enricher to attach custom properties to your logs, but it requires code changes to define what you want to attach. An example might be to include a custom TransactionId property to link all logs from a specific transaction.

There's also packages like Serilog.AspNetCore that can automatically attach web request IDs and other things to your logs.

Note: Not all sinks show enricher properties by default. See the Logging additional details section below.

Configuration

Keep config in a file instead of code

It's best practice to configure your logging in a separate file, rather than directly in code, so for all of the samples the configuration is set in the appsettings.json file.

Note: You must set the file property Copy to Output Directory to Copy if newer so that it gets copied to the app's bin directory and can be read by the app.

Logging additional details

The Enrich and Properties sections are pretty much the same; just the Enrich ones are out-of-the-box properties provided by the optional NuGet packages.

Some sinks do not display Properties (used by Enrichers) by default, such as the Console sink, and require you to explicitly define the output template and include Properties. e.g. the {Properties:j} in:

"WriteTo": [
    {
        "Name": "Console",
        "Args": {
            "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
            "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception} {Properties:j}{NewLine}"
        }
    }
],

Formatting the log output

See the docs for more information on configuring the outputTemplate.

File sync configuration

When using "rollingInterval": "Day" the date will automatically be appended to the file name.

Json configuration example

{
    "Serilog": {
        "MinimumLevel": "Verbose",
        "WriteTo": [
            {
                "Name": "Console",
                "Args": {
                    "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
                    "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:j}{NewLine}{Properties:j}{NewLine}{Exception}"
                }
            },
            {
                "Name": "File",
                "Args": {
                    "restrictedToMinimumLevel": "Warning",
                    "path": "Logs\\log.txt",
                    "rollingInterval": "Day",
                    "fileSizeLimitBytes": 10240,
                    "rollOnFileSizeLimit": true,
                    "retainedFileCountLimit": 30
                }
            }
        ],
        "Enrich": [ "FromLogContext", "WithMachineName", "WithExceptionDetails" ],
        "Properties": {
            "ApplicationName": "SampleApp",
            "Environment": "Int"
        }
    }
}

Logging synchronously can slow down your application, especially logging to the console, so you'll typically want to enable asynchronous logging by using the Async sink and providing the other sinks in it's Args, like so:

{
    "Serilog": {
        "MinimumLevel": "Verbose",
        "WriteTo": [
            {
                "Name": "Async",
                "Args": {
                    "configure": [
                        {
                            "Name": "Console",
                            "Args": {
                                "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
                                "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:j}{NewLine}{Properties:j}{NewLine}{Exception}"
                            }
                        },
                        {
                            "Name": "File",
                            "Args": {
                                "restrictedToMinimumLevel": "Warning",
                                "path": "Logs\\log.txt",
                                "rollingInterval": "Day",
                                "fileSizeLimitBytes": 10240,
                                "rollOnFileSizeLimit": true,
                                "retainedFileCountLimit": 30
                            }
                        }
                    ]
                }
            }
        ],
        "Enrich": [ "FromLogContext", "WithMachineName", "WithExceptionDetails" ],
        "Properties": {
            "ApplicationName": "SampleApp",
            "Environment": "Int"
        }
    }
}

Logging levels

As defined in the config docs, the logging levels are defined as:

  • Verbose
  • Debug
  • Information
  • Warning
  • Error
  • Fatal

NOTE: While the levels logged can vary per sink via the restrictedToMinimumLevel property, the MinimumLevel property defines the absolute minimum level logged. So if the MinimumLevel was set to Warning, sinks could never log Information, Debug, or Verbose logs.

Projects in the solution and what they show

AspNet6 project

This one also requires adding the Serilog.AspNetCore NuGet package. Using this package will automatically log additional ASP.Net information, such as information about every request that is made.

Files of interest:

  • Program.cs shows how to setup the global static Serilog logger and inject it into web host, as well as creating a bootstrap logger for logging messages during initialization.
  • Pages\Index.cshtml.cs shows how to bring an instance of the logger into your class via constructor dependency inject, and then use it to write logs.
  • appsettings.json shows how to configure Serilog, including specifying different minimum log levels for Microsoft, Microsoft.AspNetCore, and System messages.

ConsoleAppNetCore3 project

Shows how to use native Serilog without any abstractions to log to the console and file. For best practice you shouldn't reference the Serilog.Log static class in all of your classes like shown in this example. A better alternative would be to inject an abstracted log interface instance into the class using dependency injection, or creating a new static class with similar logging methods and have only that class reference Serilog.Log. That way you can more easily swap out logging frameworks later if needed.

Files of interest:

AspNetCore3 project

This one also requires adding the Serilog.AspNetCore NuGet package. Using this package will automatically log additional ASP.Net information, such as information about every request that is made.

Files of interest:

  • Program.cs shows how to setup the global static Serilog logger and inject it into web host.
  • Pages\Index.cshtml.cs shows how to bring an instance of the logger into your class via constructor dependency inject, and then use it to write logs.
  • appsettings.json shows how to configure Serilog.

ConsoleAppNetCore3UsingMsLoggingAbstraction

This one also required adding the Serilog.Extensions.Logging, Microsoft.Extensions.Hosting, and Microsoft.Extensions.DependencyInjection NuGet packages.

This project uses the Microsoft dependency injection and logging abstractions to inject an ILogger<T> into the class that will write the logs. This is a more proper way to make a console app than the ConsoleAppNetCore3 project.

Files of interest:

  • Program.cs shows how to set the global static Serilog logger and inject it into a host so that it can be used by dependency injection. It also shows how to have the host run as a console application, and how you instruct it which class should be ran; in this example it is TheApp class.
  • ClassThatLogs.cs shows how to bring an instance of the logger into your class via constructor dependency injection, and then use it to write logs.
  • TheApp.cs shows how to create the "entry point" of your app by having it implement Microsoft.Extensions.Hosting.IHostedService, which gets ran by the Host created in Program.cs.

Additional Info

This blog provides a lot of good info.