Skip to content

SCRUM-237 — Dashboard log search and live tail

Plan ref: DSH-C8/C9 (docs/11-admin-plane-plan.md). Stacked on SCRUM-236.

What exists

Piece Notes
internal/loki source.LogSource over Loki's HTTP API: Query (query_range backward, streams merged newest first; JSON lines → fields, message from msg), Tail (polls every 1.5 s from the last timestamp, deduped at the boundary), Services (label values, cached 30 s, last good set on error). Timeout, 16 MiB cap, errors without URLs
LogQL builder (loki.Build) the only place LogQL is written: {service="…"[,level="…"]} from validated values (service allow-listed + grammar, level ∈ debug/info/warn/error), contains and request_id as |= literal line filters quoted with Go quoting (which is how LogQL parses strings); newlines refused. An injection string in contains stays a literal
GET /logs (viewer) service, level, contains, request_id, from, to, limit (default 1 h / 200, limit ≤ 1000, range ≤ 7 d); {"entries":[{at,service,level,message,fields?}]}; 400 on invalid input, 502 on Loki errors
GET /logs/tail (viewer, SSE) caps 2 per staff user, 10 global → 429 too_many_tails before any stream bytes; write deadline cleared; : connected, data: <json> events, : keep-alive every 15 s; stops on disconnect; 1 h max (client reconnects). gateway_dev already marks this route Stream

How to verify

cd services/dashboard
gofmt -l . && go vet ./... && go test -race -count=1 ./...

Tests: golden LogQL, escaping and the injection literal; Loki fixtures (two streams merged, JSON line fields, error body, label-value cache); tail delivers each line once and stops on cancel; /logs validation and shape; SSE through a real httptest.Server with viewer tokens: connected, events, 3rd tail by one user 429, 11th global 429, slot freed after disconnect, keep-alive seen.

Results at time of writing

  • gofmt, go vet, go test -race (9 packages): pass.

How it was built

DeepSeek run scoped (Landlock) to services/dashboard (307 s, ~55k output tokens, reasoning effort low). Claude review: LogQL quoting, limiter and SSE lifecycle checked; no changes needed.