This version is in beta. Some features may change before release.

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()).

Info

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.

Info
Capture is non-blocking by design. The layer stamps the request duration, runs the handler, and spawns the insert - the client never waits on the database write.

Wiring

Code
rust
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:

Code
bash
cargo run -- makemigrations
cargo run -- migrate

admin_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:

Code
rust
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 responses
  • exclude_prefix(p) - skip any request whose path starts with p, on top of the built-in exclusions.
  • sample_rate(f) - fraction of requests to log, in [0.0, 1.0] (default 1.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 (default 0 = all). Set 400 for errors-only, 500 for server-errors-only.

See also

  • The plugin contract and the model-owning-plugin migration pattern: arch.md §7.
  • Admin for customizing the list view.
logsrequestsmiddlewareadminplugin