Automatic audit logging for Axum apps. Tower middleware that captures every HTTP request and writes it to PostgreSQL — zero external services.
- Zero-config middleware — wrap your Axum router, every request is logged
- Batch writes — accumulates events, flushes every 100ms or 50 events
- Non-blocking — background Tokio task, never slows down a request
audit!macro — log explicit events from handlers with JSON metadataaudit_diff!macro — capture before/after snapshots when data changesAuditabletrait — implement on your types for automatic diff detection- Skip paths — exclude
/health,/metrics, or any path you choose - Service name — disambiguate entries when multiple services share a DB
[dependencies]
phoxia-auditlog = "0.1"Requires PostgreSQL with the table below.
CREATE TABLE IF NOT EXISTS audit_log (
id BIGSERIAL PRIMARY KEY,
user_id UUID,
action TEXT NOT NULL,
ip TEXT,
method TEXT,
path TEXT,
status SMALLINT,
latency_ms INT,
metadata JSONB,
service_name TEXT NOT NULL,
ts TIMESTAMPTZ NOT NULL DEFAULT now()
);use phoxia_auditlog::{AuditConfig, AuditLayer};
use sqlx::PgPool;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let pool = PgPool::connect("postgres://...").await?;
let config = AuditConfig::new(pool, "my-service");
let (layer, _ctx) = AuditLayer::new(config);
// Add to your Axum router
// let app = Router::new().layer(layer);
Ok(())
}use phoxia_auditlog::audit;
async fn delete_user(State(state): State<AppState>) -> impl IntoResponse {
audit!(state.audit, "user.deleted", {
"user_id": "abc-123",
});
StatusCode::NO_CONTENT
}use phoxia_auditlog::{audit_diff, Auditable};
use serde_json::Value;
#[derive(Clone)]
struct User { name: String, email: String }
impl Auditable for User {
fn to_audit_json(&self) -> Value {
serde_json::json!({ "name": self.name, "email": self.email })
}
}
async fn update_user(State(state): State<AppState>) -> impl IntoResponse {
let old = state.db.get_user(id).await?;
let new = state.db.update_user(id, &payload).await?;
audit_diff!(state.audit, "user.updated", &old, &new);
StatusCode::OK
}| Field | Default | Description |
|---|---|---|
pool |
(required) | sqlx PgPool |
service_name |
(required) | Identifies this service in audit entries |
table_name |
"audit_log" |
Target table name |
skip_paths |
{"/health", "/metrics"} |
Paths excluded from automatic audit |
batch_size |
50 |
Max events before forced flush |
flush_interval_ms |
100 |
Max ms between flushes |
- Append-only: no UPDATE/DELETE on the audit table, ever
- No FK on user_id: deleted users don't erase audit history
- Background writer: flushes batches in a Tokio task — requests never block
- AGPLv3: open source, copyleft
AGPLv3. See LICENSE.