Audit Trails: Designing Systems That Can Prove What Happened
Key takeaway
An audit trail records who did what, to which record, when, and what the values were before and after — stored append-only, separately from the data it describes. Application logs don't qualify: they're unstructured, rotated, and usually writable by the same process that made the change.
Auditability is normally introduced by a compliance requirement and then turns out to be one of the most useful features in the system — because "who changed this?" is asked far more often by support than by auditors.
What each entry must contain
- Actor — the authenticated user, or the system process, with enough identity to be meaningful a year later.
- Action and target — what was done, to which entity and id.
- Before and after values for the fields that changed. Without the old value, the trail tells you something happened but not what.
- Timestamp in UTC, plus the source: IP address, session, or API client.
- Context: the reason, ticket, or approval where your process requires one.
Storage rules
- Append-only. No update or delete path should exist in the application, and the database user writing it should not have permission to modify it.
- Separate from the operational tables it describes, so a rollback or a purge of business data doesn't remove the record of it.
- Retention defined by regulation or policy, not by disk convenience — and enforced, because keeping it forever has its own risk.
- Consider a tamper-evident approach such as periodic hashing where the stakes justify it.
Record reads too, where it matters
For regulated personal data, who looked at a record is as important as who changed it. Access logging is the only way to answer the question an investigation actually asks — did this employee view patients or customers they had no business reason to see. Volume is the challenge; log the read of sensitive entities rather than every page view.
Make it usable
- Expose it in the admin interface, filtered by record and by actor. A trail only a developer can query won't be used by the people who need it.
- Present changes in business language — "status changed from pending to approved" — not as a raw column diff.
- Include system and integration actions, clearly attributed, so an automated change isn't mistaken for a person.
- Test it in your incident drills: the first thing you'll want during a real investigation is a reliable timeline.