{% extends "base.html" %} {% import "components/macros.html" as ui %} {% block title %}Audit Logging - Documentation{% endblock %} {% block content %}
A first-class, request-scoped auditing framework built to log and replay critical user mutations (Create, Read, Update, Delete, Search) transparently. Powered by Axum extractors, MongoDB, and JSON payloads.
In enterprise-grade software, logs are not secondary diagnostics. They are an immutable audit trail that ensures accountability and replayability. Every critical event records the state of the entity at the time of modification, who performed it, and their network metadata.
No manual extraction of IP addresses, headers, or cookies. The system resolves all metadata implicitly via request parts.
Snapshots are preserved as raw JSON payloads, making historical state transitions fully auditable and replayable.
A real-time search interface available to authorized administrators, supporting filtering by user, action, type, and timeline.
Rather than cluttering handlers with boilerplate code to parse headers and look up user accounts, developers can leverage the custom Axum AuditLogger extractor. This struct implements Axum's FromRequestParts trait.
Adding logger: AuditLogger to any Axum handler automatically intercepts the request context. Writing a log requires just a single asynchronous call.
use crate::audit::AuditLogger;
use axum::{response::IntoResponse, extract::State};
use serde_json::json;
pub async fn update_task_handler(
State(repo): State,
logger: AuditLogger, // <-- Extractor automatically fetches DB, User, IP, UA
axum::Form(input): axum::Form,
) -> Result {
// 1. Perform database operation
let updated_task = repo.update(&input.id, &input).await?;
// 2. Audit the event (single line, fully request-aware)
logger.log(
"Update", // action_type
"Task", // entity_type
Some(updated_task.id), // entity_id
Some("Updated task details".into()), // details
Some(json!(updated_task)), // payload for replayability
).await;
Ok(Redirect::to("/tasks"))
}
Audit logs are persisted in the audit_logs collection in MongoDB. The model captures details on the actor, action, target entity, network details, and the structural payload.
| Field | Data Type | Description |
|---|---|---|
| timestamp | DateTime<Utc> | Chronological timestamp of when the action occurred in UTC. |
| user_id | Option<ObjectId> | Reference identifier of the actor. None for guest actions. |
| username | Option<String> | Cached username of the user for immediate visual queries. |
| action_type | String | The command category (e.g. Create, Update, Delete, View, Login, Search). |
| entity_type | String | The resource kind (e.g. Task, User, Developer, Auth). |
| entity_id | Option<ObjectId> | The target resource primary key identifier. |
| details | Option<String> | Human readable description of the event. |
| payload | Option<serde_json::Value> | A snapshot of the affected model state or diff data for complete audit replayability. |
| ip_address | Option<String> | Origin client IP address resolved via proxy headers or TCP socket. |
| user_agent | Option<String> | Browser details used for authentication forensics. |
Audit logs should always be populated during state changes. Whenever writing an endpoint that uses INSERT, UPDATE, or DELETE operations, inject the AuditLogger and record the event after a successful database execution.
The `payload` field is crucial. By storing the JSON serialization of the model BEFORE or AFTER the action, system administrators can reconstruct state history. For deletions, always record the serial snapshot of the deleted model in the payload so it is never permanently lost to audit inquiries.
To maintain storage hygiene, administrators can purge logs older than x days (minimum 1 day). To prevent accidental loss of audit history, the system enforces a strict archival download requirement: