Updated on

Serilog has six log levels, from least to most severe: Verbose, Debug, Information, Warning, Error, and Fatal. Serilog’s own configuration documentation states the default: “if no MinimumLevel is specified, then Information level events and higher will be processed” (Serilog wiki, Configuration Basics, read 2026-08-09). That is also the sensible production default for application logs.

In this guide, we set minimum levels globally, per namespace, and per sink: the three controls that decide what actually gets written.

To download the source code for this article, you can visit our GitHub repository.

Setting Up Serilog

Before we can take a look at the different logging levels, we need to configure our project to use Serilog:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSerilog(options =>
{
    options.MinimumLevel.Information()
           .WriteTo.Console(LogEventLevel.Information);
});

This snippet assumes using Serilog; and using Serilog.Events; at the top of the file. The first positional argument to WriteTo.Console(LogEventLevel.Information) is restrictedToMinimumLevel, worth naming explicitly since we don’t introduce that setting until later in this article.

For a more in-depth look at using Serilog, be sure to check out our article Structured Logging in ASP.NET Core with Serilog.

Let’s start with the log levels.

What Are the Different Log Levels in Serilog?

Log levels allow us to categorize the seriousness of the logged event. Serilog supports six logging levels:

Serilog categorizes every log event with one of six levels, ordered by severity: Verbose, Debug, Information, Warning, Error, and Fatal. Verbose is the noisiest: internal flow and raw payloads we only enable while chasing a specific problem. Debug carries diagnostic state useful to developers but not to operations.

Information records the normal life of the application: requests handled, orders placed, jobs completed. Warning flags situations that are unexpected but survivable, such as a retry or a fallback. Error means an operation failed and somebody should look at it, while Fatal means the application itself cannot continue.

Serilog writes an event only when its level is at or above the configured minimum: Information by default. The names differ slightly from Microsoft’s ILogger levels: Serilog’s Verbose corresponds to Trace, and Fatal corresponds to Critical; the other four match one to one.

Serilog levelILogger methodUse it forLog it in production?
VerboseLogTrace()Tracing internals, loops, payloadsNo
DebugLogDebug()Diagnostic state for developersNo (enable on demand)
InformationLogInformation()Business events, request flowYes
WarningLogWarning()Unexpected but handled situationsYes
ErrorLogError()Failed operations that need attentionYes
FatalLogCritical()Crashes, unrecoverable statesYes (alert on it)

Serilog has no Critical level and no Trace level; Fatal and Verbose are its equivalents, and the mapping above is exactly how Serilog.Extensions.Logging translates them.

As the log level increases from Verbose to Fatal, its significance also increases.

Generating Log Messages at Different Log Levels

ASP.NET Core provides the ILogger<T> interface for logging scenarios regardless of which logging infrastructure we use. We simply inject the ILogger<T> interface into our constructor and straightaway we can begin logging.

With this in mind, let’s take a look at generating logs for each log level:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        _logger.LogTrace("Trace Log Message");
        _logger.LogDebug("Debug Log Message");
        _logger.LogInformation("Information Log Message");
        _logger.LogWarning("Warning Log Message");
        _logger.LogError("Error Log Message");
        _logger.LogCritical("Critical Log Message");

        return View();
    }
}

As we mentioned earlier, in the HomeController constructor, we inject the ILogger<HomeController> interface, which we use to initialize our _logger instance. Then, in our Index() method, using our _logger, we log a message at each of the supported log levels.

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

Serilog reaches this interface through Serilog.Extensions.Logging; we cover the full relationship, including using Serilog with Microsoft’s ILogger API and how ILogger, ILoggerFactory, and ILoggerProvider fit together, in dedicated articles.

How Do We Set the Minimum Log Level in Serilog?

Serilog gives us three places to control the minimum level, and they apply in this order. First, MinimumLevel.Default (or MinimumLevel.Information() in the fluent API) sets the global floor: events below it are never created, so they cost almost nothing.

Second, MinimumLevel.Override raises the floor for specific namespaces; overriding Microsoft and System to Warning is the standard way to silence framework noise while keeping our own Information events. Third, restrictedToMinimumLevel filters per sink, so the console can show Information while the file sink records only Warning and above.

The global minimum wins first: a sink can be more restrictive than the global level, never less. For levels we want to change at runtime without redeploying, we pass a LoggingLevelSwitch to MinimumLevel.ControlledBy() and flip it at runtime from an admin endpoint or a configuration reload, with no redeploy, no restart, and the change effective immediately on every sink.

Logging is an essential part of our applications. Through the data it provides, we can detect failures, problems, and performance issues in our code. However, logging everything may not always be a desired scenario. Due to this, we may need to configure our application to log only specific log levels.

Additionally, we may need to generate environment-specific logs. For example, during development, we may want to see all logging levels between Debug and Fatal. On the other hand, in our production environment, we probably want to restrict this to only Warning, Error, and Fatal levels, to prevent consuming too many resources through logging.

Here is where Serilog’s MinimumLevel configuration comes to our rescue. If we configure this setting with a log level, then Serilog only generates logs for that level and higher. For example, if we set the MinimumLevel as Warning, then Serilog records only Warning, Error and Fatal level logs. If we don’t specify a MinimumLevel setting, Serilog uses the Information log level as the default. We can configure this setting in either the appSettings.json file or via the fluent API in Program.cs.

Let’s take a look at configuring MinimumLevel in the appSettings.json file:

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

Here, we instruct Serilog to generate application logs for levels equal to or higher than the Information log level. Additionally, we override the logging of Microsoft and System messages, ensuring they are recorded solely when they are of Warning level or higher.

Now let’s perform the same configuration via the fluent API in Program.cs:

builder.Services.AddSerilog(options =>
{
    options.MinimumLevel.Information()
           .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
           .MinimumLevel.Override("System", LogEventLevel.Warning);
});

To change the minimum level at runtime without a redeploy, we wrap it in a LoggingLevelSwitch and pass that to MinimumLevel.ControlledBy(), then flip the switch from anywhere that holds a reference to it, such as an admin endpoint:

var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Information);

builder.Services.AddSerilog(options =>
{
    options.MinimumLevel.ControlledBy(levelSwitch)
           .WriteTo.Console();
});

// later, at runtime, e.g. from an admin endpoint:
levelSwitch.MinimumLevel = LogEventLevel.Debug;

Notice that only levelSwitch.MinimumLevel changes here; the sink configuration itself stays untouched, and the new minimum takes effect on the very next log call, with no restart.

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

Where to Log?

We can configure Serilog to write logs to different targets or sinks. A sink, in the context of logging, is the destination for our log messages. Here are some of the most common sinks:

  • The application console
  • The file system
  • Relational databases, like MS SQL Server
  • Non-relational databases, like ElasticSearch or MongoDB

A complete list of available sinks can be found here. For a more in-depth look at sinks check out our article Structured Logging in ASP.NET Core with Serilog. When the file system is our sink, our rolling file logging with Serilog article covers size- and time-based rolling in detail.

We can configure Serilog sinks in appSettings.json file or via fluent API in Program.cs file.

Configuring Sinks in appSettings.json

Let’s configure a console and a file sink via appSettings.json:

"Serilog": {
  "Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Console" ],
  "WriteTo": [
    {
      "Name": "Console",
      "Args": {
        "restrictedToMinimumLevel": "Information",
        "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
      }
    },
    {
      "Name": "File",
      "Args": {
        "path": "logs/log-.txt",
        "rollOnFileSizeLimit": true,
        "rollingInterval": "Day",
        "fileSizeLimitBytes": "1000000",
        "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}",
        "restrictedToMinimumLevel": "Warning"
      }
    }
  ]
}

Firstly, we specify the sinks, via Using option. Then, we configure each of them through the WriteTo option. This takes an array of sinks with their associated configuration settings.

Configuring Sinks via Fluent API

Now let’s see how we can configure our logging sinks using the fluent API in Program.cs:

builder.Services.AddSerilog(options =>
{
    options.MinimumLevel.Information()
           .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
           .MinimumLevel.Override("System", LogEventLevel.Warning)
           .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Information,
                outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
           .WriteTo.File("logs/log-.txt",
                rollOnFileSizeLimit: true,
                rollingInterval: RollingInterval.Day,
                fileSizeLimitBytes: 1000000,
                outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}",
                restrictedToMinimumLevel: LogEventLevel.Warning);
});

Differentiating Log Sinks Based on Log Level

Based on application requirements and environment, we may need to configure Serilog to write specific log levels to specific sinks. For example, we may want to write logs with the minimum log level Information to the console but logs with the minimum log level Warning to a file. Under these circumstances, we use the restrictedToMinimumLevel setting. It allows Serilog to differentiate log destinations based on the minimum log level.

We can configure the restrictedToMinimumLevel setting through appSettings.json:

"Serilog": {
    "Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Console" ],
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "restrictedToMinimumLevel": "Information"
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "logs/log-.txt",
          "restrictedToMinimumLevel": "Warning"
        }
      }
    ]
  }

In like fashion, we can also configure restrictedToMinimumLevel through the fluent API:

builder.Services.AddSerilog(options =>
{
    options.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Information)
           .WriteTo.File("logs/log-.txt", 
                   restrictedToMinimumLevel: LogEventLevel.Warning);
});

Conclusion

In this article, we covered Serilog’s log levels and how to use them in an application. We set the floor with the MinimumLevel parameter, configured sinks through both the appSettings.json file and the fluent API, and used restrictedToMinimumLevel to give each sink its own threshold.

For a broader set of best practices for logging with Serilog beyond level selection, we cover the full picture in a dedicated guide.

Tested with .NET 10.0.10, Serilog 4.4.0, Serilog.AspNetCore 10.0.0.