The Serilog MinimumLevel setting determines at which level log events are generated:

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.LiterateConsole()
    .CreateLogger();

Log.Information("This is printed");
// -> [09:00:15 INF] This is printed

Log.Debug("This is not printed");

Unlike a filter, which would exclude events only after fully constructing them, the minimum level setting turns statements like the Debug() event above into little more than a no-op, costing only a method call and a comparison.

This makes the minimum level an important performance feature: the ability to control which events are actually generated makes it possible to ship production code that’s fully-instrumented using Debug and Verbose-level logging, without incurring a big performance penalty when it’s turned off.

Serilog 1.x provides a single minimum level setting per logger. Historically, this was fine because most log events passing through Serilog were from the same source - the application under the programmer’s control - and so a similar level setting could reasonably be applied across the board.

With the arrival of ASP.NET Core, this is no longer the case. The integrated logging in .NET Core means that the framework itself - including all the middleware, ASP.NET MVC and Entity Framework Core - produces a lot of log events, and these need to be collected. Especially useful are the error events produced when an exception is unhandled, and warnings from the HTTP pipeline.

But, not everything the framework generates is needed all of the time. Here’s a typical Information-level event from ASP.NET MVC, collected with Serilog and shipped to Seq:

Typical ASP.NET Core log event in Seq

The expanded event Executed action WebSample.Controllers.HomeController.About (WebSample) in 4.9602ms has some interesting timing information produced by MVC that may help pinpoint performance issues, but if you’re already collecting whole-request timings it may be overkill to have this event, and others like it, enabled all of the time.

So, in Serilog 2.0 we added MinimumLevel.Override().

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .WriteTo.LiterateConsole()
    .CreateLogger();

The first argument of Override is a source context prefix, which is normally matched against the namespace-qualified type name of the class associated with the logger.

Using Serilog:

var myLog = Log.ForContext<MyClass>();
myLog.Information("Hello!");
// -> SourceContext is "MyNamespace.MyClass"

Using Microsoft.Extensions.Logging:

var myLog = loggerFactory.CreateLogger("MyNamespace.MyClass");
myLog.LogInformation("Hello!");
// -> SourceContext is "MyNamespace.MyClass"

The effect of the configuration above, then, is to generate events only at or above the Warning level when the logger is owned by a type in a Microsoft.* namespace.

You can specify as many overrides as you need by adding additional MinimumLevel.Override() statements:

    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .MinimumLevel.Override("System", LogEventLevel.Error)

JSON configuration

The Serilog.Settings.Configuration package adds JSON configuration support to Serilog on .NET Core.

To configure level overrides, instead of the simple "MinimumLevel": "Information" directive, specify a "Default" and map of "Override" values:

{
  "Serilog": {
    "MinimumLevel": {
        "Default": "Information",
        "Override": {
            "Microsoft": "Warning",
            "System": "Error"
        }
    }
  }
}

(Thanks to @skomis-mm for implementing support for this.)

Calling Serilog.Settings.Configuration “JSON” support is a little bit misleading; it works with the Microsoft.Extensions.Configuration subsystem wholesale, so you could also control the minimum level through it by setting an environment variable called Serilog:MinimumLevel, or with overrides using Serilog:MinimumLevel:Default and Serilog:MinimumLevel:Override:Microsoft.

Level switch support

But, you might ask, what about those occasions when we do need to collect framework events at Information or a lower level? Can we do that without a restart?

LoggingLevelSwitch works for level overides, too:

var frameworkSwitch = new LoggingLevelSwitch(LogEventLevel.Warning);

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .MinimumLevel.Override("Microsoft", frameworkSwitch)
    .WriteTo.LiterateConsole()
    .CreateLogger();

// Some time later, when Information events are required:
frameworkSwitch.MinimumLevel = LogEventLevel.Information;

The configuration system integration handles this automatically, so minimum level changes in JSON configuration are picked up without needing to restart the app.

What’s this 2.1 business?

Okay, I know I can’t get away with slipping that .1 in there without comment. Serilog is still moving forward, and at the time of writing, the current version is 2.1. There’s a bug fix included in the 2.1 release for minimum level overrides that is needed in order for them to work properly, so you should make sure you’re on the latest stable version to use this feature.

Happy logging!