Logging & Observability
Core Principles
- Structured logging with Serilog — Every log entry is a structured event with named properties, not a formatted string. This enables searching, filtering, and alerting. All setup (two-stage bootstrap,
AddSerilog(), sinks, enrichers) lives in the serilog skill — that skill'sAddSerilog()-over-UseSerilog()guidance is canonical. - OpenTelemetry for distributed tracing — Traces connect requests across services; metrics track system health over time. Full setup lives in the opentelemetry skill.
- Health checks for operational readiness — Every service exposes
/healthendpoints for load balancers and orchestrators. Liveness and readiness are separate questions and separate endpoints. - Correlation IDs for request tracing — Every request gets a unique ID that flows through all log entries and downstream service calls, so one user complaint maps to one filtered log stream.
Patterns
How the Pieces Fit Together
Wire logging first (you need logs to debug the rest), then health checks, then tracing.
Correlation IDs
Why middleware: pushing the property once at the pipeline edge attaches it to every log event in the request scope — no per-call-site plumbing. Propagate the same header on outgoing HttpClient calls via a DelegatingHandler (see the httpclient-factory skill).
Health Checks
Why two endpoints: liveness failing means "restart me"; readiness failing means "stop sending traffic". Conflating them makes a slow database restart your app in a loop.
Log-Level Strategy
Why Warning as the production default: Information-level request noise at scale costs real money in log storage and drowns the signals. Keep Information for genuine business events via namespace overrides (see the serilog skill's MinimumLevel.Override pattern).
