otel-desktop-viewer is a CLI tool for receiving OpenTelemetry traces while working on your local machine.
개요
otel-desktop-viewer is a local OpenTelemetry viewer. It uses the OpenTelemetry Collector, DuckDB, and Svelte. ~~Also, it has a dark mode~~ Y'all. I added another dark mode. It has dark modes now. Once running, the UI is at localhost:8000. The OTLP receivers listen on localhost:4317 (gRPC) and localhost:4318 (HTTP). Download a pre-built binary for your platform from Releases. On Windows, unzip the archive and run otel-desktop-viewer.exe. release binaries require (Ubuntu 24.04+, Debian 13+, Fedora 40+). This applies to the tarballs, the .deb/.rpm packages, and the Docker images. Stable releases include .deb and .rpm packages on GemFury. Building from source requires Go and CGO.
README
otel-desktop-viewer
Hello there.
otel-desktop-viewer is a local OpenTelemetry viewer. It uses the OpenTelemetry Collector, DuckDB, and Svelte.
Also, it has a dark mode
Y’all.
I added another dark mode.
It has two dark modes now.
Table of Contents
- Screenshots
- Getting Started
- Docker Compose
- Command Line Options
- Observe the Viewer
- Query a Running Viewer
- Search Telemetry from the CLI
- Configuring Your OpenTelemetry SDK
- Example With
otel-cli - Agent Usage Skill
- Chart Palettes
- Implementation
- What’s With the Axolotl??
- Contributing
- License
Screenshots
Traces
Metrics
Logs
Getting Started
Once running, the UI is at localhost:8000. The OTLP receivers listen on localhost:4317 (gRPC) and localhost:4318 (HTTP).
Via Homebrew Cask
On macOS:
brew tap ctrlspice/otel-desktop-viewer
brew install --cask otel-desktop-viewer
Via GitHub Releases
Download a pre-built binary for your platform from Releases.
| Platform | Architecture | File |
|---|---|---|
| macOS | Apple Silicon (M1–M4) | otel-desktop-viewer_darwin_arm64.tar.gz |
| macOS | Intel | otel-desktop-viewer_darwin_amd64.tar.gz |
| Linux | x86_64 | otel-desktop-viewer_linux_amd64.tar.gz |
| Linux | arm64 | otel-desktop-viewer_linux_arm64.tar.gz |
| Windows | x86_64 | otel-desktop-viewer_windows_amd64.zip |
On Windows, unzip the archive and run otel-desktop-viewer.exe.
Linux: release binaries require glibc 2.39 or newer (Ubuntu 24.04+, Debian 13+, Fedora 40+). This applies to the tarballs, the
.deb/.rpmpackages, and the Docker images.
# example: macOS Apple Silicon
curl -LO https://github.com/CtrlSpice/otel-desktop-viewer/releases/latest/download/otel-desktop-viewer_darwin_arm64.tar.gz
tar xzf otel-desktop-viewer_darwin_arm64.tar.gz
./otel-desktop-viewer
Via apt / dnf (Linux)
Stable releases include .deb and .rpm packages on GemFury.
Debian / Ubuntu:
curl -fsSL https://apt.fury.io/ctrlspice/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/fury.gpg
echo "deb [signed-by=/usr/share/keyrings/fury.gpg] https://apt.fury.io/ctrlspice/ * *" \
| sudo tee /etc/apt/sources.list.d/fury.list
sudo apt update
sudo apt install otel-desktop-viewer
Fedora / RHEL:
sudo tee /etc/yum.repos.d/fury.repo <<EOF
[fury]
name=Gemfury Repo
baseurl=https://yum.fury.io/ctrlspice/
enabled=1
gpgcheck=0
EOF
sudo dnf install otel-desktop-viewer
Via go install
Building from source requires Go and CGO.
go version
go env CGO_ENABLED # should print 1
gcc --version # or cc --version
On Windows: You’ll need MSYS2 for CGO compilation:
-
Install MSYS2: Download and install from https://www.msys2.org/
-
Open MSYS2 UCRT64 terminal:
- After installing MSYS2, you’ll see multiple terminal options in the Start Menu
- Choose “MSYS2 UCRT64” (not “MSYS2 MinGW 64-bit” or “MSYS2 MSYS”)
- Or run:
C:\msys64\ucrt64.exe
-
Install required packages:
pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-toolchain -
Add MSYS2 to your PATH (choose one):
Command Prompt (permanent):
setx PATH "%PATH%;C:\msys64\ucrt64\bin"PowerShell (permanent):
[Environment]::SetEnvironmentVariable("PATH", [Environment]::GetEnvironmentVariable("PATH", "User") + ";C:\msys64\ucrt64\bin", "User")PowerShell (current session only):
$env:PATH += ";C:\msys64\ucrt64\bin" -
Restart your terminal for PATH changes to take effect
-
Test the setup:
gcc --version g++ --version
On Linux/macOS: the checks above are sufficient.
@latest resolves to the newest stable tag on the Go module proxy, not an alpha or beta release. Pin a version such as @v0.3.0, or use a GitHub Release binary to avoid compiling locally.
# install the CLI tool
go install github.com/CtrlSpice/otel-desktop-viewer@latest
# run it!
$(go env GOPATH)/bin/otel-desktop-viewer
# if you have $GOPATH/bin added to your $PATH you can call it directly!
otel-desktop-viewer
# if not you can add it to your $PATH by running this or adding it to
# your startup script (usually ~/.bashrc or ~/.zshrc)
export PATH="$(go env GOPATH)/bin:$PATH"
Via Docker
Docker does not require a local Go installation.
Pull from GitHub Container Registry (auto-selects your architecture):
docker pull ghcr.io/ctrlspice/otel-desktop-viewer:latest
docker run -p 8000:8000 -p 4317:4317 -p 4318:4318 ghcr.io/ctrlspice/otel-desktop-viewer:latest
Or pin a specific version:
docker pull ghcr.io/ctrlspice/otel-desktop-viewer:v0.3.0
Explicit per-arch tags are also available:
docker pull ghcr.io/ctrlspice/otel-desktop-viewer:latest-amd64
docker pull ghcr.io/ctrlspice/otel-desktop-viewer:latest-arm64
Or build locally from source (compiles the frontend and Go binary inside the image):
docker build --tag otel-desktop-viewer:latest .
docker run -p 8000:8000 -p 4317:4317 -p 4318:4318 otel-desktop-viewer:latest
Docker Compose
Running your app in Compose? Add the viewer as a service and export OTLP to otel-desktop-viewer:4318 (HTTP) or otel-desktop-viewer:4317 (gRPC).
services:
app:
image: your-apps-image-tag
# Add your app configuration here
otel-desktop-viewer:
image: ghcr.io/ctrlspice/otel-desktop-viewer:latest
ports:
- "8000:8000"
- "4317:4317"
- "4318:4318"
Command Line Options
Telemetry is stored in memory by default. Use --db to persist to a file.
The bare command runs the viewer in the foreground. Keep it running while using the UI or client commands.
Automation should reuse an existing viewer. If none is available, start otel-desktop-viewer --open-browser=false as a managed child and wait for its HTTP endpoint. Stop it only if you started it.
Flags:
--browser-port int Port for the web UI and JSON-RPC API (default 8000)
--db string DuckDB file path (default: in-memory)
--db-max-size string Store size cap, e.g. 512MB or 2GB; oldest telemetry
is pruned past it. 0 disables pruning.
(default: 512MB in-memory, 2GB with --db)
--grpc int OTLP gRPC listen port (default 4317)
--host string Host for OTLP receivers and the web UI (default localhost)
--http int OTLP HTTP listen port (default 4318)
--open-browser Open the browser on launch (default true)
--self-telemetry-endpoint string
Export the viewer's own traces and metrics to this OTLP/gRPC endpoint
-h, --help help for otel-desktop-viewer
-v, --version version for otel-desktop-viewer
otel-desktop-viewer --db ./telemetry.duckdb --db-max-size 4GB
Observe the Viewer
Run another viewer to receive the observed viewer’s own traces and metrics:
otel-desktop-viewer --grpc 4327 --http 4328 --browser-port 8001
Then start the observed viewer in another terminal:
otel-desktop-viewer --self-telemetry-endpoint http://localhost:4327
Omitting --self-telemetry-endpoint keeps self-telemetry off. The endpoint is external to the observed viewer. The monitoring endpoint must remain running through observed viewer shutdown. The caller owns starting, stopping, and waiting for both foreground processes.
Query a Running Viewer
Start the viewer with the bare command, then query it from another terminal:
otel-desktop-viewer query 'SHOW TABLES'
otel-desktop-viewer query 'SELECT service_name, count(*) FROM spans GROUP BY service_name' --limit 50
The query command runs read-only SQL against the viewer at http://localhost:8000. It returns up to 25 rows as aligned columns by default. Use --endpoint for another viewer address, --limit for another row limit, or --json for the JSON result.
Search Telemetry from the CLI
With the viewer running, search its trace, log, or metric summaries from another terminal:
otel-desktop-viewer traces --service checkout --since 30m
otel-desktop-viewer logs --since 1h --limit 50
otel-desktop-viewer metrics --start 2026-10-02T08:00:00Z --end 2026-10-02T09:00:00Z --json
These commands use http://localhost:8000, search the last hour, and return up to 25 summaries. Use --endpoint, --service, --since, --start, --end, --limit, or --json to change those defaults.
In metrics --json, metricRef is the exact UUID text from metrics.id. The viewer generates it; OTLP does not provide it. It is valid only for that database. Pass it unchanged and do not parse it.
Inspect every compact span and trace-linked log row for one trace:
otel-desktop-viewer trace 0123456789abcdef0123456789abcdef
otel-desktop-viewer trace 0123456789abcdef0123456789abcdef --json
Table and JSON output contain the same untruncated fields. Trace start is
min(spans.start_time). Trace duration is
max(spans.end_time) - min(spans.start_time). Span offset and duration are
calculated from received nanosecond timestamps and returned as exact decimal
strings. Logs use the received timestamp unless it is zero, then use the
received observed timestamp. Severity uses received text when present;
otherwise it uses the display band derived from the received number. The body
is the compact body_preview. Use query for complete stored log fields.
Inspect one span with full typed detail and every log associated with that exact trace and span ID:
otel-desktop-viewer span 000000000000002a
otel-desktop-viewer span 0123456789abcdef0123456789abcdef 000000000000002a --json
A standalone span ID returns a not-found result, one exact span, or stable span
summaries when the ID occurs in multiple traces. The default limit is 25; use
--limit to request more or fewer. Ambiguous summaries include both IDs, the
exact match count, and whether more rows are available. It never chooses between
duplicate span IDs from different traces. The qualified form selects only the
requested trace and span pair. Both not-found forms are successful structured
results.
Configuring Your OpenTelemetry SDK
Point your app’s OTLP exporter at the viewer. Send to http://localhost:4318 (HTTP) or http://localhost:4317 (gRPC).
If your SDK supports configuration via environment variables, you can use:
# HTTP
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
# gRPC
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
Declarative configuration
SDKs that support declarative configuration can use a YAML file instead. Save this as otel-config.yaml:
file_format: "1.1"
resource:
attributes:
- name: service.name
value: my-service
tracer_provider:
processors:
- batch:
exporter:
otlp_http:
endpoint: http://localhost:4318/v1/traces
meter_provider:
readers:
- periodic:
exporter:
otlp_http:
endpoint: http://localhost:4318/v1/metrics
logger_provider:
processors:
- batch:
exporter:
otlp_http:
endpoint: http://localhost:4318/v1/logs
Then point your app at it:
export OTEL_CONFIG_FILE=/path/to/otel-config.yaml
[!NOTE] When a config file is used, SDKs ignore the traditional
OTEL_*environment variables entirely (aside from${VAR}substitution inside the file itself).
Example With otel-cli
otel-cli can send test traces from shell scripts, including attributes, events, propagated context, and background spans.
Start the desktop viewer in one terminal:
otel-desktop-viewer
In another terminal, point otel-cli at the viewer:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
Quick span: wrap any command:
otel-cli exec --service my-service --name "check the archive" curl -s -o /dev/null https://archive.org/
Chained spans: otel-cli propagates context automatically:
otel-cli exec --kind producer --service demo --name produce -- \
otel-cli exec --kind consumer --service demo --name consume sleep 0.2
Rich trace: background span, events, attributes, and linked child spans:
sockdir=$(mktemp -d)
carrier=$(mktemp)
otel-cli span background \
--service "otel-cli-example" \
--name "script runtime" \
--attrs "deployment.environment=local,team=platform" \
--tp-carrier "$carrier" \
--sockdir "$sockdir" &
sleep 0.1
otel-cli span event --name "starting work" --attrs "phase=setup,attempt=1" --sockdir "$sockdir"
otel-cli exec --service "otel-cli-example" --name "fetch example" --kind client \
--attrs "http.url=https://example.com" \
--tp-carrier "$carrier" \
curl -s -o /dev/null https://example.com
otel-cli exec --kind producer --service "otel-cli-example" --name "hand off" \
--tp-carrier "$carrier" -- \
otel-cli exec --kind consumer --service "otel-cli-example" --name "process" sleep 0.1
otel-cli span event --name "finished" --attrs "phase=teardown,status=ok" --sockdir "$sockdir"
otel-cli span end --sockdir "$sockdir"
Open http://localhost:8000/traces to explore the result. For more otel-cli features (custom span times, {{traceparent}} in command args, config files, and a built-in TUI server), see the otel-cli README.
Agent Usage Skill
Print the guide bundled with your installed viewer:
otel-desktop-viewer skills
While the viewer is running, the same guide is available at
http://localhost:8000/llms.txt.
Install the otel-desktop-viewer skill from this repository:
npx skills add CtrlSpice/otel-desktop-viewer --skill otel-desktop-viewer
The OTel Desktop Viewer skill gives coding agents focused read-only SQL examples for inspecting telemetry in a running viewer. It requires a build where otel-desktop-viewer --help lists query.
Implementation
The CLI is a custom OpenTelemetry Collector distribution. Its desktop exporter writes telemetry to the store owned by the duckdb extension. The extension:
- exposes data through a JSON-RPC API at
POST /rpc - serves a Svelte web UI embedded in the binary via
go:embed
DuckDB runs in memory by default. Use --db for file-backed storage.
See ARCHITECTURE.md for a full system overview.
What’s With the Axolotl??
Her name is Lulu Axol’Otel. She is very pink, and I love her.
More seriously, I like to give my side projects an animal theme to add a little aesthetic interest on what otherwise might be fairly plain applications.
Contributing
See CONTRIBUTING.md. Please read our Code of Conduct before participating.
License
Apache 2.0, see LICENSE
추천 도구
다른 키워드를 입력하거나 필터를 제거해 보세요.
설치
npx skillfish add ctrlspice/otel-desktop-viewer