Structured Logging & Observability

Logging is the part of a system that nobody designs and everybody relies on at three in the morning. It usually compiles, looks fine in development, and falls over the first time a real incident requires correlating two services across a single request. The reason is almost always the same: ILogger is a façade over a pipeline of opt-in behaviour, and the defaults make it look like more is wired up than actually is.

The articles in this collection are written from the angle of what logs have to prove during an incident or an audit, not what looks pretty in the console. Structured properties are the foundation. A log line is not text — it is a record with typed fields, semantic names, and a level that survives transport to Application Insights, Loki, or whatever sink is downstream. The difference between LogInformation($"User {id} did X") and a message template with {UserId} is the difference between a string you can grep and a property you can group, alert on, and join with telemetry.

BeginScope, correlation IDs through Activity.Current, request enrichment middleware, and LoggerMessage source-generated callsites are where the practical work lives. They are also where the silent failures lurk: scopes that do nothing because the provider does not flow them, allocations from interpolated debug messages on filtered hot paths, exception chains that lose their stack because somebody logged ex.Message instead of ex. Each one compiles, ships, and degrades observability without raising an error.

Then there is the half-forgotten side of audit logging. ISO/IEC 27001 Control A.8.15 is explicit that events must be produced, retained, and reviewed — and most production logs cannot answer “who authenticated as whom from where over the last six months” because the schema was never designed to. The articles here cover how to make logs forensic-grade without turning every request into a 50 MB blob nobody reads.

Six Ways ILogger Silently Fails in Production

Six Ways ILogger Silently Fails in Production

I lost half a day because BeginScope silently did nothing in production: no error, no warning, just a flat stream of undifferentiated log entries. ILogger is a façade over a pipeline full of opt-in behaviour that looks enabled by default. Scopes, structured properties, minimum levels, exception chains, timestamps: all have failure modes that compile cleanly and fail quietly.
Who Ran That Migration? Audit Trails for .NET CLI Tools

Who Ran That Migration?

Three hours into a production incident, someone asks the obvious question. Silence. The terminal closed, the build log expired last week, and your migration tool printed “Success” before forgetting everything. This scenario repeats constantly: privileged CLI operations that modify production systems, then vanish without a trace. The fix requires discipline, not genius: structured logging, user identity tracking, and persistent storage.
Audit Logging That Survives Your Next Security Incident

Audit Logging That Survives Your Next Security Incident

Your audit logs probably won’t survive a real security incident. Most implementations log too much, protect too little, and provide zero value when something breaks at 2 AM. Here’s how to fix that with structured logging that actually works.