Logging is one of the most essential tools in a developer’s toolbox, especially when something goes wrong in production. But traditional logging can quickly turn into a mess of unstructured text, making it hard to search, filter, or understand what’s really happening.
That’s where Serilog comes in.
Serilog is a powerful, diagnostic logging library for .NET applications that supports structured logging out of the box. Unlike basic logging solutions, Serilog treats logs as data, not just text, allowing you to capture rich, queryable information about your application’s behavior.
If you’ve ever struggled with unreadable logs, limited filtering, or missing context in your logs, Serilog can change the game. It integrates seamlessly with ASP.NET Core, works great with sinks, and gives you full control over the shape and destination of your logs.
In this post, I’ll show you exactly why you should use Serilog in your ASP.NET Core apps, and how to get started the right way.
Installing Serilog and Understanding Sinks & Enrichers
To get started with Serilog in your ASP.NET Core application, you’ll first need to install the main Serilog integration package:
dotnet add package Serilog.AspNetCore
This package wires Serilog into the ASP.NET Core logging pipeline, replacing the default logger with Serilog’s powerful structured logging engine.
What is a Sink?
A sink is where your logs go. Think of it as the destination for your log data, it could be the console, a file, a database, or even an external service like Seq or Elasticsearch.
Here are some of the most common sinks:
dotnet add package Serilog.Sinks.Console # Logs to the terminal
dotnet add package Serilog.Sinks.File # Logs to a local file
dotnet add package Serilog.Sinks.MSSqlServer # Logs to a SQL Server database
dotnet add package Serilog.Sinks.Seq # Logs to the Seq log server
Each sink gives you different options based on your environment and log analysis needs. For local development, Console and File are great. For production, tools like Seq or MSSql make logs searchable and easy to analyze.
What is an Enricher?
An enricher adds extra information to every log event automatically, giving you more context without repeating yourself in every log line.
Useful enrichers include:
dotnet add package Serilog.Enrichers.Environment # Adds machine name and user name
dotnet add package Serilog.Enrichers.Thread # Adds the thread ID
dotnet add package Serilog.Enrichers.Process # Adds process ID and name
With just a few packages, your logs become rich, searchable, and production-ready.
Understanding Log Levels in Serilog
Serilog supports standard log levels to help you control the importance and verbosity of your logs. Each level represents a severity threshold, allowing you to filter what gets recorded or displayed.
Here are the log levels in order, from most critical to most verbose:
| Level | Description |
|---|---|
| Fatal | Something went terribly wrong – the app may crash or become unstable. |
| Error | An error occurred, but the app can still continue running. |
| Warning | Something unexpected happened, but not necessarily an error. |
| Information | General application events, like startup, shutdown, or business logic. |
| Debug | Detailed info useful for debugging during development. |
| Verbose | All possible logs – the most detailed messages (usually excessive). |
You can configure Serilog to filter logs by level, so you only see what matters in different environments. For example, in production, you might log only warning while in development, you include information, debug and verbose.
Setting Up Serilog
Once you’ve installed the required packages, it’s time to wire Serilog into your ASP.NET Core application.
builder.Host.UseSerilog((context, configuration) =>
configuration.ReadFrom.Configuration(context.Configuration));
var app = builder.Build();
app.UseSerilogRequestLogging(); // Logs HTTP requests automatically
Let’s break it down:
- UseSerilog(…) tells the app to use Serilog as the logging provider.
- ReadFrom.Configuration(…) allows you to configure Serilog via appsettings.json, keeping your setup clean and flexible.
- UseSerilogRequestLogging() logs details of every HTTP request, method, path, status code, and timing, with zero manual effort.
Configuring Serilog in appsettings.json
Here’s an example of how to configure Serilog using appsettings.json, with file, console, and SQL Server sinks:
"Serilog": {
"Using": [
"Serilog.Sinks.Console",
"Serilog.Sinks.File",
"Serilog.Sinks.MSSqlServer"
],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"WriteTo": [
{
"Name": "Console"
},
{
"Name": "File",
"Args": {
"path": "C:\\Work\\Pedro Constantino\\Blog\\Code\\ApiSerilog\\logs\\log-.txt",
"rollingInterval": "Day",
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}",
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
}
},
{
"Name": "MSSqlServer",
"Args": {
"connectionString": "Server=.;Database=SerilogDb;Trusted_Connection=True;TrustServerCertificate=True;",
"tableName": "Logs",
"autoCreateSqlTable": true,
"restrictedToMinimumLevel": "Warning"
}
}
]
}
- Logs will be written to the console, a daily rolling file, and a SQL Server table called Logs.
- The log file path points to: C:\Work\Pedro Constantino\Blog\Code\ApiSerilog\logs
- The MinimumLevel is set to Information, filtering out debug and verbose logs.
- The SQL Server table will be auto-created on the database SerilogDb.
- This setup keeps your logs persistent, structured, and queryable across environments.
Minimal APIs with Serilog
Here, we’ll demonstrate how to implement logging in a Minimal API route, specifically focusing on logging an Information level message:
app.MapGet("/serilog-information", (ILogger logger) =>
{
logger.Information("Information: This is an informational log. Everything is running fine.");
return Results.Ok();
});
Remember, logging is essential for tracking your application’s behavior and identifying issues. You can apply logging in various places, like background jobs, services, and even API endpoints. This can be incredibly useful for pinpointing errors and improving the maintainability of your code. For instance, by logging every step of a service’s execution, you can quickly trace the source of any failures or performance bottlenecks.
Here’s another example of how you can use Error and Fatal levels to log critical issues:
try
{
// Log informational message before job execution
_logger.Information("Job started at {Time}", DateTimeOffset.Now);
await Task.Delay(5000, stoppingToken); // Simulate work with a 5-second delay
// Log successful job completion
_logger.Information("Job completed successfully at {Time}", DateTimeOffset.Now);
}
catch (Exception ex)
{
// Log error if something goes wrong
_logger.Error(ex, "An error occurred while running the job at {Time}", DateTimeOffset.Now);
}
Conclusion
Logging plays a critical role in modern software development. Whether you’re tracking application flow, identifying performance bottlenecks, or debugging errors, Serilog’s robust features such as log levels, enrichers, and sinks make it a game-changer for developers. Not only does it help you capture more relevant data, but it also simplifies troubleshooting by allowing you to easily filter and search logs based on their severity or context.
By integrating Serilog into your .NET application, you can ensure that your logs are structured, persistent, and actionable. Whether you’re logging basic information or handling critical errors, the insights gained from these logs can be invaluable in maintaining and improving your applications.
Thank you for reading.
See you next time!



