Roadmap › Logging & Custom Logging in .NET — ILogger, Serilog, NLog, log4net and Your Own Logger › Module: Custom and Production Logging
Lessons in this course (8)
7. Audit logging vs diagnostic logging
Intermediate★ Must knowLesson 7 of 8
| Diagnostic logs | Audit logs | |
|---|---|---|
| Purpose | Troubleshooting, monitoring | Compliance, security, accountability |
| Content | Technical events, errors, timings | Who, what, when, where, before/after values |
| Retention | Days–weeks | Years (regulation-driven) |
| Integrity | Best effort; may sample/drop | Must be complete, tamper-evident |
| Storage | Log platforms | Database tables / immutable storage |
csharp
// EF Core SaveChanges interceptor writing audit rows for changed entities
public sealed class AuditInterceptor(ICurrentUser user) : SaveChangesInterceptor
{
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData e, InterceptionResult<int> result, CancellationToken ct = default)
{
var db = e.Context!;
var audits = db.ChangeTracker.Entries()
.Where(x => x.State is EntityState.Added or EntityState.Modified or EntityState.Deleted && x.Entity is not AuditEntry)
.Select(x => new AuditEntry
{
Table = x.Metadata.GetTableName()!, Action = x.State.ToString(), User = user.Name, Utc = DateTime.UtcNow,
Changes = JsonSerializer.Serialize(x.Properties.Where(p => p.IsModified || x.State != EntityState.Modified)
.ToDictionary(p => p.Metadata.Name, p => new { Old = p.OriginalValue, New = p.CurrentValue }))
}).ToList();
db.Set<AuditEntry>().AddRange(audits);
return base.SavingChangesAsync(e, result, ct);
}
}SQL Server alternatives: temporal tables (automatic row history), SQL Server Audit, Change Data Capture.