Still chasing cryptic stack traces at 2 a.m.? Imagine if your logs were more like a black‑box flight recorder than a dusty, unreadable diary. Serilog turns everyday logging into structured, queryable insights – so you can sleep at night (or at least fix production issues before the third cup of coffee).
Why Bother Logging at All?
- Post‑mortem clarity ⇢ reproduce bugs in minutes, not hours.
- Operational health ⇢ spot performance bottlenecks before users complain.
- Security audits ⇢ prove that data actually stayed inside the castle walls.
Personal note: In my first large‑scale fintech project, we lost two days tracing an intermittent 500 error. A single structured log entry –
{TransactionId}
andElapsedMs
– would have narrowed the search to one microservice in seconds.
Why Serilog Instead of ILogger‑Only?
Feature | Microsoft.Extensions.Logging (out of box) | Serilog |
---|---|---|
Structured events | Limited (state object) | First‑class JSON, templates |
Rich sink ecosystem | Few (Console, Debug) | 100+ (Seq, Elasticsearch, Datadog, etc.) |
Enrichers | Manual | Plug‑and‑play (MachineName , ThreadId , CorrelationId ) |
Runtime level switch | No | Yes (Serilog.Expressions ) |
Message templates | Plain strings | Named tokens ({OrderId} ) |
Serilog isn’t a replacement for the built‑in logging abstractions; it integrates seamlessly via Serilog.AspNetCore
, so you still inject ILogger<T>
yet enjoy Serilog’s superpowers under the hood.
Getting Started in 90 Seconds
// Program.cs (minimal hosting)
using Serilog;
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.Enrich.FromLogContext()
.CreateBootstrapLogger(); // Early‑startup safety
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((ctx, services, cfg) => cfg
.ReadFrom.Configuration(ctx.Configuration) // appsettings.json
.ReadFrom.Services(services) // DI enrichers
.WriteTo.Console(new RenderedCompactJsonFormatter()));
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
appsettings.json
snippet
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"WriteTo": [
{
"Name": "Console",
"Args": { "formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact" }
}
]
}
}
Tip: Use the Serilog.Settings.Configuration package to move all tuning knobs to your config file – zero redeploys to bump levels.
Structured Logging
Message Templates vs. String Interpolation
Bad (loses structure):
_logger.LogInformation($"User {user.Id} logged in at {DateTime.UtcNow}");
Good (preserves named fields):
_logger.LogInformation("User {UserId} logged in", user.Id);
Resulting JSON (Console sink with compact formatter):
{"@t":"2025-07-26T10:13:45.123Z","@mt":"User {UserId} logged in","UserId":42,"SourceContext":"MyApp.AuthController"}
Searchable, filterable, Grafana‑friendly.
Destructuring Complex Objects
_logger.LogWarning("Failed payment {@Payment}", payment);
The @
tells Serilog to serialize the object rather than call ToString()
– extremely handy when debugging nested DTOs.
Enrichers
Serilog.Enrichers.ClientInfo
⇢ browser, OS, deviceSerilog.Enrichers.CorrelationId
⇢ trace a request across microservices- Custom enricher (e.g., tenant ID):
class TenantEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propFactory)
=> logEvent.AddPropertyIfAbsent(propFactory.CreateProperty("TenantId", TenantProvider.Current));
}
Add with .Enrich.With<TenantEnricher>()
in LoggerConfiguration
.
Choosing the Right Sink
Scenario | Recommended Sink | Why |
---|---|---|
Local dev | Console with color/theme | Fast feedback, no files to clean |
Single VM prod | File rolling daily | Disk is cheap; simple retention |
Distributed prod | Seq or Elasticsearch | Powerful querying, dashboards |
Serverless | AWS CloudWatch / Azure Monitor | Native integration, pay‑as‑you‑go |
Real‑world note: We switched from rolling files to Seq in a Kubernetes cluster and cut incident triage time by 70 % – searching by
CorrelationId
instead ofgrep -R
is game‑changing.
Async Performance
Most sinks buffer asynchronously. For high‑traffic APIs enable WriteTo.Async()
around your sink chain:
.WriteTo.Async(a => a.Console())
Buffer size & back‑pressure are configurable; always benchmark under load.
Environment‑Aware Logging Levels
.MinimumLevel.ControlledBy(new LoggingLevelSwitch(LogEventLevel.Information))
Expose the switch via your health‑check UI or an admin endpoint so ops can temporarily raise verbosity to Debug
without redeploying. Pair with Serilog.Settings.Configuration#reloadOnChange for hot‑reload from appsettings.json
.
Tracing, Metrics & OpenTelemetry
Serilog plays nicely with the .NET DiagnosticListener
and OpenTelemetry exporters. Use Serilog.Sinks.OpenTelemetry
to emit spans side‑by‑side with logs – correlate everything in one backend.
WriteTo.OpenTelemetry(options => {
options.Endpoint = "http://otel‑collector:4317";
});
FAQ: Serilog Adoption in Existing Projects
ILogger<T>
?Yes. Register Serilog as the provider; your existing calls remain untouched.
JSON is verbose, but filtering and retention policies offset storage costs. Compression on disk or in Elastic further helps.
Log.Error(ex, "Unhandled error processing order {OrderId}", order.Id);
Serilog captures ex
plus contextual properties – no custom serializer needed.
Use Serilog.Filters.Expressions
to drop or hash sensitive properties before they leave the process.
Seq
for rich queries; OpenTelemetry
+ Grafana Loki
for unified metrics/logs. Both support horizontal scaling.
Conclusion: Logs That Work as Hard as Your Code
When your logs speak JSON and carry every breadcrumb – from CorrelationId
to ElapsedMs
– root‑cause analysis shrinks from hours to minutes. Serilog makes that upgrade almost friction‑free: add a package, tweak a config, and watch your observability score skyrocket.
Ready to ditch Console.WriteLine
‑driven debugging and join the structured‑logging club? Try swapping Serilog into your next sprint and share your experience in the comments – what insight surprised you the most?