Add logviewer: web UI for the ClickHouse log store #1
Reference in New Issue
Block a user
Delete Branch "benvin/initial"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
The vector -> JetStream -> ClickHouse logging stack has no UI: exploring
logs.rawmeans hand-writing clickhouse-client queries, and at ~281M rows/day with no text index a careless query is expensive. logviewer gives a browsable, tail-able web view with guardrails baked in, sitting behind oauth2-proxy at logviewer.unkin.net.Changes
go:embedUI, vendored Bootstrap 3.4.1 + jQuery 3.7.1 (no CDN), mediamover conventionslogreaderuser (CH_URL/CH_USER/CH_PASSWORD); the app does no auth itself — oauth2-proxy is the boundaryparam_*query parameter; setsmax_execution_time=30on every queryGET /api/query(filters: namespace/host/pod/container/app/severity/stream/source, fuzzyq,sql, limit/offset),GET /api/tail(exclusive ms cursor, clamped to 15m, ascending),GET /api/facets(top 20 namespaces/apps/hosts in window),GET /healthz(CH ping)artifactapi.k8s.syd1.au.unkin.net/docker-internal/logviewer(CA-baked buildx plugin, k8s resources set)go test -race, vet and gofmt cleanReviewed on a shallow clone of
benvin/initial(commita81d3bb). All checklist items check out; no issues found.Raw-SQL filter path —
whereClauseininternal/server/query.goalways ANDs the enforced time-bound params first; the optionalsqlfragment is only ever parenthesised and ANDed into that same outer query (( frag )), never a standalone statement.validateSQLFragmentblocks;, and both the code comment and README are explicit that real safety comes from the readonlylogreaderCH user, not the fragment gate.LIMIT(capped at 1000) andmax_execution_time=30are applied on every query unconditionally inchClient.query. Every non-SQL value (filters, fuzzyqtokens, since/until) goes through{name:Type}/param_*binding — grepped all 4fmt.Sprintfcall sites in query.go and none interpolate user-controlled strings into SQL text (only whitelisted column/param names and validated ints).Bounds —
defaultWindow= 15m,maxWindow= 72h enforced server-side inparseTimeRangefor both/api/queryand/api/facets;/api/tailclamps its cursor todefaultWindowinstead (inherently bounded, can't exceed maxWindow). Covered byTestQueryRejectsUnboundedWindow,TestFacetsRejectsWideWindow,TestTailStaleCursorClamped.No app-side auth — README states this explicitly (oauth2-proxy is the boundary).
CH_PASSWORDonly ever flows from env into theX-ClickHouse-Keyheader; never logged (main.go's startup log omits it) or echoed in any API response.Assets/tail/healthz — no CDN references anywhere in
web/(grepped), Bootstrap 3.4.1 + jQuery 3.7.1 are genuinely vendored. Tail cursor uses an exclusive lower bound (timestamp > {since_ms}) so no repeats, advances to the maxts_msseen, tested inTestTailCursorAdvances/TestTailCursorUnchangedWhenNoRows./healthzdoes a realSELECT 1CH ping.Build/test — ran on the clone myself:
gofmt -l .clean,go vet ./...clean,go build ./...clean,go test -race ./...passes (11 tests in internal/server, no third-party deps in go.mod). Tests are meaningful, not rubber-stamped — they assert on the actual SQL text and bound params, not just status codes.Dockerfile/CI — multi-stage (
golang:1.25-alpine→distroless/static-debian12:nonroot), CGO disabled. Diffed.woodpecker/{build,docker}.yamldirectly againstunkin/mediamover: same CA-baked buildx plugin,backend_options.kubernetes.resourcesset on every step,serviceAccountName: defaultmatches mediamover's own convention (no new ServiceAccount needed). logviewer'sdocker.yamlactually quotes"${CI_COMMIT_TAG}"where mediamover's doesn't — a strict improvement.Makefilehaspatch/minor/majortag-and-push targets per convention.CI state: both
ci/woodpecker/push/buildandci/woodpecker/pr/buildgreen.VERDICT: CLEAN
Delta re-review at
09f8fcd(previously reviewed clean at abc81e6; delta =git diff abc81e6 09f8fcd).Scope — delta touches only CI/pre-commit files: new
.pre-commit-config.yaml,.woodpecker/pre-commit.yaml,.woodpecker/test.yaml,build.yamlreworked,docker.yamlrenamed torelease.yaml(100% similarity, no content change). No app code touched. Good.Resources — every step in every workflow sets
backend_options.kubernetes.resources(requests/limits): pre-commit (512Mi/1 → 2Gi/2), test (512Mi/1 → 2Gi/2), build's two steps (512Mi/1→2Gi/2 and 1Gi/1→4Gi/2), release's docker step (1Gi/1→4Gi/2). Good.Push/PR duplication —
build.yaml'swhennarrowed fromevent: [pull_request, push]toevent: pull_requestonly; pre-commit.yaml and test.yaml are PR-only too. No workflow now double-fires on the same commit. Good.release.yaml — still
event: tag, ref: refs/tags/v*, still the CA-baked buildx plugin (buildkit_config trusts artifactapi's CA), still quotes"${CI_COMMIT_TAG}"in bothbuild_args.VERSIONandtags. Unchanged from the already-revieweddocker.yaml(literal rename). Good.Pre-commit hooks — the hook set (
pre-commit-hooksv4.6.0: trailing-whitespace/end-of-file-fixer/check-yaml/check-merge-conflict/mixed-line-ending;pre-commit-golangv0.5.1: go-fmt/go-vet/go-unit-tests) is an exact match for node-lookup's.pre-commit-config.yaml. Good.Issue — exclude is broader than "vendored assets":
exclude: ^web/static/blankets the whole directory, butweb/static/holds both vendored, minified libs (jquery.min.js,bootstrap.min.js,bootstrap.min.css) and first-party hand-written source (app.js, 253 lines;logviewer.css, 14 lines). As written, the exclude also silently exemptsapp.js/logviewer.cssfrom trailing-whitespace, end-of-file-fixer, and mixed-line-ending — files that aren't vendored and have no reason to skip basic hygiene checks. Suggest scoping the pattern to the vendored files specifically, e.g.^web/static/(jquery|bootstrap)\.min\.(js|css)$, so first-party JS/CSS stays covered.VERDICT: ISSUES
Addressed review comment 10485 in
b07872a: narrowed the pre-commit exclude from^web/static/to^web/static/(jquery|bootstrap)[^/]*\.min\.(js|css)$, so only the vendored jquery.min.js / bootstrap.min.{js,css} are exempt. Hand-written web/static/app.js and web/static/logviewer.css are now covered by the hygiene hooks;pre-commit run --all-filespasses with no fixes needed. All three ci/woodpecker/pr/* contexts are green on the new head.