Audit Logging
FORGE provides automatic activity logging that records all significant actions in your application. Every create, update, delete, login, and logout event is captured with full context, enabling compliance auditing, debugging, and security monitoring.
Overview
The audit system captures structured records of user actions along with before/after state snapshots, request metadata, and user identification. Audit logs are immutable -- once written, they cannot be modified or deleted through the application.
Database Schema
The audit_logs table stores all activity records:
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
user_id | UUID (nullable) | Acting user's ID (null for system or API key actions) |
user_email | VARCHAR(255) | Denormalized user email for fast display |
api_key_id | UUID (nullable) | API key used for the action (if applicable) |
action | VARCHAR(20) | Action type: created, updated, deleted, login, logout |
resource_type | VARCHAR(100) | Model or resource name (e.g., user, content) |
resource_id | VARCHAR(100) | Affected resource primary key |
old_values | JSONB | State before the change (for updates and deletes) |
new_values | JSONB | State after the change (for creates and updates) |
ip_address | VARCHAR(45) | Client IP address (supports IPv4 and IPv6) |
user_agent | TEXT | Client user agent string |
url | VARCHAR(2000) | Request URL that triggered the action |
created_at | TIMESTAMP | When the event occurred |
Example Record
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "user-uuid-123",
"user_email": "admin@example.com",
"api_key_id": null,
"action": "updated",
"resource_type": "content",
"resource_id": "content-uuid-456",
"old_values": {
"title": "Old Title",
"is_active": false
},
"new_values": {
"title": "New Title",
"is_active": true
},
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0...",
"url": "/api/admin/contents/content-uuid-456",
"created_at": "2025-01-15T10:30:00Z"
}TIP
The old_values and new_values columns only contain the fields that changed, not the entire record. This keeps storage efficient and makes it easy to see exactly what was modified.
Automatic Logging
Audit logs are generated automatically on CRUD operations. There is no need to manually instrument your handlers -- the system intercepts write operations and records the relevant data.
Logged Actions
| Action | Trigger | old_values | new_values |
|---|---|---|---|
created | A new record is inserted | -- | All fields |
updated | An existing record is modified | Changed fields (before) | Changed fields (after) |
deleted | A record is removed | All fields | -- |
login | A user successfully authenticates | -- | -- |
logout | A user ends their session | -- | -- |
Denormalized User Email
The user_email field is stored directly on the audit record so that log entries remain readable even if the user account is later deleted or modified. This avoids JOIN queries when displaying the audit log.
Admin Interface
The admin panel provides a filterable audit log viewer:
- User Filter -- Filter by the acting user's email or ID
- Action Filter -- Show only specific action types (created, updated, deleted, login, logout)
- Resource Type Filter -- Filter by model name (e.g., only show
contentchanges) - Date Range -- Filter events within a specific time period
- Detail View -- Expand an entry to see full old/new value diffs
WARNING
Audit logs are immutable. The admin interface provides read-only access. There are no update or delete operations available for audit records through the application.
Retention Policy
Audit log retention is configurable to manage database growth:
# forge.yaml
audit:
retention_days: 365 # Keep logs for 1 year
cleanup_schedule: daily # Run cleanup job dailyWhen the cleanup job runs, records older than the configured retention period are permanently deleted from the database.
WARNING
Ensure your retention policy complies with your organization's regulatory requirements. Some industries require audit logs to be retained for a minimum number of years.
API Key Attribution
When an action is performed via an API key rather than a user session, the api_key_id field is populated instead of user_id. This allows you to track which third-party integration made a specific change.
{
"user_id": null,
"user_email": null,
"api_key_id": "api-key-uuid-789",
"action": "created",
"resource_type": "content",
"resource_id": "content-uuid-101"
}Querying Audit Logs
Backend
// Get all changes to a specific resource
let logs = audit_service.get_for_resource("content", content_id).await?;
// Get all actions by a specific user
let logs = audit_service.get_for_user(user_id).await?;
// Get recent login events
let logs = audit_service.get_by_action("login", page, per_page).await?;TIP
For high-traffic applications, consider indexing the audit_logs table on (resource_type, resource_id) and (user_id, created_at) for efficient querying.