Files
unkin-agent abc81e60c8
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
Add logviewer: web UI for the ClickHouse log store
Single Go binary with embedded Bootstrap 3 + jQuery UI, querying logs.raw
over the ClickHouse HTTP interface as the readonly logreader user. Runs
behind oauth2-proxy; the app does no auth itself. Server-side enforced
time bounds (15m default, 72h max), parameterized queries, raw-SQL WHERE
fragment wrapped with enforced bounds and LIMIT, tail polling with a
clamped cursor, facets, healthz. Woodpecker build/test plus tag-driven
image push to artifactapi docker-internal.
2026-08-23 16:39:22 +10:00

58 lines
2.3 KiB
Markdown

# logviewer
Web UI for the ClickHouse log store (`logs.raw`). Single Go binary with an
embedded Bootstrap 3 + jQuery UI — no CDN assets, no build step.
logviewer does **no authentication itself**. It runs behind oauth2-proxy at
`logviewer.unkin.net`; the proxy is the auth boundary. Query safety relies on
the readonly ClickHouse user (`logreader`) plus server-side guardrails:
- **Time bounds are always enforced.** Missing `since`/`until` default to a
15m window ending now; windows wider than 72h (the table TTL) are rejected.
No request can scan the store unbounded.
- **Everything is parameterized** (ClickHouse `param_*` bound parameters)
except the explicit raw `sql` fragment, which is parenthesised and ANDed
into the outer query — the enforced time bounds and `LIMIT` still apply,
and the readonly user can't write.
- `max_execution_time=30` is set on every query.
## Configuration
| Env | Default | |
|---|---|---|
| `CH_URL` | `http://clickhouse-logs.logging.svc.cluster.local:8123` | ClickHouse HTTP endpoint |
| `CH_USER` | `logreader` | readonly ClickHouse user |
| `CH_PASSWORD` | (empty) | password for `CH_USER` |
| `LISTEN_ADDR` | `:8080` | HTTP listen address |
## API
- `GET /api/query``since`/`until` (RFC3339, unix, or relative `15m`/`6h`/`1d`),
filters `namespace`/`host`/`pod`/`container`/`app`/`severity`/`stream`/`source`,
`q` (space-separated case-insensitive substring terms, all must match),
`sql` (raw WHERE fragment), `limit` (max 1000) / `offset`. Newest first.
- `GET /api/tail` — same filters plus `cursor` (exclusive lower bound, unix ms).
Returns ascending rows and the next `cursor`. Stale cursors are clamped to
the last 15m.
- `GET /api/facets` — top 20 namespaces / apps (`labels['app']`) / hosts by
row count in the window.
- `GET /healthz` — ClickHouse ping.
## UI
Time-range picker (15m/1h/6h/1d/custom), facet dropdowns, debounced fuzzy
search, toggleable raw-SQL box, paged log table with expandable rows
(labels/fields), and a Tail mode (1s polling, autoscroll, pauses when you
scroll up).
## Development
```sh
make build # dist/logviewer
make test # go test -race ./...
make patch # tag + push next vX.Y.Z → CI builds+pushes the image
```
Releases: pushing a `v*` tag builds the container and pushes it to
`artifactapi.k8s.syd1.au.unkin.net/docker-internal/logviewer`.