1. Introduction
If plain text logging is your diary with notes like "I ate today", structured logging turns each entry into a card with fields: {Date: ..., Event: "Ate", Calories: 500, Dish: "Fried meat"}. That means you can not only read the diary later, but also build a calories chart for the month, filter events by dish, and find out when you ate too late.
Why is plain text not enough?
Plain text is simple... until it's not. Try to collect statistics on errors in logs, sales volumes in an online shop, or the action trail of a single user by their ID (for example, 42) when all data is just pages of text. Structured logging lets you attach analysis and even AI to logs. With it you can spot anomalies, build dashboards and react to issues automatically.
Advantages
- Allows logging not only messages but also related data (fields/properties).
- Logs can be analyzed automatically: counted, filtered, used to build reports.
- Standardized formats like JSON are used, which are easy for machines to parse.
Serilog: what is it and why use it?
Serilog (official site: serilog.net, docs: github.com/serilog/serilog/wiki) is a popular library for structured logging in .NET. It integrates nicely with Microsoft.Extensions.Logging, supports output to dozens of systems (file, console, Seq, ElasticSearch, Grafana, Azure, etc.), has minimal performance impact and is easy to configure.
How does Serilog differ from "just logging"?
- Structure: logs are objects with fields you can filter by (for example, all errors from user with ID 42).
- Formats: can write not only text but also JSON, XML, which is handy for further processing.
- Flexibility: many ready-made sink packages to send logs anywhere.
Structure of a log entry with Serilog
Let's look at a structured log before writing code.
{
"Timestamp": "2024-06-22T10:23:45.123Z",
"Level": "Information",
"MessageTemplate": "User {UserId} logged in",
"Properties": {
"UserId": 42,
"IpAddress": "127.0.0.1"
}
}
Even a basic analysis will understand: this is about user №42 and their IP address.
2. Installation and basic setup of Serilog in a C# project
Step 1. Install NuGet packages
In Rider/Visual Studio via NuGet Package Manager install:
- Serilog
- Serilog.Sinks.Console (console output)
- Serilog.Extensions.Logging (for integration with Microsoft.Extensions.Logging)
Via command line:
dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
Step 2. Minimal setup
Add configuration in Program.cs and write the first log.
using System;
using Serilog;
namespace MySuperApp
{
class Program
{
static void Main(string[] args)
{
// 1. Basic Serilog setup: console output
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger();
// 2. Example of structured logging
int userId = 42;
string ip = "127.0.0.1";
Log.Information("User {UserId} logged in with IP {IpAddress}", userId, ip);
Log.CloseAndFlush();
}
}
}
In the console you'll see something like:
[10:30:16 INF] User 42 logged in with IP 127.0.0.1
From here it's easy to direct output to a JSON file, Seq or another system.
3. Log formatting: Message Template
In Serilog you use template syntax instead of string concatenation:
Log.Information("Operation {Operation} on file {FileName}", "delete", "test.txt");
This is not just a pretty syntax — it's structured logging: the entry will contain fields Operation and FileName, available for filtering and aggregation.
How is this different from string.Format?
string.Format("Operation {0} on file {1}", operation, fileName) simply stitches a string with placeholders {0}, {1}. Serilog creates separate fields you can later analyze.
Flexible configuration: levels, filters, multiple "sinks"
Serilog can write logs to multiple places at once.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
Now logs go both to the console and to a file with daily rotation.
4. Example
Suppose we're making a console "notes" app that lets users create entries. Let's add structured logging of actions.
using System;
using Serilog;
namespace NotesApp
{
class Program
{
static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Console()
.WriteTo.File("notes-log.json", rollingInterval: RollingInterval.Day,
formatter: new Serilog.Formatting.Json.JsonFormatter())
.CreateLogger();
Console.WriteLine("Enter user name:");
string userName = Console.ReadLine();
Log.Information("User {UserName} started the NotesApp", userName);
while (true)
{
Console.WriteLine("Enter note text (or type 'exit'):");
string note = Console.ReadLine();
if (note == "exit")
{
Log.Information("User {UserName} ended the session", userName);
break;
}
Log.Information("User {UserName} created a note: {NoteText}", userName, note);
}
Log.CloseAndFlush();
}
}
}
Comment:
The log records who starts the program, what they type and when they finish. In the file notes-log.json each entry is a JSON object that's easy to analyze.
5. Useful nuances
Best practices for structured logging
- Don't overuse Debug/Trace levels in production — use Information and Warning reasonably.
- Use named parameters in templates instead of string concatenation.
- Never log sensitive data (passwords, tokens, keys).
- Log important business events, not only errors and exceptions.
- Set up rotation and cleanup so logs don't fill up disk space.
Visualization and analysis: Seq, Kibana, Application Insights
Serilog supports many sinks — endpoints where logs are sent.
| Sink | Short description | Where used |
|---|---|---|
| Console | Directly to the console | Development, tests |
| File | To a local or network file | Small projects, dev |
| Seq | Web UI with filtering and dashboards | Enterprise, analytics |
| ElasticSearch | Powerful storage and analysis system | Large companies |
| Azure Application Insights | Cloud monitoring and telemetry | Azure-heavy services |
Seq (datalust.co/seq) is a very popular solution for development and internal use: convenient filtering, field search and quick deployment.
Tables and visualization
Below is a short table of what you can log structurally:
| What we log | How it looks in Serilog | Example value |
|---|---|---|
| User ID | |
123 |
| Action | |
"Delete" |
| Error | |
"Registration module" |
| Operation time | {Elapsed:0.000} sec | 1.234 |
| File name | |
"report.pdf" |
Cool tricks and additional capabilities of Serilog
- Enrichers: add properties to every log (for example, .Enrich.WithMachineName()).
- Correlated logs: add a RequestId to correlate a chain of events.
- Configuration via appsettings.json: convenient for production.
{
"Serilog": {
"MinimumLevel": "Debug",
"WriteTo": [
{ "Name": "Console" },
{ "Name": "File", "Args": { "path": "log.txt" } }
]
}
}
Advanced sinks: you can send logs to Slack, Telegram, email (but be careful not to get a thousand emails for every error).
6. Practical part: integration with Microsoft.Extensions.Logging
In .NET it's common to use the standard ILogger interface to avoid being tied to a specific library. Serilog can be plugged in as a provider.
Step 1. Install the package
dotnet add package Serilog.Extensions.Logging
Step 2. Configuration
using Microsoft.Extensions.Logging;
using Serilog;
// ...
// Configure Serilog as usual:
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
// Now use Microsoft.Extensions.Logging
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddSerilog();
});
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("Test message: {TestValue}", 123);
// Don't forget to close:
Log.CloseAndFlush();
Comment:
Now all your code using ILogger<T> is independent of the provider — you can switch to NLog or Log4Net if you want.
7. Common mistakes when working with Serilog
Log overload: if you log everything, useful information becomes hard to find.
Logging exceptions as text: use the overload that accepts the exception — that way the error structure gets into the log.
try
{
// some code
}
catch (Exception ex)
{
Log.Error(ex, "An error occurred while executing the request");
}
Config abuse: don't turn configuration into a mess — add only necessary sinks and levels.
GO TO FULL VERSION