Every few months, a team discovers that patient IDs, email addresses, or credit card numbers have been flowing freely into Splunk or Datadog for weeks. The immediate reaction is always the same: tighten the log configuration, add some field filtering, maybe run a regex across the sink.
These fixes work — until the next developer adds a new endpoint, a new DTO, or a new exception handler. Then the cycle starts again.
Why the Standard Approach Breaks
The root issue is that log configuration is treated as infrastructure concern, not a code concern. Sink-level filters, destination scrubbers, and log management platform rules all operate downstream — after the data has already been serialized and emitted. By that point, you're playing defense with incomplete information.
Downstream scrubbing can only filter what it knows to look for. It has no awareness of your domain model, your field semantics, or your regulatory scope. A field called `userRef` means nothing to a regex rule.
There are three specific failure modes that repeat across almost every engineering team dealing with this problem:
- Sink-level filtering is brittle — it relies on field name conventions that drift over time and across teams.
- Exception logging is the biggest vector — structured exception objects often contain entire request payloads.
- Third-party SDKs emit logs you don't control — yet they share your logging pipeline.
The Governance Model vs. the Configuration Model
A governance model approaches the problem differently. Instead of defining rules at the sink, it defines them at the point of emission — inside the application process, before the log event is ever serialized.
This means the enforcement is code-local. It travels with the application. When a developer adds a new endpoint, the governance policy already applies. When a new field gets logged, it's evaluated against the policy before it leaves the process.
What this looks like in .NET
In practice, this means intercepting the log event pipeline before it reaches any sink. In .NET, that's the ILogger abstraction layer — specifically, the enrichment and filtering stage.
// Without governance — sensitive data reaches the sink unfiltered
_logger.LogInformation("Processing payment for {UserId}, card {CardNumber}", userId, cardNumber);
// With governance — the policy intercepts at emission time
// CardNumber is classified as PII in the governance config
// CerbiStream strips it before the event reaches Serilog / NLog / MEL
_logger.LogInformation("Processing payment for {UserId}, card {CardNumber}", userId, cardNumber);
// → "Processing payment for {UserId}, card [REDACTED]"The application code doesn't change. The governance layer sits between your ILogger calls and your sinks, evaluating every structured log property against the active policy.
The Mindset Shift
The most important change isn't technical — it's conceptual. Logging governance needs to be treated like input validation: something that happens in-process, as close to the source as possible, and applied unconditionally.
You don't validate user input at the database layer. You validate it before it enters your domain. The same principle applies to sensitive data in logs.
A useful mental model: treat your log emission layer like an API boundary. Everything that crosses it should be explicitly considered — not implicitly trusted.
Where to Go From Here
If you're evaluating whether your current setup has a governance gap, start with exception logging. Capture a sample of your exception events and check how much of the original request context is serialized into the log payload. In most applications, this alone accounts for the majority of PII exposure.
The goal isn't to log less. It's to log precisely — with full awareness of what leaves your application process and why.