Serilog
Core Principles
- Two-stage initialization — Create a bootstrap logger for startup, then replace it with the full logger after DI is ready. This captures startup errors that would otherwise be lost.
AddSerilog()overUseSerilog()— Usebuilder.Services.AddSerilog()(the modern API) instead ofbuilder.Host.UseSerilog(). It integrates with DI services viaReadFrom.Services(services).- Message templates, not interpolation —
{PropertyName}syntax creates structured data that can be queried. String interpolation ($"...") breaks structure and allocates even when the log level is disabled. - Configure via appsettings.json — Keep log levels, sinks, and overrides in configuration so they can change per environment without redeployment.
Patterns
Two-Stage Bootstrap Setup
appsettings.json Configuration
Override section uses namespace prefixes matched against SourceContext. More specific prefixes take precedence.
Request Logging Middleware
Replaces the multiple per-request log events from ASP.NET Core with a single summary event.
Structured Logging and Destructuring
Scoped Properties with LogContext
Requires .Enrich.FromLogContext() on the logger configuration.
OpenTelemetry Sink (OTLP Export)
Export Serilog events directly to any OTLP backend without the OpenTelemetry SDK:
Serilog.Expressions for Filtering
Requires the Serilog.Expressions package.
[LoggerMessage] Source Generator for Hot Paths
Built into Microsoft.Extensions.Logging.Abstractions — compile-time generated, zero allocations when the level is disabled.
