What This Template Is For
An audit log records who did what, when, and to which resource. It is the single most requested enterprise feature that product teams underestimate. Building an audit log looks simple on the surface: capture events, store them, display them. In practice, getting the event taxonomy right, ensuring immutability, handling retention across compliance regimes, and making logs actually searchable requires deliberate upfront design.
This template walks you through specifying an audit log system that satisfies security reviews, compliance audits, and customer admin needs. It covers the event taxonomy, log schema, storage architecture, search interface, retention policies, and export/integration capabilities.
If your product handles sensitive data, the HIPAA Compliance Checklist and Fintech Compliance Checklist provide domain-specific requirements that feed into your audit log design. For the broader admin experience that the audit log sits within, see the Admin Console Template. The Product Strategy Handbook covers how to sequence compliance investments for enterprise readiness.
How to Use This Template
- Start by cataloging every user and system action that modifies data or accesses sensitive resources. This is your raw event list.
- Group events into categories (authentication, user management, data access, configuration, billing). This hierarchy makes filtering usable.
- Define the log schema. Every event needs a timestamp, actor, action, resource, and result. Add a structured changes field for mutation events.
- Decide on storage. Append-only writes to a separate datastore (not your main application database) ensure immutability and prevent accidental deletion.
- Specify retention periods per compliance regime. SOC 2 requires 1 year. HIPAA requires 6 years. GDPR requires you to delete personal data, which conflicts with audit retention. Document how you handle each.
- Design the search interface. Audit logs are useless if nobody can find anything. Time range, actor, action, and resource are the minimum filter dimensions.
- Review with your security team, legal counsel, and at least two enterprise customers.
The Template
Audit Log Overview
| Field | Details |
|---|---|
| Product Name | [Product name] |
| Author | [PM or Engineer name] |
| Reviewers | [Names and roles] |
| Date | [Date] |
| Status | Draft / In Review / Approved / In Development |
| Compliance Targets | [SOC 2, HIPAA, GDPR, PCI DSS, FedRAMP, or None] |
Purpose. [Why this product needs an audit log. Which customer segments require it. What compliance certifications depend on it.]
Event Taxonomy
Event categories.
| Category | Description | Example Events |
|---|---|---|
| [Authentication] | [Login, logout, password changes, 2FA events] | [user.login.success, user.login.failed, user.mfa.enabled] |
| [User Management] | [User creation, updates, deletion, role changes] | [user.created, user.role.updated, user.deactivated] |
| [Data Access] | [Reads of sensitive data, exports, downloads] | [record.viewed, data.exported, report.downloaded] |
| [Data Mutation] | [Creates, updates, deletes of business objects] | [project.created, task.updated, document.deleted] |
| [Configuration] | [Org settings, integrations, security policies] | [org.settings.updated, sso.configured, webhook.created] |
| [Billing] | [Plan changes, payment events, invoice actions] | [plan.upgraded, payment.succeeded, invoice.voided] |
| [API] | [API key creation, revocation, usage anomalies] | [api_key.created, api_key.revoked, api.rate_limited] |
Event naming convention. [resource].[action].[result]
- Resource: lowercase, singular (user, project, api_key)
- Action: past tense verb (created, updated, deleted, viewed, exported)
- Result (optional): success, failed, denied
Full event catalog.
| Event Name | Category | Severity | Includes Changes | Triggered By |
|---|---|---|---|---|
| [user.login.success] | [Auth] | [Info] | [No] | [System] |
| [user.login.failed] | [Auth] | [Warning] | [No] | [System] |
| [user.created] | [User Mgmt] | [Info] | [Yes: fields set] | [Admin] |
| [user.role.updated] | [User Mgmt] | [Warning] | [Yes: old/new role] | [Admin] |
| [user.deactivated] | [User Mgmt] | [Critical] | [Yes: status change] | [Admin] |
| [Continue for all events...] |
Log Entry Schema
Required fields.
| Field | Type | Description | Example |
|---|---|---|---|
id | UUID | Unique event identifier | evt_a1b2c3d4e5f6 |
timestamp | ISO 8601 (UTC) | When the event occurred | 2026-03-05T14:30:22.456Z |
org_id | UUID | Organization the event belongs to | org_123abc |
actor | Object | Who performed the action | See Actor schema below |
action | String | Event name from taxonomy | user.role.updated |
category | Enum | Event category | user_management |
severity | Enum | Info / Warning / Critical | warning |
resource | Object | What was affected | See Resource schema below |
result | Enum | success / failed / denied | success |
context | Object | Request metadata | See Context schema below |
Actor schema.
{
"id": "usr_456",
"type": "user",
"email": "[email protected]",
"name": "Jane Smith",
"role": "admin",
"ip_address": "203.0.113.42"
}
Actor types: user, admin, api_key, system, support_agent
Resource schema.
{
"type": "user",
"id": "usr_789",
"name": "John Doe",
"url": "/admin/users/usr_789"
}
Changes schema (for mutations).
{
"changes": [
{
"field": "role",
"old_value": "member",
"new_value": "admin"
},
{
"field": "team_id",
"old_value": null,
"new_value": "team_456"
}
]
}
Context schema.
{
"request_id": "req_abc123",
"session_id": "ses_def456",
"user_agent": "Mozilla/5.0...",
"ip_address": "203.0.113.42",
"geo": {
"country": "US",
"region": "CA",
"city": "San Francisco"
},
"source": "web_app"
}
Source types: web_app, mobile_app, api, cli, system, admin_console
Storage Architecture
| Requirement | Specification |
|---|---|
| Write pattern | [Append-only. No updates or deletes permitted on log entries.] |
| Storage backend | [Separate from application DB. Options: dedicated PostgreSQL with write-only role, S3 + Athena, Elasticsearch, or managed service (Datadog, Splunk).] |
| Write latency | [Asynchronous. Events queued via message bus, written within [X] seconds.] |
| Read latency | [Search results returned within [X] seconds for queries spanning [X] days.] |
| Throughput | [Sustained [X] events/second, burst [X] events/second.] |
| Availability | [Write availability: [X]%. Read availability: [X]%.] |
| Encryption | [At rest: AES-256. In transit: TLS 1.2+.] |
| Backup | [Frequency: [X]. Retention: [X]. Cross-region: Yes/No.] |
Immutability guarantees.
- ☐ Application code has no DELETE or UPDATE permissions on audit log tables
- ☐ Database user for audit writes has INSERT-only privileges
- ☐ Log entries are cryptographically signed or hash-chained to detect tampering
- ☐ Infrastructure access to audit storage is logged separately (meta-audit)
- ☐ Backup deletion requires approval from two authorized personnel
Retention and Lifecycle
| Compliance Regime | Minimum Retention | Data Scope | Deletion Rules |
|---|---|---|---|
| [SOC 2] | [1 year] | [All events] | [After retention period, archive to cold storage] |
| [HIPAA] | [6 years] | [PHI access events] | [Cannot delete. Must retain for full period.] |
| [GDPR] | [Varies] | [Events containing PII] | [Anonymize actor PII on data deletion request, keep event metadata.] |
| [PCI DSS] | [1 year readily available, 7 years archived] | [Payment and access events] | [After 1 year, move to archive tier.] |
| [Default (no compliance)] | [90 days] | [All events] | [Purge after retention period.] |
GDPR and audit log conflict resolution. When a user exercises their right to erasure (Article 17), the audit log must retain the event record for compliance but anonymize personally identifiable information. Replace actor email, name, and IP with anonymized placeholders. Keep the actor ID as a pseudonymized reference. Document this approach in your privacy policy.
Storage tiers.
| Tier | Data Age | Storage | Query Performance | Cost |
|---|---|---|---|---|
| Hot | 0-90 days | [Primary datastore] | [Sub-second search] | [Highest] |
| Warm | 90 days - 1 year | [Compressed, indexed archive] | [< 10 second search] | [Medium] |
| Cold | 1-7 years | [Object storage (S3/GCS)] | [Minutes, on-demand rehydration] | [Lowest] |
Search and Filtering Interface
Filter dimensions.
| Filter | Input Type | Options | Required |
|---|---|---|---|
| Time range | Date picker (start/end) | Presets: Last hour, 24h, 7d, 30d, 90d, Custom | Yes (default: last 7 days) |
| Actor | Searchable dropdown | All users + system actors | No |
| Action | Multi-select dropdown | Grouped by category | No |
| Resource type | Dropdown | All resource types from taxonomy | No |
| Resource ID | Text input | Exact match | No |
| Severity | Checkbox group | Info, Warning, Critical | No |
| Result | Checkbox group | Success, Failed, Denied | No |
| IP address | Text input | Exact or CIDR range | No |
Search capabilities.
| Feature | Specification |
|---|---|
| Full-text search | [Search across action names, resource names, actor names] |
| Saved filters | [Admins can save and name filter combinations] |
| Real-time updates | [Auto-refresh when viewing last 1 hour, or manual refresh button] |
| Permalink | [Every log entry has a shareable URL for incident references] |
| Context expansion | [Click to expand shows full context, changes diff, and related events] |
Export and Integration
Export formats.
| Format | Scope | Max Records | Delivery |
|---|---|---|---|
| CSV | [Current filter results] | [100,000 rows] | [Browser download] |
| JSON | [Current filter results] | [100,000 rows] | [Browser download] |
| JSON Lines | [Full date range export] | [Unlimited] | [Async: email download link] |
SIEM integration.
| Feature | Specification |
|---|---|
| Protocol | [Webhook (HTTPS POST), Syslog (TCP/TLS), or S3 bucket export] |
| Supported targets | [Splunk, Datadog, Sumo Logic, Elastic, Sentinel, custom webhook] |
| Delivery guarantee | [At-least-once with deduplication via event ID] |
| Latency | [< 60 seconds from event occurrence] |
| Filtering | [Configurable: select which event categories to stream] |
| Authentication | [Webhook: HMAC signature. Syslog: mTLS. S3: IAM role.] |
| Backfill | [On initial setup, option to backfill last 30 days of events] |
Filled Example: B2B Project Management SaaS
Event Taxonomy
| Event Name | Category | Severity | Includes Changes | Volume (est.) |
|---|---|---|---|---|
| user.login.success | Auth | Info | No | 12,000/day |
| user.login.failed | Auth | Warning | No | 800/day |
| user.mfa.enabled | Auth | Info | Yes | 50/day |
| user.created | User Mgmt | Info | Yes: email, role, team | 200/day |
| user.role.updated | User Mgmt | Warning | Yes: old/new role | 80/day |
| user.suspended | User Mgmt | Critical | Yes: reason, admin | 15/day |
| project.created | Data Mutation | Info | Yes: name, team, visibility | 500/day |
| project.deleted | Data Mutation | Critical | Yes: project metadata | 20/day |
| task.updated | Data Mutation | Info | Yes: changed fields | 45,000/day |
| data.exported | Data Access | Warning | Yes: format, row count, filters | 150/day |
| org.settings.updated | Config | Warning | Yes: changed settings | 30/day |
| sso.configured | Config | Critical | Yes: provider, status | 5/day |
| api_key.created | API | Warning | Yes: scopes, expiry | 40/day |
| plan.upgraded | Billing | Info | Yes: old/new plan | 10/day |
Estimated total volume: ~59,000 events/day across 2,400 organizations.
Storage Architecture
| Requirement | Specification |
|---|---|
| Storage backend | PostgreSQL 15 (dedicated instance, write-only application role) with TimescaleDB extension for time-series compression |
| Write latency | Async via SQS. Events written within 5 seconds of occurrence. |
| Read latency | < 2 seconds for queries within 90-day hot tier |
| Throughput | Sustained 100 events/second, burst 1,000 events/second |
| Availability | Write: 99.95%. Read: 99.9%. |
| Retention | Hot: 90 days (TimescaleDB). Warm: 1 year (compressed chunks). Cold: 7 years (S3 Glacier). |
Search Interface
The admin console displays audit logs with these default columns: Timestamp, Actor (name + avatar), Action (color-coded badge), Resource (linked), Result (green/red), and an expand button for full context.
Filter presets for common workflows:
- "Failed logins (last 24h)": category=auth, action=user.login.failed, time=24h
- "Admin actions": actor.role=admin, time=7d
- "Data exports": action=data.exported, time=30d
- "Security events": severity=critical, time=7d
SIEM Integration
Supports Splunk HEC, Datadog Log Intake, and generic webhook. Events are streamed in real-time via a dedicated SQS queue per integration. Each event includes a HMAC-SHA256 signature header for webhook verification. Customers configure integrations in the admin console under Settings, Integrations. Each integration can filter by event category to reduce noise.
Common Mistakes to Avoid
- Logging too little. If your audit log only captures CRUD operations on primary resources, you will miss authentication events, configuration changes, and data access events that security teams need for incident investigation.
- Logging too much without structure. Dumping application logs into the audit log makes it unsearchable. Every event must follow the schema and taxonomy. Unstructured text fields are not audit events.
- Storing audit logs in the application database. When a malicious actor compromises the application, they can delete their tracks if audit logs are in the same database. Use a separate datastore with restricted access.
- Forgetting the GDPR/audit conflict. You need audit logs for compliance, but GDPR requires you to delete personal data on request. Solve this upfront with the anonymization approach, not during your first data deletion request.
- Building search as an afterthought. An audit log that nobody can query is the same as no audit log. Invest in the search interface alongside the logging infrastructure.
Key Takeaways
- Define your event taxonomy before writing any logging code. Consistent naming makes search usable.
- Store audit logs in a separate, append-only datastore with restricted access.
- Plan for the GDPR/audit conflict. Anonymize PII in log entries when users are deleted, but keep the event metadata.
- Build the search interface alongside the logging infrastructure. Unsearchable logs are useless.
- Design retention policies per compliance regime and use tiered storage to manage cost.
About This Template
Created by: Tim Adair
Last Updated: 3/5/2026
Version: 1.0.0
License: Free for personal and commercial use
