# log-ui: full reference
> Generated by scripts/build_llms_full.py from README.md and module docstrings. Source: https://github.com/AadityaSalgarkar/log-ui
# log-ui
A self-hosted, wandb-style dashboard for experiments logged with [trackio](https://github.com/gradio-app/trackio).
One Python process serves a read-only JSON API over trackio's sqlite store and a prebuilt React app.
No Node at runtime, no accounts, no cloud.
**Site:** · **For LLMs:** [`llms.txt`](https://aadityasalgarkar.github.io/log-ui/llms.txt)
## Install and run
```
uvx log-ui # run it without installing; serves ~/.cache/huggingface/trackio at http://127.0.0.1:8765
uvx log-ui --dir /path/to/store --project my-project --open
uv tool install log-ui # or install the `log-ui` command (pip install log-ui works too)
uv run --with log-ui log-ui # or run it from a throwaway environment next to your project
```
The latest code from `main`: `uvx --from git+https://github.com/AadityaSalgarkar/log-ui log-ui`.
No runs of your own yet? Write a synthetic store with a small learning-rate sweep and open it:
```
git clone https://github.com/AadityaSalgarkar/log-ui && cd log-ui && uv sync
uv run python scripts/demo_store.py /tmp/log-ui-demo
uv run log-ui --dir /tmp/log-ui-demo --project lm-sweep
```
## Docker
A prebuilt image (linux/amd64 and linux/arm64) is published from this repository's CI:
```
docker run --rm -p 127.0.0.1:8765:8765 ghcr.io/aadityasalgarkar/log-ui # bundled demo store
docker run --rm -p 127.0.0.1:8765:8765 \
-v ~/.cache/huggingface/trackio:/data:ro \
--read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges \
ghcr.io/aadityasalgarkar/log-ui # your runs, read-only
```
Check it was built by this repository's workflow (signed SLSA provenance; an SBOM is attached too), or skip
the registry and build it yourself from source:
```
gh attestation verify oci://ghcr.io/aadityasalgarkar/log-ui:latest --owner AadityaSalgarkar
docker build -t log-ui https://github.com/AadityaSalgarkar/log-ui.git # then run `log-ui` instead
```
Or, from a clone, `docker compose up` (store path via `TRACKIO_STORE`, port via `LOG_UI_PORT`). What the
container can do, by construction:
- **Read your store, never write it.** It is mounted `:ro`. A store on a read-only mount is read through a
private snapshot in the container's `/tmp`, so rows still in trackio's WAL file are included.
- **Nothing else on disk.** With `--read-only` (the default in `compose.yaml`) the root filesystem is
immutable; only an in-memory `/tmp` is writable.
- **Minimal surface.** Alpine plus a Python virtualenv: no compilers, no Node, no shell tools beyond
BusyBox. Always runs as an unprivileged user (uid 10001); `--cap-drop ALL` (default in `compose.yaml`)
removes every Linux capability.
- **Local only.** The examples publish the port on `127.0.0.1`; log-ui makes no outbound connections.
## What you get
- **Projects**: every trackio project in the store with its run count and last write.
- **Workspace**: pick runs in the sidebar; every logged key gets a chart, nested by its path in collapsible
groups (`loss/train/xent` sits in a "train" box inside "loss"; open/closed state is remembered). Global
smoothing (EMA), step / relative / wall-clock x-axis, log y, and a free-form point budget per series.
- **Key plots**: charts at the top that overlay several metrics on one y axis, e.g. `train/loss` with
`val/loss`. Add a metric from any chart's pin menu or a plot's **+** picker; a metric can be in any number of
key plots (A+B, A+C and A+D at once). Colour is the run; each metric gets its own line style (dash, marker,
thickness), assigned automatically and changeable from the plot's legend. Shown on the workspace and run pages.
- **Per-chart settings**: a mean ± std or min–max band over a trailing window of N raw points (never looks
ahead), and fixed x/y limits. Settings persist per project in the browser. The window counts logged points,
so a metric logged every 100 steps gets a band 100× wider in steps than one logged every step.
- **Charts**: a synced cursor across charts, a tooltip on the chart under the mouse (every line's nearest
logged value, so metrics logged at different steps still show), and a full-screen view
with a range brush.
- **Runs table**: status, steps, duration, the config columns that differ between runs, sorting, filtering
and multi-select.
- **Run page**: charts, flattened config, last value of every key, and system metrics when logged.
- **Views**: declarative panels (ladder, heatmap, table, line grid, stats) contributed by plugins, for
project-specific comparisons.
- Refreshes when a selected run logs new rows; dark and light themes; page state lives in the URL.
## The trackio contract
log-ui is read-only and does not import trackio. Its only I/O with trackio is the on-disk sqlite store, and
all of that access lives in one module, [`log_ui/contract.py`](https://github.com/AadityaSalgarkar/log-ui/blob/main/log_ui/contract.py), which declares:
- the files it reads: `/.db` for canonical project names (`[A-Za-z0-9_-]+`), excluding
trackio's `registry-*` databases;
- the tables and columns it reads (`configs`, `metrics`, `system_metrics`) and how their values are encoded;
- the trackio versions it is verified against (`TRACKIO_VERSIONS`, currently `>=0.38,<0.40`).
Connections are opened with `mode=ro`, and each one's schema is checked when it opens. If a store lacks a
declared column, log-ui returns an `unsupported trackio store` error instead of guessing. On a read-only
filesystem, where SQLite cannot create the `-shm` file a WAL database needs, log-ui reads a snapshot of the
database and its WAL from the temp directory instead. To delete, rename or
move runs, use trackio (`trackio.Api().runs(project)`); log-ui picks up the change on its next read.
## Configuration
| Flag | Environment | Default |
|---|---|---|
| `--dir` | `LOG_UI_DIR`, then `TRACKIO_DIR` | `~/.cache/huggingface/trackio` |
| `--host` | `LOG_UI_HOST` | `127.0.0.1` |
| `--port` | `LOG_UI_PORT` | `8765` |
| `--project` | `LOG_UI_PROJECT` | none (opens the project list) |
| `--views module:function` (repeatable) | `LOG_UI_VIEWS` (comma-separated) | entry points in `log_ui.views` |
| `--stale-seconds` | | `120` (a run that logged within this window is "running") |
| `--open` | | open a browser |
Flags override environment variables. The server binds to localhost by default; to reach it on a remote
machine, forward the port (`ssh -L 8765:localhost:8765 box`) rather than binding to `0.0.0.0`.
## API
All endpoints are read-only, return JSON under `/api`, and are documented interactively at `/docs`.
| Method | Path | Purpose |
|---|---|---|
| GET | `/api/health` | version, store dir, supported trackio range |
| GET | `/api/projects` | projects with run counts and last write |
| GET | `/api/projects/{p}/runs` | runs with status, last step, flattened config, summary |
| GET | `/api/projects/{p}/runs/{run}` | one run plus its keys and eval steps |
| GET | `/api/projects/{p}/keys` | metric keys with prefix and run counts |
| GET | `/api/projects/{p}/metrics?runs=a,b&keys=k1,k2&x=step&smoothing=0.5&max_points=1000&bands=k1:10` | series per run and key; `bands` adds a trailing-window mean/std/min/max |
| GET | `/api/projects/{p}/system?runs=a,b&bands=gpu:10` | system metrics (GPU, CPU) when logged |
| GET | `/api/projects/{p}/views` | view specs from plugins |
| GET | `/api/projects/{p}/views/{id}?runs=a,b&metric=m&point=last` | resolved panels |
## Views (plugins)
A view provider is a function `provide(project, runs) -> list[ViewSpec]`. Register it under the entry-point
group `log_ui.views` in your package, or pass `--views my_module:provide`. `runs` carries each run's flattened
config and summary, so a provider can derive categories from the config (datasets, species, seeds) and map
them to metric keys with templates like `val/{category}/{metric}`. Panel types: `ladder` (one line per run
across ordered categories), `heatmap` (runs × categories), `table`, `lines` (small multiples over steps) and
`stats`. See [`log_ui/views.py`](https://github.com/AadityaSalgarkar/log-ui/blob/main/log_ui/views.py) for the dataclasses.
## Development
```
uv sync # backend + test deps (tests write real stores with trackio)
uv run pytest
uv run --with ruff ruff check log_ui tests && uv run --with ruff ruff format --check log_ui tests
cd web
npm ci
npm run dev # Vite on :5173, proxies /api to http://127.0.0.1:8765
npm run lint && npm run typecheck && npm test
npm run build # writes ../log_ui/static (committed, shipped in the wheel)
```
The built app in `log_ui/static` is committed so that installing needs no Node; CI checks it matches the
sources. The project site lives in [`docs/`](https://github.com/AadityaSalgarkar/log-ui/tree/main/docs) and is published with GitHub Pages.
## Versions
Releases follow [semantic versioning](https://semver.org/); see [CHANGELOG.md](https://github.com/AadityaSalgarkar/log-ui/blob/main/CHANGELOG.md) and
[GitHub Releases](https://github.com/AadityaSalgarkar/log-ui/releases). Pin a version with
`uvx log-ui@0.3.1`, `uv tool install log-ui==0.3.1` or `ghcr.io/aadityasalgarkar/log-ui:0.3.1`
(`:0.3` follows the latest patch, `:latest` follows `main`).
To release: set `__version__` in `log_ui/__init__.py`, move the `Unreleased` notes in `CHANGELOG.md` under the
new version, commit, then `git tag vX.Y.Z && git push origin vX.Y.Z`. CI checks the tag matches `__version__`,
creates the GitHub Release with the wheel and sdist, publishes to PyPI (trusted publishing, no stored token)
and publishes the image.
## Roadmap
Media and table panels, artifacts, traces, alerts, sweep pages, run tags and notes, parameter importance,
report authoring.
## License
[MIT](https://github.com/AadityaSalgarkar/log-ui/blob/main/LICENSE).
# Module reference
## log_ui/contract.py
The trackio store contract: the only module in log-ui that touches trackio's files.
log-ui never imports trackio and never writes to its store. It reads the sqlite database trackio keeps per
project, `/.db`, over a read-only connection, and uses exactly the tables and columns
declared in `SCHEMA`. Every connection is checked against `SCHEMA` when it opens; a present table that lacks a
declared column raises `ContractError`, so a trackio schema change fails loudly instead of rendering wrong data.
An absent table reads as empty (older stores predate `system_metrics`).
Value encoding, as written by trackio:
- timestamps are ISO-8601 text; naive values are UTC.
- `metrics.metrics` / `system_metrics.metrics` are JSON objects. Values are numbers, the string "NaN", or
non-scalars (media, tables, histograms). Only finite numbers are kept; "NaN" decodes to None; booleans and
non-scalars are dropped. Keys starting with "__" are trackio-internal and hidden.
- `configs.config` is a JSON object. Keys starting with "_" are trackio-internal (`_Created`, ...).
- project files are named by trackio's canonical project name (`[A-Za-z0-9_-]+`); `registry-*` files are
trackio artifact registries, not projects.
Verified against trackio `TRACKIO_VERSIONS`. Anything not declared here is outside the contract.
## log_ui/api.py
JSON API under /api. Read-only: log-ui never modifies the trackio store.
## log_ui/series.py
Turn metric rows into per-run, per-key series; smoothing and downsampling for the charts.
### smooth
TensorBoard/wandb exponential moving average with debiasing.
s_t = w * s_{t-1} + (1 - w) * v_t, reported as s_t / (1 - w^t). weight=0 returns the input.
### downsample_indices
Indices keeping endpoints plus the min and max of each bucket; result has at most max_points entries.
### rolling_bands
Mean, population std, min and max of the `window` raw values ending at each index in `at` (trailing window).
The window for index i spans [i - window + 1, i]: only points logged up to i, never later ones. At the start of
a run it holds the points that exist so far instead of padding. Independent of how many points are plotted.
### to_payload
Plotted {x, y}; with `band_window`, also the raw-value spread in that many points around each plotted point.
### parse_bands
`key:window` entries -> {key: window}. The last ':' splits, so keys may contain colons.
## log_ui/views.py
Views: declarative panels resolved against run metrics (the generic replacement for wandb custom panels).
A view provider is a callable `provide(project: str, runs: list[RunInfo]) -> list[ViewSpec]` registered
under the entry-point group `log_ui.views` or given on the command line as `module:function`.
Key templates: a panel's `key_template` is either a string with `{category}` and `{metric}` placeholders,
or a dict `{metric_id: template}`; a template value may itself be `{"sub": [tmpl_a, tmpl_b]}` meaning the
value is `a - b`. Missing keys yield null.
### Category (fields: id, label, group, order, meta)
### MetricOption (fields: id, label, help)
### PanelSpec (fields: type, title, categories, key_template, keys, description)
### ViewSpec (fields: id, title, description, panels, metrics, default_metric, best_key, points)
### pick_step
'last': the last step at which any considered key was logged; 'best': argmin of best_key.
### resolve_view
## log_ui/settings.py
Server settings: dataclass defaults, overridden by LOG_UI_* environment variables, then CLI flags.