Request logs
Request logging for umbral. A non-blocking capture layer records each HTTP request to a RequestLog model, browsable in the admin.
Request logs
umbral-logs records every HTTP request to a RequestLog row, browsable in the admin. Capture is fire-and-forget: the response returns to the client immediately and the row is inserted on a background task, so logging never blocks a request and a DB error is swallowed (logged at warn, never propagated).
Each row carries method, path, status, duration_ms, plus best-effort user_id, ip (from X-Forwarded-For / X-Real-IP), user_agent, and a created_at timestamp. The model lives in the logs_requestlog table - migrate creates it the same way the built-in auth and sessions tables are created (from the plugin's models()).
How user_id is resolved. The session/identity surface lives in optional sibling plugins (umbral-auth / umbral-sessions) that umbral-logs deliberately doesn't depend on, so a logs-only app stays slim. v1 reads the X-Umbral-User-Id request header (when present) and parses it as an i64; otherwise user_id is None. Set that header from your auth middleware to attribute logged requests to a user.
Wiring
use umbral::prelude::*;use umbral_logs::LogsPlugin;use umbral_admin::AdminPlugin; App::builder() .plugin(LogsPlugin::default()) // Browse captured requests in the admin (read-only). .plugin(AdminPlugin::default().register(umbral_logs::admin_model())) .build()?;Then run the usual migration loop so the table is created:
cargo run -- makemigrationscargo run -- migrateadmin_model() returns a read-only AdminModel: operators browse logs, they don't author them. Every column is marked readonly. It's gated behind the crate's admin feature (on by default); build umbral-logs with default-features = false for a logs-only app that doesn't pull the admin into its dependency graph.
Exclusions and sampling
By default the logger skips its own/asset traffic - the static mounts (/static, /admin/static), /health, and /favicon.ico - and logs every other request. Tune it with the builder:
LogsPlugin::default() .exclude_prefix("/metrics") // skip an extra path prefix (repeatable) .sample_rate(0.1) // log ~1 in 10 requests, deterministically .min_status(400) // …and only error responsesexclude_prefix(p)- skip any request whose path starts withp, on top of the built-in exclusions.sample_rate(f)- fraction of requests to log, in[0.0, 1.0](default1.0= all). Sampling is deterministic, not random: every Nth candidate request by an atomic counter, so it's reproducible in tests. Out-of-range values clamp to the nearest bound.min_status(s)- only log responses with status>= s(default0= all). Set400for errors-only,500for server-errors-only.