Every debug surface
Each panel below is live in the dashboard whenever the app runs with the devtools tag. Panels fail soft: if a single endpoint is unreachable, the others keep rendering.
App cards
Six cards at the top of the dashboard: Health (polled against /health every 5s), Port, Swagger URL (if docs/swagger.json exists), GraphQL URL (if gqlgen.yml exists), and Queue URL (if an asynqmon compose service is running).
Metrics
Parses the Prometheus text output from /metrics (emitted by pkg/observability) and surfaces: total requests served, in-flight count, and average latency. Refreshes every 5s via SSE.
Profiles
One-click links to every profile net/http/pprof exposes: CPU (30s capture), heap, goroutine, mutex, block, allocs, threadcreate, execution trace (5s), and the pprof index. Each opens in a new tab — point go tool pprof at the URL, or use the built-in HTML UI.
Goroutines
Parses /debug/pprof/goroutine?debug=2, groups goroutines by top-of-stack function, and sorts descending by count. A spike in net/http.(*conn).serve is normal under load; a spike in your own code usually means a leak.
Routes
Scraped from docs/swagger.json. Each row shows method, path, operation summary, request type, and response type — extracted from the OpenAPI 2.0 parameters[in=body].schema or OpenAPI 3.0 requestBody.content.schema, with the primary 2xx response picked automatically.
Services
Runtime state of every compose service attached to this dev session — queried via docker compose ps --format json every 5s. Shows name + health/state as colored pills.
N+1 findings
Grouping by (trace_id, normalized_template) across the SQL ring. Templates are normalized by collapsing string / numeric literals to ? and compressing whitespace. Any group with three or more hits surfaces as a finding, sorted by count descending.
Recent requests
The last 200 requests captured by the devtools middleware (ring buffer — oldest request is overwritten when the cap is hit, see Ring sizes to change the cap). Each row:
- Time — wall clock
- Method — colored badge (GET = cyan, POST = green, PATCH = amber, DELETE = red)
- Path — URL path
- Status — pill (2xx green, 3xx cyan, 4xx amber, 5xx red)
- Duration — wall-clock milliseconds
- Trace — CTA button that opens the trace detail card below the Traces list
- Replay — opens the inline editor
An Export HAR button in the header downloads the current ring as HAR 1.2 JSON for Chrome DevTools, Insomnia, or any HAR-aware viewer.
Recent SQL
Every statement captured by the GORM callback, with wall-clock duration, rows affected, error (if any), and the trace ID that triggered it. Each SELECT row also has an EXPLAIN button.
Traces & trace waterfall
The trace ring holds the most recent 50 traces — older ones are rotated out when new traces arrive (see Ring sizes if 50 is too tight). Clicking a row (or the trace CTA in a request row) pins that trace and opens a detail card below the list. The card fetches /debug/traces/{id} on demand (the summary list endpoint returns traces without spans to keep SSE payloads cheap) and renders:
- A waterfall of nested spans — controller → service → repository → SQL — with bar widths proportional to duration and offsets relative to the root span’s start
- The span’s kind (
server,internal,client) next to its name - A click-to-expand stack beside each span name showing up to 20 call frames captured at span start via
runtime.Callers - OTel events attached to the span (error markers, custom annotations)
Two tabs inside the card:
- Waterfall (default) — the span tree
- Logs — on-demand fetch of every
slogrecord emitted during this request (pulled from/debug/logs?trace_id=…)
The detail card lives in its own DOM surface, so 5s SSE refreshes don’t collapse whatever waterfall you’re reading. A pinned-row banner above the Traces list keeps the inspected trace visible even if its row rotates out of view.
See Tracing & Waterfalls for how to get a multi-bar waterfall in your own code — and how to turn SQL into waterfall bars.
Exceptions
devtools.Recovery wraps pkg/middleware’s recovery middleware. Every panic is recorded with the recovered value, a 20-frame stack, method + path of the offending request, and the trace ID. The table shows all recoveries this session with their stacks rendered as expandable <pre> blocks.
Cache ops
devtools.WrapCache decorates cache.CacheService. Every Get/Set/Delete/Flush/Ping records an entry with op, key, hit/miss flag (for Get), duration, error, and trace ID. Per-trace aggregation answers “did this page actually use the cache?” at a glance.