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
CorrelationIdinstead ofgrep -Ris 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?
