A lightweight, zero-config OpenTelemetry viewer for local development — traces, logs, and metrics in one place.
Drop-in replacement for a collector endpoint — point your OTLP exporter at it and see your telemetry immediately. No database required.
- Zero config — listens on port 4318 (OTLP/HTTP) and 4317 (OTLP/gRPC), the standard OTLP ports. Most exporters work without changing a single setting
- OTLP ingestion — OTLP/HTTP accepts OTLP/JSON (
application/json) and OTLP/Protobuf (application/x-protobuf); OTLP/gRPC accepts Protobuf exports over HTTP/2, with optional gzip compression - Real-time streaming — traces, logs, and metrics all update live over SSE (Server-Sent Events) using an incremental snapshot + delta protocol, so views refresh in place without flicker or polling
- Traces and spans — trace list; waterfall timeline with full span details, and correlation with logs
- Logs — log list; log details view; correlation with traces
- Metrics — gauges, counters, up/down counters, histograms, exponential histograms, and summaries with time-series charts, histogram distribution + heatmap views, per-series filtering, and a paginated datapoint table with attributes
- Service map — auto-generated graph of cross-service calls with error rates and latency (p50/p99)
- Search & filter — filter traces, logs and metrics by text, id, service, severity, and attributes: search spans inside a trace based on attributes, events, and span name or id
- MCP server — read-only Model Context Protocol endpoint for AI agents to search traces, logs, and metrics, inspect records, list services, and view the service map
- Import/export traces and logs — export one trace or log, or the filtered or selected traces/logs, as an OTLP JSON envelope; import from OTLP JSON or otel-gui export files with metadata preview before confirmation
- Bulk list actions — trace and log lists support multi-select export and split delete actions (
Clear All+Delete Selected (n)) - Keyboard navigation — rich keyboard control: arrow keys for the span tree,
/to search,t/l/m/sto jump to Traces/Logs/Metrics/Service Map tabs, Enter/Space to activate focused rows in the Traces and Logs grids, escape key to clear search and go back to the list,?for shortcuts help - Error navigation — jump between error spans with one key
- Span details — attributes, events with timeline markers, resource attributes, instrumentation scope, span links, correlated logs
- Global logs workflow — browse all logs in a dedicated tab, open full log details, and jump from logs to the owning trace/span
- Trace and log counts — the trace list shows both span and correlated log counts, and trace detail summarizes correlated logs alongside spans/services/depth
- Collapse/expand — hide subtrees in the waterfall for cleaner viewing
- Resizable panels — drag splitters to resize the waterfall name column and the span details sidebar
- Dark mode — toggle between light and dark themes
- Incremental ingestion — spans from the same trace can arrive in separate requests and out of order; the store merges them correctly
- In-memory with optional local persistence — default is in-memory only; opt into PGlite-backed restart recovery with bounded retention
otel-gui can be installed from a custom Homebrew tap:
brew install metafab/tap/otel-guiThen run:
otel-gui
# or override default port (4318)
PORT=55681 otel-gui
# open the UI in your default browser on startup
otel-gui --open # or -o
# show usage
otel-gui -h # or --helpNotes:
- Homebrew formula updates are automated from tagged releases (
v*) by the release workflow.
To update to the latest version, you'll use:
brew update
brew upgrade metafab/tap/otel-guiWith Nix flakes enabled, run the latest version from this repository:
nix run github:metafab/otel-guiTo install it into your user profile:
nix profile install github:metafab/otel-guiThe app listens on port 4318 by default. Set PORT to use a different port:
PORT=55681 nix run github:metafab/otel-guiFor development in a checkout, enter the pinned Node.js and pnpm environment with:
nix develop
pnpm install
pnpm run devPull and run the published GHCR image:
docker pull ghcr.io/metafab/otel-gui:latest
docker run --rm --name otel-gui -p 4318:4318 -p 4317:4317 ghcr.io/metafab/otel-gui:latestContainer tags are published from Git refs:
latestfor the default branchv*tags (for example,v1.1.0)sha-<commit>immutable tags
Build and run locally with Docker:
docker build -t otel-gui .
docker run --rm -p 4318:4318 -p 4317:4317 otel-guiThen use the standard OTLP endpoint:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# or, for gRPC exporters
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317The container reads PORT (default 4318). You can override it at runtime:
docker run --rm -e PORT=55681 -p 55681:55681 otel-guiUsing port 4318 is recommended for zero-config OTLP exporters. The OTLP/gRPC receiver listens on OTEL_GUI_GRPC_PORT (default 4317).
Run with Docker Compose:
docker compose up --buildRun in background:
docker compose up -d --buildStop:
docker compose downTo use a different port:
PORT=55681 docker compose up --buildRequires: Node.js ≥ 22.17, pnpm
git clone https://github.com/metafab/otel-gui
cd otel-gui
pnpm install
pnpm devOpen http://localhost:4318 — the OTLP endpoint is live at the same address.
pnpm run dev # Start dev server on port 4318
pnpm run lint # Lint TypeScript, JavaScript, and Svelte files
pnpm run format # Format files with Prettier
pnpm run format:check # Check formatting without writing changes
pnpm run check # TypeScript type-check
pnpm run test # Run unit tests (Vitest)
pnpm run test:watch # Tests in watch mode
pnpm run build # Production buildPoint any OpenTelemetry SDK exporter at the viewer:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318No other configuration needed. The viewer accepts the standard POST /v1/traces endpoint.
Requests with Content-Encoding: gzip are also supported.
The viewer also accepts OTLP logs at:
POST /v1/logsUse the same traceId/spanId values as your spans to get correlated logs in trace detail sidebar.
The viewer also accepts OTLP metrics at:
POST /v1/metricsGauges, sums, histograms, exponential histograms, and summaries are all supported and appear in the Metrics tab. Monotonic sums are presented as Counters and non-monotonic sums as UpDownCounters; their OTLP data type remains Sum. Sum points get a server-computed per-second rate (with counter-reset detection); histograms render as both a distribution and a time/bucket heatmap.
OTLP/gRPC exporters can send traces, logs, and metrics to the standard gRPC port:
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317The receiver implements the OTLP TraceService, LogsService, and MetricsService Export methods over plaintext HTTP/2 (no TLS), with optional gzip compression. Change the port with OTEL_GUI_GRPC_PORT, or disable the receiver with OTEL_GUI_GRPC_ENABLED=false. If the port is already in use, a warning is logged and the HTTP server keeps running.
Run the bundled e-commerce demo to see all features immediately:
./demo-ecommerce.shOn Windows (PowerShell):
.\demo-ecommerce.ps1If script execution is blocked, run it for the current shell session only:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\demo-ecommerce.ps1This sends a realistic multi-service trace (frontend → backend-api → auth-service + database) with errors, retries, incremental span arrival across two requests, correlated logs, and staged metrics.
Run the dedicated metrics-only demo:
./demo-metrics.shOn Windows (PowerShell):
.\demo-metrics.ps1# Simple 3-span trace
curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
-d @samples/sample-trace.json
# Correlated logs for the simple trace
curl -X POST http://localhost:4318/v1/logs \
-H "Content-Type: application/json" \
-d @samples/sample-log.json
# E-commerce trace — part 1 (frontend + backend-api)
curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
-d @samples/sample-trace-ecommerce-part1.json
# E-commerce correlated logs — part 1
curl -X POST http://localhost:4318/v1/logs \
-H "Content-Type: application/json" \
-d @samples/sample-log-ecommerce-part1.json
# E-commerce trace — part 2 (auth-service + database with errors)
curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
-d @samples/sample-trace-ecommerce-part2.json
# E-commerce correlated logs — part 2
curl -X POST http://localhost:4318/v1/logs \
-H "Content-Type: application/json" \
-d @samples/sample-log-ecommerce-part2.json
# Trace with error spans (status.code = 2)
curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
-d @samples/sample-trace-error.json
# Trace with span links
curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
-d @samples/sample-trace-links.json
# Gauge metrics (memory usage)
curl -X POST http://localhost:4318/v1/metrics \
-H "Content-Type: application/json" \
-d @samples/sample-metrics-gauge.json
# Counter metrics (monotonic OTLP Sum with cumulative points)
curl -X POST http://localhost:4318/v1/metrics \
-H "Content-Type: application/json" \
-d @samples/sample-metrics-sum-counter.json
# Histogram metrics (database latency distribution)
curl -X POST http://localhost:4318/v1/metrics \
-H "Content-Type: application/json" \
-d @samples/sample-metrics-histogram.json
# E-commerce metrics staged flow (part 1 then part 2)
curl -X POST http://localhost:4318/v1/metrics \
-H "Content-Type: application/json" \
-d @samples/sample-metrics-ecommerce-part1.json
curl -X POST http://localhost:4318/v1/metrics \
-H "Content-Type: application/json" \
-d @samples/sample-metrics-ecommerce-part2.jsonSee SAMPLE_TRACES.md for a full feature exploration guide.
The Streamable HTTP MCP endpoint is available at http://localhost:4318/mcp (assuming the default port 4318).
For example, add it to Claude Code:
claude mcp add --transport http otel-gui http://localhost:4318/mcpAdd this server to a VS Code .vscode/mcp.json file:
{
"servers": {
"otel-gui": {
"type": "http",
"url": "http://localhost:4318/mcp"
}
}
}The server exposes read-only tools: search_traces, get_trace, search_logs,
get_log, search_metrics, get_metric, list_services, and
get_service_map. Search results and detail payloads are bounded; oversized
responses return a tool error rather than being silently truncated.
MCP is enabled by default and restricted to loopback clients. To disable it, set
OTEL_GUI_MCP_ENABLED=0. For Docker or reverse-proxy setups where the client
does not arrive as loopback, OTEL_GUI_MCP_ALLOW_REMOTE=1 opts into remote
access. Only do this when network exposure is appropriately restricted:
otel-gui does not authenticate MCP clients, and the MCP tools can read retained
telemetry.
Agents that can make HTTP requests can read telemetry from the existing
/api/* routes without an MCP client. See the HTTP API guide
for examples, limits, response shapes, and access considerations. A
machine-readable OpenAPI specification is also served by
the running instance at /openapi.yaml; the root llms.txt
provides a short discovery index.
These application routes are unversioned and currently have no authentication.
The OpenAPI document describes existing behavior but does not establish a
compatibility guarantee. For read-only access, use the GET routes; treat
DELETE and import endpoints as destructive. MCP remains an alternative with
purpose-built search tools.
| Variable | Default | Description |
|---|---|---|
PORT |
4318 |
HTTP port the server listens on |
OTEL_GUI_CORS_ALLOWED_ORIGINS |
* |
Allowed CORS origin(s) for the OTLP ingest (/v1/*) and read API (/api/*) endpoints, so browser-based OTLP exporters can post telemetry cross-origin. Use * to allow any origin, or a comma-separated list of exact origins (e.g. https://app.example.com,http://localhost:5173). For non-local/public deployments, prefer an explicit origin list to avoid exposing trace data to arbitrary websites. |
OTEL_GUI_MAX_TRACES |
1000 |
Maximum number of traces kept in memory (1–10 000). Oldest traces are evicted first when the limit is reached. Requires a restart. |
OTEL_GUI_MAX_LOGS |
1000 |
Maximum number of log records kept in memory (1–10 000). Oldest records are evicted first when the limit is reached. Requires a restart. |
OTEL_GUI_MAX_METRICS |
1000 |
Maximum number of metric entries keyed by service.name + metric name (1–10 000). Each entry may contain multiple attribute series; this does not cap series cardinality or points per series. Oldest-created entries are evicted first. Requires a restart. |
OTEL_GUI_MAX_METRIC_POINTS |
600 |
Maximum number of data points retained per metric series (10–10 000). Oldest points are evicted first. Requires a restart. |
OTEL_GUI_GRPC_ENABLED |
true |
Enables the OTLP/gRPC receiver. Set to 0 or false to disable it. |
OTEL_GUI_GRPC_PORT |
4317 |
Port the OTLP/gRPC receiver listens on. |
OTEL_GUI_GRPC_HOST |
(all interfaces) | Interface the OTLP/gRPC receiver binds to. |
OTEL_GUI_MCP_ENABLED |
true |
Enables the read-only MCP endpoint at /mcp. Set to 0 or false to disable it. |
OTEL_GUI_MCP_ALLOW_REMOTE |
false |
Allows non-loopback clients to use /mcp. Only enable when remote access is protected at the network layer; otel-gui does not authenticate MCP clients. |
OTEL_GUI_PERSISTENCE_MODE |
memory |
Persistence backend mode. Use memory (default, no disk writes) or pglite (requires an external backend module, typically enterprise). |
OTEL_GUI_PERSISTENCE_PATH |
.otel-gui/pglite |
Directory path for local PGlite data when persistence mode is pglite. |
OTEL_GUI_PERSISTENCE_FLUSH_MS |
750 |
Debounce interval for batched persistence flushes in milliseconds (50–60000). |
OTEL_GUI_PERSISTENCE_BACKEND_MODULE |
(empty) | Optional module id/path dynamically loaded at startup to register persistence backends. Relative file paths resolve from the otel-gui project root (examples: @otel-gui/enterprise-persistence/register, ../otel-gui-enterprise/enterprise-persistence/dist/register.js). |
OTEL_GUI_LICENSE_KEY |
(empty) | Optional enterprise license key consumed by private persistence backend modules. |
OTEL_GUI_LICENSE_PUBLIC_KEY_PATH |
(empty) | Optional filesystem path to the PEM-encoded public key used by enterprise modules for offline license verification. |
Copy .env.example to .env to customize:
cp .env.example .env
# then edit .envFor external backend registration details, see docs/enterprise-persistence-module.md.
When OTEL_GUI_PERSISTENCE_MODE=pglite falls back to memory, check GET /api/config -> persistence.unavailableReason for a precise cause.
pnpm build
PORT=4318 node buildThe production build uses @sveltejs/adapter-node. In-memory state is kept alive by the Node.js process — no external store required for local use.
Direct HTTP requests use their connection scheme for origin checks. Behind a
TLS-terminating reverse proxy, set PROTOCOL_HEADER=x-forwarded-proto and
configure the trusted proxy to remove client-supplied X-Forwarded-Proto
headers and set the correct value itself. Only enable this setting when clients
cannot bypass the proxy. The proxy must also forward the correct Host header.
In Docker, with the default configuration, traces are in-memory only and are lost when the container stops.
otel-gui can be packaged as a self-contained executable using Node 22 SEA (Single Executable Applications).
Requirements:
- Node.js 22.x (for SEA blob generation)
pnpm
Build for your current OS/arch:
pnpm run build
pnpm run sea:packageGenerate for a specific target platform:
pnpm run sea:bundle
pnpm run sea:package:target -- --target linux-x64Supported targets:
linux-x64linux-arm64macos-x64macos-arm64win-x64win-arm64
Cross-target note:
- If target differs from your host (for example building
linux-x64on macOS), provide a matching Node 22 target binary:
pnpm run sea:package:target -- \
--target linux-x64 \
--node-binary /absolute/path/to/node-linux-x64Output directory:
dist/binaries/otel-gui-<platform>/
otel-gui[.exe]
build/
proto/
Run from that directory:
./otel-gui
# or override default port (4318)
PORT=55681 ./otel-gui
# open the UI in your default browser on startup
./otel-gui --open # or -o
# show usage
./otel-gui -h # or --help
# print version + platform/runtime details
./otel-gui --version # or -vNotes:
- Keep
otel-gui[.exe],build/, andproto/together in the same output folder. - Current OSS executable packaging targets memory mode. Optional enterprise persistence remains an external module workflow.
| Key | Where | Action |
|---|---|---|
/ |
Everywhere | Focus search |
Esc |
Everywhere | Clear search / go back |
t |
Everywhere | Switch to Traces tab |
l |
Everywhere | Switch to Logs tab |
m |
Everywhere | Switch to Metrics tab |
s |
Trace list | Switch to Service Map tab |
s |
Trace detail | Toggle Service Map |
Alt+Backspace |
Trace list | Clear all traces |
↑↓←→ / Enter |
Trace detail | Navigate span tree |
n / N |
Trace detail | Next / prev search match |
e / E |
Trace detail | Next / prev error span |
? |
Everywhere | Toggle shortcuts overlay |
| Interface | Endpoint | Purpose |
|---|---|---|
| OTLP/HTTP | POST /v1/traces, POST /v1/logs, POST /v1/metrics |
Receivers that ingest OTLP/JSON or OTLP/Protobuf |
| OTLP/gRPC | :4317 by default |
Trace, Logs, and Metrics Export services; Protobuf with identity or gzip encoding |
| HTTP API | /api/traces, /api/logs, /api/metrics, /api/service-map, /api/config |
Read and manage telemetry and runtime configuration; see the HTTP API guide and OpenAPI specification |
| Streaming | GET /api/stream |
SSE updates for traces, logs, and metrics |
| MCP | POST /mcp |
Streamable HTTP MCP endpoint; see Connect an AI agent with MCP |
Server-only state lives in src/lib/server/traceStore.ts with swappable backends behind the TraceStore interface. In default memory mode, runtime state is kept in memory with FIFO eviction. The retention limits default to 1000 traces (OTEL_GUI_MAX_TRACES), 1000 log records (OTEL_GUI_MAX_LOGS), 1000 metric entries (OTEL_GUI_MAX_METRICS), and 600 points per metric series (OTEL_GUI_MAX_METRIC_POINTS).
SSE subscribers are notified on every write and receive a debounced event: traces message.
Additional persistence backends (including pglite) are loaded via OTEL_GUI_PERSISTENCE_BACKEND_MODULE and can be distributed separately (for example in an enterprise package).
- SvelteKit 3 with Svelte 5 runes (
$state,$derived,$effect) @sveltejs/adapter-nodefor persistent in-memory stateprotobufjsfor Protobuf decoding- No UI framework — custom waterfall and service-map SVG, with uPlot for metric charts
- TypeScript throughout
You can submit a new idea.
And of course, you can develop an existing or a new idea 😀:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes and add tests if applicable
- Run
pnpm run lint && pnpm run format:check && pnpm run check && pnpm run testto validate - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is open source. See the LICENSE file for details.