Audit Logs: Build Them Before You Need Them
Why every system that moves money or changes permissions needs an append-only audit log — and how small teams can build one without heavy tooling.
The first time you really need an audit log, it's already too late to build one. A customer disputes a charge from three weeks ago. An admin account changed a permission nobody remembers changing. A balance looks wrong and the only evidence is the current state of a database row that has been overwritten a dozen times since. If all you have is what the data looks like now, you can't answer the only question that matters: how did it get this way?
Audit logs are one of the highest-leverage features a small team can build, precisely because they're boring. Nobody demos them. Nobody asks for them in a sales call. And then one incident pays for the entire effort.
What an audit log actually is
An audit log is an append-only record of who did what, to which thing, and when. Not application logs — those rotate, get truncated, and describe what the code did. An audit log describes what a person or system actor did, in domain terms:
- Actor — the user, API key, or service that performed the action
- Action — a stable, named verb:
wallet.topup,user.role_changed,refund.issued - Target — the entity affected, by ID
- Before/after — the fields that changed, old and new values
- Context — timestamp, request ID, IP or origin where relevant
The critical property is append-only. Rows are inserted, never updated, never deleted. The moment someone can edit the audit trail, it stops being evidence and becomes decoration.
You don't need special infrastructure
Teams delay audit logging because they imagine it requires an event-sourcing platform or a compliance product. It doesn't. A single database table gets you most of the value:
One
audit_eventstable, written in the same transaction as the change it describes, beats an elaborate pipeline you never finished building.
Writing the audit row inside the same transaction matters. If the audit write happens after the commit — in a separate call, or fire-and-forget to another service — you will eventually have changes with no record, and those are exactly the changes you'll be asked about. Keep it atomic: either the change and its evidence both land, or neither does.
For higher assurance later, you can add hash chaining (each row stores a hash of the previous one) so tampering is detectable. But that's an upgrade, not a prerequisite.
Log decisions, not noise
A common failure mode is logging everything and learning nothing. Page views and read operations usually don't belong in an audit log. Focus on actions that are irreversible, financial, or security-relevant:
- Money moving: charges, refunds, top-ups, balance adjustments
- Access changing: role grants, password resets, API key creation
- Records disappearing: deletions, cancellations, overrides
- Anything an admin does on behalf of a customer
That last one deserves emphasis. Admin and support tooling is where "how did this happen?" questions concentrate, and it's often the least-instrumented part of a system.
The takeaway
If your software moves money, manages permissions, or lets staff modify customer data, an audit log isn't optional polish — it's how you defend your own correctness. Build it as one append-only table, written transactionally, covering the actions you'd need to explain under pressure. Do it this sprint, while nothing is on fire. The day you need it, you'll either have three weeks of history or an apology — and only one of those resolves a dispute.
Build with Abati Technology
We build software that ships — WhatsApp API, developer tools, POS, and mobile apps. Let's talk about your project.
Get in Touch →