You Don't Need an Audit Log¶
Sooner or later, every serious system meets an auditor. Compliance, regulation, or plain good governance shows up and asks for the same thing: a complete, ordered, tamper-proof record of what happened and when. And almost every architecture review answers with the same reflex. Add an audit log. It has the right name, it produces something you can point at, and it feels responsible. In most systems, it is also the wrong answer.
This is not a case against auditing. Auditing matters, often more than the features built around it. It is a case against the separate audit log as the way to deliver it, because a good auditor, one who actually does the job, will take about one meeting to see through it. The reason they can is the same reason the audit log was compromised from the start, and it reaches all the way down to how you decided to store your data in the first place.
What Compliance Actually Asks For¶
Strip away the jargon and a compliance requirement usually comes down to three things. Auditors want to reconstruct what happened, in the order it happened. They want to reconstruct the state of the system at any past moment, not just the state you have today. And above all, they want to prove that none of it was altered after the fact. In finance, healthcare, and public services, this is not a nice-to-have; it is a legal obligation with real consequences when it fails.
Picture the concrete version of that. A regulator asks what a particular account looked like on a given day two years ago, and how it got from there to where it is now. Answering means replaying every change in order and convincing a skeptical outsider that the replay is faithful and complete. Auditing, at its core, is about trusting a record you did not personally watch being made, a theme we explored through a different lens in What Aviation Teaches Us About Auditing.
Faced with that, the instinct is almost mechanical. You already have a database full of current state, so you bolt a log onto the side of it. Every time the system changes something, it also appends a row somewhere: who did it, what changed, when. Ordering gets a timestamp column. Immutability gets a promise that nobody will ever run an UPDATE on the log table.
It feels responsible, and it produces an artifact you can hand over. When the auditor arrives, you have something to show. That something is exactly where the trouble starts.
The Question a Good Auditor Asks¶
A weak auditor asks whether you have an audit log. A good one asks a harder question: how do you know the audit log matches what actually happened to the data?
That question is fatal, and it is worth sitting with why. Your business data lives in one place, the tables that hold current state, and your audit log lives in another. They are two separate artifacts, produced by two separate writes, and nothing in the structure of the system forces them to agree. The log claims to describe the data. Whether it actually does is a matter of faith.
You can show the auditor the log. What you cannot do is prove that it is complete and faithful, that every change to the data produced exactly one truthful entry and that nothing slipped through unrecorded. The burden of proof sits with you, and a separate log does not carry it. You are effectively asking the auditor to trust that two things which can disagree happen to agree, everywhere, always, going back years.
How the Log Drifts from Reality¶
Once you accept that the log and the data can diverge, the interesting question is how. There are three ways, and every long-lived system eventually meets at least one.
The first is malice. The whole point of an audit trail is to catch people doing things they should not. But whoever can write the business data can usually write, or skip, the matching log entry. The person you are auditing against is often the person holding the pen. A record you can quietly edit protects no one.
The second is accident. Somewhere in the code there is a path that changes state but forgets to write the log. Perhaps an exception fires between the state write and the log write, so only one of them lands. Perhaps a batch job updates rows directly. The log does not announce that it missed something; it simply develops a hole, and the hole looks exactly like nothing having happened.
The third, and the most insidious, is drift over time. On the day it was written, the code logged the right thing. Then a refactor moved the logic, and the log call did not come along. Then a new feature added a second way to change the same data, through a path that predates the logging convention entirely. Each change is reasonable on its own. Together they turn yesterday's faithful log into today's incomplete one, and nobody notices until the audit shines a light on the gap.
What ties all three together is a single fact: the log is a second write, kept correct by discipline rather than by construction. Anything that depends on discipline, on every developer and every code path, forever, eventually fails. It is the same trap as the dual-write problem behind the outbox pattern, which we took apart in You Don't Need an Outbox: two things that must stay in step, held together by nothing but good intentions.
Two Stores Can Always Disagree¶
Step back from the mechanics and the deeper problem comes into focus. The separate audit log rests on an assumption so ingrained that it is easy to miss: that the data lives in one place and the record of how it got there lives in another. Two stores, two sources of truth for one history.
The moment you have two sources of truth, you have a question with no good answer. When they disagree, which one is right? The data says the balance is one number; the log implies it should be another. From inside the system there is no way to settle it. You have built a contradiction and hoped never to be asked about it.
This is the same structural mistake wearing a different costume. It is the outbox pattern's dual write, state here and events there, and it is the framing we questioned in It Was Never About the Database: the way you set up the problem quietly decides which problems you will spend years fighting. The fix is never a better-synchronized second log. The fix is to stop having two.
When the Log Is the Data¶
Event Sourcing turns the whole arrangement inside out. Instead of storing current state and logging the changes on the side, you store the changes, and you derive current state from them. An account balance is not a number you overwrite; it is the result of every deposit and withdrawal that ever happened. The list of those events is the data and the history at the same time.
There is no separate audit log in an event-sourced system, because there is nothing to keep in sync. The events are not a copy of the truth written for the auditor's benefit. They are the truth, the very records the application reads to do its everyday work. You cannot forget to write the audit entry, because the audit entry is the write. You cannot let the log drift from the data, because they were never two things to begin with. Reconstructing the state of an account two years ago stops being a special audit feature and becomes an ordinary replay, the same operation the system already runs to answer any question about the present.
This is where the resemblance to an append-only message log gets seductive, and where it pays to be precise. An ordered, append-only stream looks a great deal like what Event Sourcing needs, which is exactly why teams reach for tools that are not event stores and get burned, a trap we mapped out in Kafka Is Not an Event Store. The point is not that events happen to flow through a log. The point is that the log is the system of record, the thing every read and every decision is built on, rather than a side channel narrating something that lives elsewhere.
Proving Nothing Was Touched¶
Collapsing two stores into one closes the gap, but it leaves the auditor's other demand standing: proving that the single history was not altered after the fact. Making the events the source of truth does not help if someone can quietly rewrite an old one.
EventSourcingDB answers this by hash-chaining every event. Each event carries a cryptographic hash of its predecessor, linking the entire history into a chain that behaves much like a blockchain. Change a single event anywhere in the past and its hash changes, which breaks the hash stored in the next event, which breaks the one after that, all the way to the present. Tampering is not something you have to trust has not happened; it is something you can recompute and detect.
That turns an audit into a mechanical check rather than an act of faith. A POST to the /api/v1/audit-history endpoint walks the chain and confirms its integrity:
curl \
-i \
-X POST \
-H "Authorization: Bearer <API_TOKEN>" \
http://localhost:3000/api/v1/audit-history
Once a store has been audited, you send back the latestEventId and latestEventHash from the previous run, and the next check covers only the events added since. Those incremental audits keep the cost flat no matter how large the history grows, and a separate full store verification stands behind them when you need to check the storage itself end to end. This exists for a simple reason: auditors should not have to take anyone's word for it. A verifiable history means an external party can confirm for themselves that nothing was changed, so you can walk into a compliance review carrying proof instead of promises. If you would like to try it against your own data, the guide to auditing the event store walks through the entire flow.
Now watch what happens to the auditor's fatal question. "How do you know the log matches the data?" no longer has an answer, because it no longer has a subject. There is no separate log to match. There is one history, and here is the cryptographic proof that it is intact. The question that sank the bolted-on approach becomes the easiest slide in the deck.
When You Still Want a Separate Log¶
None of this means you should never write a log again, and it is worth being clear about the exceptions.
If you are locked into a CRUD system you cannot change, a legacy core, a vendor database, a platform decision made long before you arrived, then a separate audit log is a reasonable way to reduce risk. It will not give you proof, and everything above still applies, but a flawed record beats no record. Just be honest with yourself about what it is: a mitigation, not a guarantee.
Plenty of logging has nothing to do with any of this. Access logs, debug traces, request logs, that is telemetry, and it is genuinely useful. The difference is what the log is for. Telemetry helps you operate the system. A compliance audit trail is meant to be the authoritative record of business facts, and that is the job a second copy of the truth cannot safely do.
So the rule is not "never keep a log." It is narrower and sharper: do not build your compliance story on a second copy of the truth when you can make the truth itself the record.
One History, Provable¶
Come back to the auditor and their one hard question. Against a bolted-on audit log, "how do you know it matches the data?" is the question that ends the meeting badly. Against an event-sourced history with a verifiable chain, it is the question that ends it well, because there is nothing to reconcile and everything to prove. You did not add an audit log. You removed the gap that made one necessary.
That is the shift worth holding onto. The record of what happened should not sit beside your data, forever hoping to stay in step with it. The record should be the data. Once the events are the source of truth and the chain makes them tamper-evident, the audit trail stops being a component you build and maintain, and becomes something the system simply is.
If you are staring down a compliance requirement and wondering whether a second log is really the best you can do, we would genuinely like to hear about it. Tell us what you are up against at hello@thenativeweb.io, whether it is a consulting conversation, a workshop, or a second opinion; that is exactly the kind of problem we like to think through with people.