Audit Logging: What to Record and How to Secure It

- Who
- What and when
- Before and after
- Loglune
Audit logging is the practice of recording who did what, when, and to which object in a software system, as a time-ordered record that teams keep to investigate incidents and to show compliance. An audit log row names the actor, the action, the time, and the affected object, and for configuration changes it should also keep the values before and after the change. Audit logs are only useful as evidence when nobody can quietly edit or delete them, so audit logging systems store rows append-only and add integrity checks such as hash chains that make tampering detectable.
This guide is for developers and platform engineers who have to show an auditor or an incident reviewer who changed what. It closes a gap that many audit logging guides leave open: they list the fields to record but do not show what verification returns after a stored row is altered. After reading, you can decide what each row records, make the log append-only and tamper-evident, and set a retention period for the frameworks that apply to you. It covers the difference between an audit log and an audit trail, how audit logs differ from ordinary system logs, what to record, how to secure audit logs and make them tamper-evident, how long to keep them, and which compliance frameworks ask for them. The example section shows a real audit log of feature flag changes exported by Loglune, a feature-flag platform, together with its chain verification result and what that result looks like after a row is altered.
What is audit logging?
Audit logging is the process of documenting activity within the software systems used across an organization, and an audit log records the occurrence of an event, the time it occurred, the responsible user or service, and the impacted entity. An audit log is a record that provides documentary evidence of specific events, and it is a chronological record of system activities, including system accesses and operations performed in a given period.
Audit logging answers four questions after the fact:
- Who made the change or the access: a user, a service account, or an API key.
- What happened: the action and the object it touched.
- When it happened: a timestamp, and in many systems a sequence number that fixes the order.
- What changed: for edits, the value before and the value after.
Audit logs come from many places. Okta and VPN logs are sources that capture failed logins, and AWS CloudTrail is a source that captures larger events such as a user creating a new VM instance. Teams can send their audit logs to a central log management platform for storage, search, and analysis.
Audit log vs audit trail: what is the difference?
The two terms are often used as synonyms, and an audit trail is also called an audit log. When writers separate them, the difference is the unit:
- An audit log is the raw, system-generated record of individual events.
- An audit trail is the sequence of those events that lets you follow one change, transaction, or user session from start to end. A series of audit logs is called an audit trail because it shows a sequential record of all activity on a system, and the trail is the evidentiary narrative you assemble from one or more logs.
In practice, you build audit logging so that an audit trail can be reconstructed from it. If each row carries an actor, a time, an order, and the object it changed, filtering the rows for one object gives you that object’s audit trail.
Audit logs vs system logs: auditing vs logging
System logs and audit logs are both logs, but they answer different questions. They differ in the information they contain, their purpose, and their immutability: regular system logs such as error logs help developers troubleshoot, while audit logs document a historical record of activity for compliance and policy enforcement. Put differently, system logs give a broad view of system operations and performance, and audit logs focus on security-related events for compliance, incident monitoring, and forensic analysis.
| System or application log | Audit log | |
|---|---|---|
| Question it answers | What did the system do, and why did it fail? | Who did what, when, and to which object? |
| Typical content | Errors, startup and shutdown, performance | Actor, action, target, time, before and after |
| Edits after writing | Often rotated, sampled, or dropped | Should be append-only and tamper-evident |
| Main readers | Developers and on-call engineers | Security, compliance, auditors, incident reviewers |
Logging is writing these records. Auditing is the later review of them, by a security team or an external auditor, to check that activity followed policy.
What should an audit log record?
Record enough to answer who, what, when, and what changed without joining other systems. One row holds the actor (a user ID or an API identity), the action, the target, a timestamp, and the values the change replaced and wrote. Audit logs typically capture these fields:
- the event name as identified in the system, and a readable description
- the event timestamp
- the actor or service that created, edited, or deleted something (user ID or API ID)
- the application, device, system, or object that was affected
- the source the actor came from, such as a country, host name, or IP address
- custom tags, such as the severity of the event
For changes to configuration or data, add the values before and after the change. Optro’s audit trail FAQ lists “granularity, including before/after values” as one of the criteria auditors use to evaluate a vendor’s audit trail. A row that says “flag checkout-v2 updated” tells you that something changed. A row that also holds the old and new definitions tells you what the system did differently afterward.
Two more fields make the log easier to trust and to read:
- A sequence number per row, so gaps and reordering are visible.
- A link from each change to the release or version that shipped it, so you can tell a change that reached production from one that stayed in a draft.
Which activities should you audit?
Audited activity falls into administrative activity (creating or deleting user accounts), data access and modification, user denials and login failures, and system-wide changes such as creating a new VM. Audit trails fall into four types: system-level, application-level, user, and manual documentation audit trails. System-level trails typically include log-on attempts, network performance metrics, and automated system operations, and application-level trails track actions inside software applications like opening, closing, editing, and deleting records. For software systems, these two groups matter most; user audit trails and manual documentation audit trails complete the four types.
Configuration that changes production behavior without a deploy, such as feature flags, belongs in the application-level group. A flag edit can change what every customer sees, so it needs the same actor, time, and before and after fields as a code change.
How to secure audit logs and make them tamper-evident
An audit log that an administrator can edit proves little, so securing audit logs is about two things: limiting who can read and manage them, and making any change to stored rows detectable.
Common safeguards include access control with role-based access and multi-factor authentication, encryption in transit and at rest, a tamper-evident design that uses cryptographic hash functions or digital signatures for each entry, and centralized logging. Microsoft’s audit logging overview says the tools Microsoft online services use to collect and process audit records do not allow permanent or irreversible changes to the original record content or time ordering, that management of audit logs is limited to a small subset of the security team, and that administrative access requires Just-In-Time approval.
An audit trail is tamper-evident when any unauthorized modification, deletion, or insertion can be detected, typically through cryptographic hashing, write-once or append-only (WORM) storage, digital signatures, or hash chaining.
The common building blocks:
- Append-only storage. The database or storage layer rejects updates and deletes on audit rows. Corrections are new rows.
- A hash chain. Each row stores the hash of the previous row’s hash plus its own content. Changing any row changes its hash, so the next row’s stored link no longer matches, and verification reports the first broken row.
- An external anchor. A chain proves the rows agree with each other, but someone who rewrites the whole tail, or drops the last rows, can leave a shorter chain that still agrees with itself. Copying the latest sequence number and hash to separate storage on a schedule turns that case into a mismatch.
- Separate access. The people who can change the audited system should not be able to change its audit log.
The example below shows the first three in a real run.
Example: an audit log of feature flag changes
Feature flags change production behavior without a deploy, so a flag edit needs an audit log row as much as a code change needs a commit. Loglune records flag changes this way. Each change to a flag, an attribute set, or a setting is a change event with the actor, the time, and the values before and after. The change history refuses UPDATE and DELETE, and each row holds a hash chain link. The audit operation lists the change events and exports the rows together with the chain verification result as JSON or NDJSON. The flag change events sit in a hash chain, verified on export.
The run below executed Loglune’s shipped control API route on a local workerd D1 and R2 under Vitest on October 6, 2026, with editorial example flags, not customer data. One operator made four changes:
- seq 1: create
checkout-v2, serving off by default. - seq 2: create
promo-banner, serving off by default. Then publish version 1. - seq 3: switch
checkout-v2to on by default. Then publish version 2. - seq 4: switch
promo-bannerto on by default, and leave the edit unpublished in the draft.
The run then exported the audit log as NDJSON, read the audit list for checkout-v2 as JSON, tried to update and delete a stored row, and finally removed the append-only trigger in its own throwaway in-memory database to alter seq 3 and export again. Each line of the NDJSON export is one stored row, and the last line is the chain verification. The output below is copied from the run; rows 1, 2, and 4 are left out of the first block and rows 1, 2, and 4 of the second block are identical to the first export.
{"event_id":"b193d96d-5d86-4d95-b7a8-83a2bf3ed6a2","tenant_id":"tenant-audit-demo","seq":3,"actor":"dev@example.com","at":"2026-10-06T09:00:04.000Z","op":"update","entity":"flag","target_key":"checkout-v2","before":"{\"key\":\"checkout-v2\",\"kind\":\"bool\",\"variations\":[{\"id\":\"on\",\"value\":true},{\"id\":\"off\",\"value\":false}],\"defaultVariationId\":\"off\",\"rules\":[]}","after":"{\"key\":\"checkout-v2\",\"kind\":\"bool\",\"variations\":[{\"id\":\"on\",\"value\":true},{\"id\":\"off\",\"value\":false}],\"defaultVariationId\":\"on\",\"rules\":[]}","prev_hash":"913ff69664914c4cfbc25905f8b52327fc3383338e51e05d2da9a2391f391777","hash":"b3b9fb34b795bc6d85f9d142fcb95f36faa9a386e36437146135be0f472a37ef"}
{"chain":{"verified":true,"count":4,"legacy":0,"brokenAtSeq":null,"head":{"seq":4,"hash":"409547da21a558e94dd18fcfd26ccebb6001a34fdf91e779ee58730824872d5f"},"anchor":"absent"}}
The JSON audit list for checkout-v2 returned the same rows with the version that first published each change: seq 1 has "publishedIn": 1, and seq 3 has "publishedIn": 2.
The attempts to edit stored rows returned these errors:
=== UPDATE flag_events
D1_ERROR: flag_events is append-only: SQLITE_CONSTRAINT (extended: SQLITE_CONSTRAINT_TRIGGER)
=== DELETE flag_events
D1_ERROR: flag_events is append-only: SQLITE_CONSTRAINT (extended: SQLITE_CONSTRAINT_TRIGGER)
After the trigger was removed in the throwaway local database and the actor of seq 3 was changed to someone-else@example.com, the export printed:
{"event_id":"b193d96d-5d86-4d95-b7a8-83a2bf3ed6a2","tenant_id":"tenant-audit-demo","seq":3,"actor":"someone-else@example.com","at":"2026-10-06T09:00:04.000Z","op":"update","entity":"flag","target_key":"checkout-v2","before":"{\"key\":\"checkout-v2\",\"kind\":\"bool\",\"variations\":[{\"id\":\"on\",\"value\":true},{\"id\":\"off\",\"value\":false}],\"defaultVariationId\":\"off\",\"rules\":[]}","after":"{\"key\":\"checkout-v2\",\"kind\":\"bool\",\"variations\":[{\"id\":\"on\",\"value\":true},{\"id\":\"off\",\"value\":false}],\"defaultVariationId\":\"on\",\"rules\":[]}","prev_hash":"913ff69664914c4cfbc25905f8b52327fc3383338e51e05d2da9a2391f391777","hash":"b3b9fb34b795bc6d85f9d142fcb95f36faa9a386e36437146135be0f472a37ef"}
{"chain":{"verified":false,"count":4,"legacy":0,"brokenAtSeq":3,"head":{"seq":4,"hash":"409547da21a558e94dd18fcfd26ccebb6001a34fdf91e779ee58730824872d5f"},"anchor":"absent"}}
What the output shows:
- Who, what, when, and what changed: the seq 3 row names the actor
dev@example.com, the time2026-10-06T09:00:04.000Z, the operationupdateon flagcheckout-v2, and the definitions before ("defaultVariationId":"off") and after ("defaultVariationId":"on"). - Shipped or still a draft: seq 3 was first published in version 2. The seq 4 edit was still in the draft when the log was exported, and the audit list marks such an edit with
nulluntil a version publishes it. - Append-only storage: the database refused both the
UPDATEand theDELETEwithflag_events is append-only. - Tamper evidence: the altered row kept its old stored hash, so recomputing the chain no longer matched, and verification changed from
"verified":trueto"verified":falsewith"brokenAtSeq":3, the exact row that was edited. - The anchor:
"anchor":"absent"means no daily anchor existed yet in this fresh local run. In Loglune, a daily job verifies each customer’s chain, stores the head sequence number and hash as an anchor in R2, and warns when the chain is broken or does not match the anchor.
The event sourcing guide uses the same change history to rebuild a flag’s state at a past time, and the git revert guide shows how Loglune undoes a single change event by appending a new one, so the audit log keeps both the mistake and the fix.
How long should you keep audit logs?
Keep audit logs for the longest period any regulation or contract that applies to you requires, then add time for investigations. The published sources name these periods:
| Framework | Retention named | Source |
|---|---|---|
| HIPAA | Six years | eCFR, 45 CFR 164.316(b)(2), for the documentation the rule requires |
| ISO 27001 | At least three years | CrowdStrike |
| SOX | Seven years | Ping Identity |
| PCI DSS v4.0 | 12 months, with three months immediately available | Optro |
Optro’s FAQ recommends matching the longest applicable regulatory minimum, extending for litigation hold and business need, and documenting a mapping from each system to the regulation and retention it falls under. Long retention has storage and maintenance costs, which is why teams decide which logs matter most before setting one period for everything.
In Loglune, the flag change history is kept until the customer is deleted, and the separate control-plane audit of key and token issuance, membership and role changes, invitations, and spending limit changes is kept for 1 year.
Which compliance frameworks require audit logs?
An audit trail is mandatory when a regulation or contract that applies to you requires one. Several frameworks ask for audit logging or audit trails, and SOC 2 audits use audit logs as evidence that controls operated during a period. The sources in this guide name these:
- HIPAA. The HIPAA Security Rule requires covered entities and business associates to implement hardware, software, and procedural mechanisms that record and examine activity in information systems containing electronic protected health information, specified at 45 CFR 164.312(b).
- SOX. The Sarbanes-Oxley Act requires public companies to maintain internal controls over financial reporting, which in practice requires audit trails for financial transactions and system access.
- PCI DSS. PCI DSS Requirement 10 requires tracking and monitoring all access to network resources and cardholder data through logging.
- SOC 2, CIS, and PCI DSS. These are regulations that affect many industries, and audit logs can show that an organization met certain benchmarks during a specific time period.
- NIST CSF 2.0 and PCI DSS v4.0. Both expect controls that prevent log alteration.
Microsoft’s audit logging overview lists SOC 2 among the external audits that cover its audit logging controls.
Who can view audit logs?
Only the people whose job requires it. Sumo Logic recommends limiting access to authorized personnel with role-based access control and multi-factor authentication, and Microsoft limits management of its service audit logs to a subset of the security team. A useful rule is that the people who can change a system should be able to read its audit log but not edit it, and nobody should be able to delete rows.
Audit logging best practices
- Decide what to audit before you collect everything. Datadog names knowing what to audit as the primary challenge of audit logging, because distributed systems emit large volumes of logs.
- Record actor, action, target, time, and before and after values in one row, so one row answers the question without joins.
- Store rows append-only and verify them with a hash chain, and copy the chain head to separate storage on a schedule.
- Keep audit logs out of reach of the administrators of the audited system.
- Set retention from the strictest regulation that applies, and write down which system falls under which rule.
- Export in a plain format such as JSON or NDJSON, so auditors and other tools can read the rows and recheck the chain. Optro’s vendor criteria include exportability via API or SIEM connector, not just a UI.
- Review the logs on a schedule. Wikipedia’s article notes that a proposed update to the HIPAA Security Rule (NPRM, December 2024) would require reviewing audit logs at least once every 30 days.
FAQ
How long does it take for an audit record to be available after an event?
It depends on the system: some auditing systems make records available almost immediately, and others process logs in batches with a delay. In the Loglune run above, each change event was in the exported audit log as soon as the change was written.
Does reading the Loglune audit log count toward billing?
No. Audit is a read operation in Loglune and does not count toward billing. Audit logs are included in the Team Standard plan.
Audit logging for the changes that ship without a deploy
Audit logging tracks critical changes within your tech stack, and protecting audit logs from alteration is part of the job. Feature flag changes are critical changes that skip the commit history, so they need their own audit log with the same protection. Loglune records each flag change with the actor, the time, and the values before and after, refuses updates and deletes on that history, chains every row with a hash, and exports the rows with a verification result that names the first altered row, as the run above showed with "brokenAtSeq":3.
Add tamper-evident audit logging to every feature flag change
Loglune records every flag change as a change event with the actor, the time, and the values before and after, in a history that refuses updates and deletes and links each row in a hash chain. The audit operation lists those events with the first version that published each one, and exports the whole chain as NDJSON with the verification result on the last line. Audit is a read operation and does not count toward billing. Audit logs are included in the Team Standard plan, $20 per seat per month for 2 to 50 seats.