The devtools package (core, stdlib only) serves a dashboard at /_vukem: recent requests with timings, SQL queries with N+1 detection, jobs, events, sent mail previews, log records, a per-request trace and the route table. Everything lives in bounded in-memory ring buffers (200 records per kind).
Enabling#
DEVTOOLS=true vukem serve # or set DEVTOOLS=true in .envapp, err := vukem.New(vukem.WithDevtools(), /* … */) // app.Devtools; nil when off- The dashboard answers only requests whose direct peer is a loopback address, whose
Hostislocalhost,*.localhostor a loopback IP, and that carry noForwarded,X-Forwarded-*orX-Real-Ipheader. Anything else gets a plain 404. TheHostcheck stops DNS-rebinding pages from reading the dashboard through your browser. DEVTOOLS_INSECURE_ALLOW_REMOTE=truedrops those checks (an app in a container or a remote VM). Anyone who reaches the app then sees its requests, queries, logs and mail.
Pages#
| Path | Shows |
|---|---|
/_vukem/ | counts per kind, N+1 alerts, recent requests |
/_vukem/requests, /_vukem/requests/{id} | requests (method, path, route, status, duration, query count); the trace of one request with a timeline of its queries, logs, jobs, events and mail |
/_vukem/queries | normalized SQL, redacted arguments, duration, caller frame, N+1 badge |
/_vukem/jobs | dispatched and attempted jobs (kind, queue, status, attempt, error) |
/_vukem/events | dispatched event types, listener count, duration, error |
/_vukem/mails, /_vukem/mails/{id} | sent messages and their HTML and text preview |
/_vukem/logs | log records at LOG_LEVEL or above |
/_vukem/routes | the route table with middleware |
Pages are server-rendered with no script, so they work under the default CSP. ?refresh=5 reloads the page every 5 seconds (2–60). The dashboard's own requests are never recorded.
How it collects#
| Kind | Hook |
|---|---|
| Requests | outermost server middleware, so timings cover the whole stack |
| SQL | a database.QueryObserver set by app.DB(); observers added with vukem.WithQueryObservers (tracing) run next to it; statements run on DB.SQL() directly bypass it |
| Jobs, events | queue.Observer and events.Observer |
wraps the transport chosen by MAIL_MAILER | |
| Logs | a handler teed next to the app's |
The observed packages never import devtools; with it off, each hook costs one nil check.
Redaction and N+1#
- SQL is shown as a normalized shape (literals and placeholders become
?). Bound arguments are shown as types and lengths (string(len=16),int,NULL), never values. Jobs and events show kinds and Go types, not payloads. - A
SELECTshape that runs at least 3 times in one request (devtools.Config.NPlusOneThreshold) is flagged as N+1. - Mail HTML renders in a sandboxed
iframeunder a CSP with no script source that blocks remote images (and tracking pixels).