Skip to content

DevTools

In-app debug bar for Sova HTML apps — Symfony / Clockwork style. Inspect the last request, SQL, logs, outbound HTTP, mail, session, and a live site-wide timeline — without leaving the browser.

Development only

DevTools is off in release builds (cargo build --release). Toml or .enabled(true) cannot turn it on there. Ops escape hatch: SOVA_DEVTOOLS=1.

DevTools tour

Install

bash
cargo add sova --features "web,devtools"
rust
use sova::{App, DevTools, Parser, ServerArgs};

#[tokio::main]
async fn main() -> sova::Result<()> {
    let args = ServerArgs::parse();
    args.init_tracing();

    let mut app = App::web()
        .site("App")
        .public_url("http://127.0.0.1:3000")
        .into_app();

    app.install(DevTools::new()); // on in debug / development by default
    app.get("/", || async { sova::Html("<html><body><h1>hi</h1></body></html>") });
    app.run().await
}

Demo app in the repo:

bash
cargo run -p devtools_demo
# http://127.0.0.1:3030/

What you get

SurfacePurpose
Bottom barStatus / time / SQL / errors chips; click to dock the panel
Dock panelFull Vue SPA in an iframe (/_devtools/app?embed=1)
New tabOpen the same SPA in a browser tab
SSE feedLive request.finished, custom, memory.sample
JSON APISnapshots, logs, custom events, memory, config under /_devtools/*

Tabs

TabContents
RequestMethod, path, status, duration, route / locale / CSRF / rate-limit / encoding
TimelineRecent requests (SSE); click to load a snapshot
DBSQL queries for the selected request (bindings redacted)
CacheKvStore / Cache / Redis ops (sova.store / sova.redis)
Logstracing / console lines (per-request + site-wide)
HTTPOutbound client calls
MailFakeMail / last messages (with mail feature)
JobsTask enqueue / worker (sova.tasks)
AuthSession keys + user / email / roles (redacted)
EventsCustom hub.emit + forwarded domain EventBus (auth/mail/csrf/session/tasks/…)
MemoryProcess RSS sparkline + current/peak/min (Linux + macOS; ~2s)
ConfigProfile + compiled DevTools feature flags

What comes from where

SignalSourceNeeds
Route patternMatchedRouteCapture soft-hookalways
LocaleLocaleCodedevtools-i18n / sova-devtools/i18n
CSRF presentCsrfTokendevtools-csrf
Rate-limit headersratelimit-* / x-ratelimit-* on responsealways (headers)
Content-Encodingresponse headeralways
Session / userSession + CurrentUserdevtools (auth)
SQLsqlx / SeaORM logsdevtools (db) + sqlx logging
Outbound HTTPhttp.client spanshttp-client + sova-devtools/http
MailFakeMail bag + MailSent EventBusmail
Custom eventsDevToolsHub::emit / auth EventBusalways (hub) / auth
Memory RSSbackground sampler (cross-platform)always when DevTools enabled
Cache / KVtracing target: "sova.store"devtools-store (instrumented store)
Redis pub/queuetarget: "sova.redis"devtools-redis
Jobstarget: "sova.tasks"devtools (tasks)

Facade features: devtools, devtools-store, devtools-redis, devtools-i18n, devtools-csrf, devtools-passport, devtools-rate-limit.

Request tab

Timeline

DB queries

Logs

Outbound HTTP

How it works

  1. Middleware opens a collector bag for every non-/_devtools request (needs request_id).
  2. Soft hooks attach SQL / HTTP / mail / session data while the request runs.
  3. On finish, a snapshot is stored and broadcast over SSE.
  4. HTML responses get a tiny host marker + bridge.js (not the full SPA).
  5. The bar toggles a dock iframe; New tab opens /_devtools/app.

Access logs for /_devtools/* are skipped via logger_skip_path("/_devtools") so the panel does not pollute the console or its own Logs tab.

Enable / disable

ContextBehavior
cargo run / debug build, development profileOn by default
Debug build + SOVA_PROFILE=productionOff, unless .enabled(true) or SOVA_DEVTOOLS=1
cargo build --releaseOff, unless SOVA_DEVTOOLS=1
SOVA_DEVTOOLS=0Always off
toml
[development.devtools]
enabled = true

[production.devtools]
enabled = false
bash
SOVA_DEVTOOLS=1   # force on (including release)
SOVA_DEVTOOLS=0   # force off
rust
app.install(DevTools::new());                 // default
app.install(DevTools::new().enabled(true));   // debug only — ignored in release
app.install(DevTools::new().request_cap(200).log_cap(1000));

Filling the tabs

Install related plugins before DevTools when you want those panels populated:

rust
app.install(Mail::from_env());       // Mail tab (fake backend)
app.install(OutboundHttp::new());    // HTTP tab
app.install(DevTools::new());

For Cache / Redis / Jobs, use instrumented plugins (sova-store / sova-redis / sova-tasks) and enable the matching facade features (devtools-store, devtools-redis; jobs come with devtools).

SQL (SeaORM): enable sqlx logging, e.g. Db::from_env().sqlx_logging(true) and/or RUST_LOG=sqlx=debug.

Skip other noisy routes from access logs:

rust
sova::logger_skip_path("/healthz");

Responsive UI

The dock uses the host window width for breakpoints. The UI playground (Vite) embeds the same SPA in an iframe so phone/tablet presets exercise real media queries:

bash
npm --prefix plugins/sova-devtools/ui run playground
# http://localhost:5175/playground.html

Desktop playground

Tablet

Mobile

Security notes

  • /_devtools/* is a GET-only debug surface — do not expose it on the public internet.
  • Release builds keep the plugin inert even if you forget to strip the feature from Cargo.toml.
  • Session values and SQL bindings are redacted/masked in the UI.
  • HTML pages that get the bar send Cache-Control: no-store so browser Back is not served from bfcache (otherwise no server hit → empty Timeline).
  • Plugin catalog: devtools
  • Example: examples/web/devtools (devtools_demo)
  • Core helper: sova::logger_skip_path (skip noisy routes from access logs)