|
| 1 | +# otel-viewer |
| 2 | + |
| 3 | +A local OTLP trace viewer for rewatch development. Receives OpenTelemetry traces, stores them in SQLite, and provides a web UI for browsing and exporting span data. |
| 4 | + |
| 5 | +This tool was vibe-coded with Claude and replaces the previous Jaeger-based workflow. |
| 6 | + |
| 7 | +## Why not Jaeger? |
| 8 | + |
| 9 | +We originally used the [Jaeger all-in-one](https://www.jaegertracing.io/) Docker image to view OTLP traces. It works, but has several drawbacks for our use case: |
| 10 | + |
| 11 | +- **Requires Docker** — an extra dependency that not every contributor has running, and the image is heavy for what we need. |
| 12 | +- **Generic UI** — Jaeger is designed for production distributed tracing across many services. Its UI is cluttered with service selectors, operation dropdowns, and tag filters that are irrelevant when you're just debugging a single rewatch build or LSP session. |
| 13 | +- **No LLM export** — we frequently want to copy a subtree of spans (with attributes and events) as JSON to paste into an LLM conversation for debugging. Jaeger has no way to do this. |
| 14 | +- **No deep links to traces** — Jaeger generates opaque URLs that don't map cleanly to trace IDs. Our viewer gives each trace a permalink at `/traces/{trace_id}`. |
| 15 | + |
| 16 | +otel-viewer is a single Python file with a SQLite database. It starts instantly, needs no Docker, and has a focused UI tailored to rewatch development. |
| 17 | + |
| 18 | +## Setup |
| 19 | + |
| 20 | +Requires [uv](https://docs.astral.sh/uv/), a fast Python package and project manager written in Rust. It replaces `pip`, `virtualenv`, and `pyenv` in a single tool — it handles installing Python itself, creating virtual environments, and installing dependencies. If you've never worked with Python before, `uv` is the only thing you need to install. |
| 21 | + |
| 22 | +On macOS: |
| 23 | + |
| 24 | +```bash |
| 25 | +brew install uv |
| 26 | +``` |
| 27 | + |
| 28 | +Then install the project dependencies: |
| 29 | + |
| 30 | +```bash |
| 31 | +cd rewatch/otel-viewer |
| 32 | +uv sync |
| 33 | +``` |
| 34 | + |
| 35 | +## Usage |
| 36 | + |
| 37 | +Start the viewer: |
| 38 | + |
| 39 | +```bash |
| 40 | +uv run python server.py |
| 41 | +``` |
| 42 | + |
| 43 | +This starts a server on port 4707 that acts as both an OTLP collector and a web UI. |
| 44 | + |
| 45 | +Run rewatch with tracing pointed at the viewer: |
| 46 | + |
| 47 | +```bash |
| 48 | +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4707 cargo run --manifest-path rewatch/Cargo.toml -- build |
| 49 | +``` |
| 50 | + |
| 51 | +Open http://localhost:4707 in your browser to browse traces. |
| 52 | + |
| 53 | +## Features |
| 54 | + |
| 55 | +- **Trace list**: Shows all captured traces with timestamp, root span name, duration, and span count. |
| 56 | +- **Lazy-loaded span tree**: Click a trace to see its root spans. Expand spans to load children on demand. |
| 57 | +- **Export for LLM**: Select spans via checkboxes, click "Copy for LLM" to copy the full subtree (including all descendants) as JSON to your clipboard. |
| 58 | + |
| 59 | +## API |
| 60 | + |
| 61 | +The following endpoints are available for programmatic access: |
| 62 | + |
| 63 | +- `GET /api/traces` — list all traces |
| 64 | +- `GET /api/traces/{trace_id}/roots` — root spans of a trace |
| 65 | +- `GET /api/spans/{span_id}/children` — direct children of a span |
| 66 | +- `GET /api/spans/{span_id}` — full detail for a single span (attributes, events) |
| 67 | +- `POST /api/export` — export selected spans with full subtrees; body: `{"span_ids": ["id1", "id2"]}` |
| 68 | + |
| 69 | +## How traces work |
| 70 | + |
| 71 | +Only **finished traces** are shown in the UI. A trace is considered finished when its root span (a span with no parent) has been received. This means: |
| 72 | + |
| 73 | +- Traces from completed `build`, `clean`, or `format` commands appear immediately. |
| 74 | +- LSP traces only appear after the LSP server shuts down cleanly and flushes its telemetry. If the LSP process is killed without a graceful shutdown (e.g. `kill -9`), the root `rewatch.lsp` span is never sent and the trace won't be listed. |
| 75 | +- During development, use `--watch` mode (`uv run python server.py --watch`) and restart the LSP server cleanly (via editor restart or SIGTERM) to see its trace. |
| 76 | + |
| 77 | +Spans from in-progress or interrupted traces are still stored in the database but hidden from the trace list. |
| 78 | + |
| 79 | +## Viewing test traces |
| 80 | + |
| 81 | +The rewatch integration tests (`tests/rewatch_tests/`) can forward their OTEL traces to this viewer. Start the viewer, then run tests with `OTEL_VIEWER_ENDPOINT` set: |
| 82 | + |
| 83 | +```bash |
| 84 | +# Terminal 1 |
| 85 | +cd rewatch/otel-viewer |
| 86 | +uv run python server.py |
| 87 | + |
| 88 | +# Terminal 2 |
| 89 | +cd tests/rewatch_tests |
| 90 | +OTEL_VIEWER_ENDPOINT=http://localhost:4707 yarn test tests/build.test.mjs |
| 91 | +``` |
| 92 | + |
| 93 | +Test traces appear in the viewer alongside manually triggered builds. The forwarding is fire-and-forget — if the viewer isn't running, tests pass normally. |
| 94 | + |
| 95 | +## Data |
| 96 | + |
| 97 | +Traces are stored in `traces.db` (SQLite) in the `otel-viewer` directory. Delete this file to start fresh. |
0 commit comments