Skip to content

Configuration Basics

Nicholas Blumhardt edited this page Nov 29, 2021 · 23 revisions

Serilog uses a simple C# API to configure logging. When external configuration is desirable it can be mixed in (sparingly) using the Serilog.Settings.AppSettings package or Serilog.Settings.Configuration package.

Creating a logger

Loggers are created using a LoggerConfiguration object:

Log.Logger = new LoggerConfiguration().CreateLogger();
Log.Information("No one listens to me!");

// Finally, once just before the application exits...
Log.CloseAndFlush();

The example above will create a logger that does not record events anywhere. To see log events, a sink must be configured.

Sinks

Log event sinks generally record log events to some external representation, typically the console, a file or data store. Serilog sinks are distributed via NuGet. A curated list of available sinks is listed here on the wiki.

This example will use the console sink package, which pretty-prints log data, and the file sink package, which writes log events to a set of date-stamped text files.

$ dotnet add package Serilog.Sinks.Console
$ dotnet add package Serilog.Sinks.File

Sinks are configured using the WriteTo configuration object.

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger();

Log.Information("Ah, there you are!");

Multiple sinks can be active at the same time. Adding additional sinks is a simple as chaining WriteTo blocks:

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.File("log-.txt", rollingInterval: RollingInterval.Day)
    .CreateLogger();

Output templates

Text-based sinks use output templates to control formatting. this can be modified through the outputTemplate parameter:

    .WriteTo.File("log.txt",
        outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")

The default template, shown in the example above, uses built-in properties like Timestamp and Level. Properties from events, including those attached using enrichers, can also appear in the output template.

The {Message:lj} format options cause data embedded in the message to be output in JSON (j) except for string literals, which are output as-is.

For more compact level names, use a format such as {Level:u3} or {Level:w3} for three-character upper- or lowercase level names, respectively.

Add {Properties:j} to the output template to include additional context information.

Minimum level

Serilog implements the common concept of a 'minimum level' for log event processing.

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .CreateLogger();

The MinimumLevel configuration object provides for one of the log event levels to be specified as the minimum. In the example above, log events with level Debug and higher will be processed and ultimately written to the console.

Level Usage
Verbose Verbose is the noisiest level, rarely (if ever) enabled for a production app.
Debug Debug is used for internal system events that are not necessarily observable from the outside, but useful when determining how something happened.
Information Information events describe things happening in the system that correspond to its responsibilities and functions. Generally these are the observable actions the system can perform.
Warning When service is degraded, endangered, or may be behaving outside of its expected parameters, Warning level events are used.
Error When functionality is unavailable or expectations broken, an Error event is used.
Fatal The most critical level, Fatal events demand immediate attention.

Default Level - if no MinimumLevel is specified, then Information level events and higher will be processed.

Overriding per sink

Sometimes it is desirable to write detailed logs to one medium, but less detailed logs to another.

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.File("log.txt")
    .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Information)
    .CreateLogger();

In this example debug logs will be written to the rolling file, while only Information level logs and higher will be written to the console.

All provided sinks support the restrictedToMinimumLevel configuration parameter.

Logger vs. sink minimums - it is important to realize that the logging level can only be raised for sinks, not lowered. So, if the logger's MinimumLevel is set to Information then a sink with Debug as its specified level will still only see Information level events. This is because the logger-level configuration controls which logging statements will result in the creation of events, while the sink-level configuration only filters these. To create a single logger with a more verbose level, use a separate LoggerConfiguration.

Enrichers

Enrichers are simple components that add, remove or modify the properties attached to a log event. This can be used for the purpose of attaching a thread id to each event, for example.

class ThreadIdEnricher : ILogEventEnricher
{
    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
                "ThreadId", Thread.CurrentThread.ManagedThreadId));
    }
}

Enrichers are added using the Enrich configuration object.

Log.Logger = new LoggerConfiguration()
    .Enrich.With(new ThreadIdEnricher())
    .WriteTo.Console(
        outputTemplate: "{Timestamp:HH:mm} [{Level}] ({ThreadId}) {Message}{NewLine}{Exception}")
    .CreateLogger();

The configuration above shows how a property added by an enricher can be used in output formatting.

If the enriched property value is constant throughout the application run, the shortcut WithProperty method can be used to simplify configuration.

Log.Logger = new LoggerConfiguration()
    .Enrich.WithProperty("Version", "1.0.0")
    .WriteTo.Console()
    .CreateLogger();

Enrichers and the properties they attach are generally more useful with sinks that use structured storage, where the property values can be viewed and filtered.

Filters

Events can be selectively logged by filtering. Filters are just predicates over LogEvent, with some common scenarios handled by the Matching class.

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .Filter.ByExcluding(Matching.WithProperty<int>("Count", p => p < 10))
    .CreateLogger();

Sub-loggers

Sometimes a finer level of control over what is seen by a sink is necessary. For this, Serilog allows a full logging pipeline to act as a sink.

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.Logger(lc => lc
        .Filter.ByIncludingOnly(...)
        .WriteTo.File("log.txt"))
    .CreateLogger();

For scenarios not handled well by sub-loggers, it's fine to create multiple independent top-level pipelines. Only one pipeline can be assigned to Log.Logger, but your app can use as many additional ILogger instances as it requires.

Note that destructuring policies will not have an effect if they are specified inside the WriteTo.Logger() callback since sub-loggers work with already created LogEvents.