Skip to content
mashiroPublic

About

Local OpenTelemetry viewer for traces, metrics, and logs — single binary, persistent DuckDB storage, browser UI.

Topics

Resources

Stars

17 stars

Watchers

0 watching

Forks

Latest commit

 

History

574 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

otelop

otelop

An OpenTelemetry viewer for traces, metrics, and logs. Single binary, persistent storage, browser UI.

Release Go React License


What it is

otelop runs an OTLP receiver and shows whatever it gets in a browser. No Docker, external database, or Jaeger/Prometheus/Loki stack to wire up. Start the binary, point your app at it, open the page.

It's meant for the loop where you're writing instrumentation and just want to see what came through.

otelop trace view

Metrics Logs
otelop metric view otelop log view

Features

Receive

  • Single binary with the frontend embedded
  • OTLP gRPC and HTTP receivers (built-in OpenTelemetry Collector)
  • Optional OTLP forwarding to one upstream endpoint

Store

  • Embedded DuckDB storage with time-based retention and a size ceiling
  • History persists across restarts with no external setup

Explore

  • Traces, metrics, and logs in one UI, updated live over WebSocket
  • Trace waterfall with a minimap, range selection, span search, errors-only view, and trace-to-logs navigation
  • Metric charts with attribute breakdowns and legend-driven series selection
  • Structured filters for traces and logs with suggestions drawn from stored data
  • Time windows from 1 minute to 3 days (or everything), with previous/next navigation and Load more paging into older results
  • Log context navigation for inspecting records around a selected event
  • Shareable URLs: the selected trace, span, metric, filters, and time window all live in the address bar
  • Keyboard shortcuts: / focuses search, Esc closes the details panel

Integrate

  • GraphQL API at /graphql
  • Documentation bundled in the binary (otelop docs), written for both humans and AI coding agents
  • /healthz and /readyz probes for Kubernetes
  • Optional self-observability for otelop's own traces, metrics, and logs

Install

With mise:

mise use -g github:mashiro/otelop

From GitHub Releases:

Prebuilt binaries for macOS and Linux are available from GitHub Releases.

With Docker:

docker run --rm \
  -p 4317:4317 -p 4318:4318 -p 4319:4319 \
  -v otelop-data:/data \
  ghcr.io/mashiro/otelop:latest

The named volume keeps the DuckDB database when the container is replaced or removed. Delete the otelop-data volume explicitly when you want to discard the stored telemetry.

Quick start

otelop start

This detaches into the background so your terminal stays free. Use otelop status to see what it is listening on and otelop stop to shut it down. Pass --foreground (or -f) if you want logs in the current terminal.

Then point your app at it:

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
OTEL_EXPORTER_OTLP_PROTOCOL=grpc \
your-app

For OTLP over HTTP/protobuf, use port 4318 instead:

OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
your-app

And open http://localhost:4319.

With AI coding agents

Any AI coding agent that supports OpenTelemetry can export to otelop, so you can watch the agent's API calls, tool runs, and prompts live. For example:

Search and filters

Traces and Logs share the same filter model. Search and filters apply within the selected time window, including subsequent pages, and to both retained and live data.

Filter builder

Use Add filter to build a condition from Key / Operator / Value:

  • Standard log keys: trace_id, span_id, service_name, severity_text, severity_number, body
  • Standard trace keys: trace_id, span_id, parent_span_id, service_name, name, kind, status_code, status_message, duration_ms
  • Attribute keys are suggested from stored data in the selected time window, or can be typed as attributes.key / resource.key
  • Operators cover equality, exclusion, contains, wildcard matching, existence, and numeric comparisons
  • Conditions are combined with AND. Trace conditions must all match the same span
  • Click a condition to edit it, pause it temporarily, or remove it
  • The details panel offers filter buttons for standard fields, attributes, and resource values

Text search, conditions (including paused ones), and the time window are recorded in the URL, so opening a URL restores them. Logs on a trace opens the Logs view with a trace_id filter applied.

Query syntax

The same conditions can be typed into the search box and applied with Enter:

attributes.http.method:GET
attributes.http.status_code:500 AND resource.service.name:api
attributes.user.name:"Alice Smith"
attributes.http.route:/api/*
attributes.error.type:*
failed attributes.http.method:GET
Syntax Meaning
attributes.key:value Signal attribute equals value (full match, case-insensitive)
resource.key:value Resource attribute equals value
key:/api/* Unquoted * is a wildcard
key:* Attribute exists (non-null)
key:"Alice Smith" Quoted values use JSON string escaping and match literally, including *
key:~"*request failed*" Wildcard match on a value containing spaces
key:>=500 Numeric comparison; applies only to numeric values
-key:value Exclude matches, including records where the key is missing
a:1 b:2 or a:1 AND b:2 Combine conditions with AND
failed Free text

Keys are case-sensitive and dots are part of the key. String, number, and boolean values are all matched. Free text in Logs searches body, service, severity, trace ID, and the JSON of attributes and resource, including nested keys and values. OR, the NOT keyword, and grouping are not supported; incomplete filters are treated as free text.

Endpoints

Port Purpose
4319 Web UI + GraphQL
4317 OTLP gRPC receiver
4318 OTLP HTTP receiver

The GraphQL endpoint is available at http://localhost:4319/graphql.

The Web UI/GraphQL endpoint has no authentication, so it binds to 127.0.0.1 by default — set --http 0.0.0.0:4319 (or an env/config equivalent) to opt in to exposing it on the LAN. The published Docker image binds 0.0.0.0:4319 inside the container by default, since Docker's own -p publishing is itself an explicit opt-in.

When the Web UI binds to a loopback address, /graphql and /ws reject non-local Host headers to guard against DNS rebinding. An explicit non-loopback bind accepts any Host; use a reverse proxy, load balancer, or network policy to control access on the network path.

Commands

otelop start [flags]   # launch in the background (default), or foreground with -f
otelop restart [flags] # restart with the current flags, environment, and config
otelop stop            # stop the background server
otelop status          # show whether it's running: PID, version, uptime, listen addresses
otelop info            # show the resolved configuration (flags, env, config file, defaults)
otelop logs [-f]       # print the daemon log, or follow it with -f
otelop version
otelop docs list       # list documentation bundled with this binary (--json for JSON)
otelop docs show <name> # print one document as Markdown

start flags:

  --foreground, -f         run in the foreground instead of detaching
  --http                   Web UI listen address           (default 127.0.0.1:4319)
  --otlp-grpc              OTLP gRPC receiver endpoint     (default 0.0.0.0:4317)
  --otlp-http              OTLP HTTP receiver endpoint     (default 0.0.0.0:4318)
  --proxy-url              upstream OTLP endpoint for forwarding
  --proxy-protocol         upstream OTLP protocol          (grpc|http)
  --proxy-auth-type        upstream OTLP auth type         (bearer|basic|headers)
  --proxy-auth-token       upstream bearer token
  --proxy-auth-username    upstream basic auth username
  --proxy-auth-password    upstream basic auth password
  --proxy-auth-headers     upstream header                 (repeatable key=value)
  --storage-path           DuckDB database path             (default: XDG data directory)
  --storage-retention      telemetry retention period       (default 7d)
  --storage-max-size       database size ceiling            (default 4GB)
  --storage-memory-limit   DuckDB memory ceiling             (default 512MB)
  --ui-render-window-max   max rows the traces/metrics/logs tables render at once (default 500)
  --log-level              debug|info|warn|error           (default warn)
  --debug                  export otelop's own telemetry to itself

PID, log, and metadata files live in $XDG_STATE_HOME/otelop/ (defaults to ~/.local/state/otelop/). The DuckDB database defaults to $XDG_DATA_HOME/otelop/otelop.duckdb (or ~/.local/share/otelop/otelop.duckdb). Use otelop info to see the resolved configuration and paths. Open Server info in the Web UI header or query GraphQL status for the running instance's storage usage, signal counts, and latest retention sweep result. Run otelop docs show configuration for a query example.

Bundled documentation

The binary ships its own documentation, matched to the installed version:

otelop docs list                        # getting-started, configuration, investigate-telemetry, troubleshooting
otelop docs show investigate-telemetry  # print one document as Markdown

investigate-telemetry describes how to query stored traces, metrics, and logs through the GraphQL API. Point an AI coding agent at otelop docs list and it can inspect the telemetry of the app it is debugging on its own.

Configuration

Every configuration flag can be set three ways; --foreground is CLI-only. Higher precedence wins:

  1. CLI flag (otelop start --http 127.0.0.1:4319)
  2. Environment variable (OTELOP_HTTP=127.0.0.1:4319 otelop start)
  3. TOML config file at $XDG_CONFIG_HOME/otelop/config.toml (defaults to ~/.config/otelop/config.toml; override with OTELOP_CONFIG_FILE=/path/to/config.toml)

Example ~/.config/otelop/config.toml:

http = "127.0.0.1:4319"
otlp_grpc = "0.0.0.0:4317"
otlp_http = "0.0.0.0:4318"
log_level = "warn"
debug = false

[storage]
path = "" # empty uses $XDG_DATA_HOME/otelop/otelop.duckdb
retention = "7d"
max_size = "4GB"
memory_limit = "512MB"

[ui]
render_window_max = 500 # max rows the traces/metrics/logs tables render at once

[proxy]
url = "https://collector.internal:4318"
protocol = "http"

[proxy.auth]
type = "bearer"
token = "replace-me"

Config key paths map to CLI flags by replacing dots and underscores with hyphens. Environment variables use the same path in uppercase with underscores and an OTELOP_ prefix:

Config key CLI flag Environment variable
http --http OTELOP_HTTP
otlp_grpc --otlp-grpc OTELOP_OTLP_GRPC
otlp_http --otlp-http OTELOP_OTLP_HTTP
log_level --log-level OTELOP_LOG_LEVEL
debug --debug OTELOP_DEBUG
storage.path --storage-path OTELOP_STORAGE_PATH
storage.retention --storage-retention OTELOP_STORAGE_RETENTION
storage.max_size --storage-max-size OTELOP_STORAGE_MAX_SIZE
storage.memory_limit --storage-memory-limit OTELOP_STORAGE_MEMORY_LIMIT
ui.render_window_max --ui-render-window-max OTELOP_UI_RENDER_WINDOW_MAX
proxy.url --proxy-url OTELOP_PROXY_URL
proxy.protocol --proxy-protocol OTELOP_PROXY_PROTOCOL
proxy.auth.type --proxy-auth-type OTELOP_PROXY_AUTH_TYPE
proxy.auth.token --proxy-auth-token OTELOP_PROXY_AUTH_TOKEN
proxy.auth.username --proxy-auth-username OTELOP_PROXY_AUTH_USERNAME
proxy.auth.password --proxy-auth-password OTELOP_PROXY_AUTH_PASSWORD
proxy.auth.headers --proxy-auth-headers OTELOP_PROXY_AUTH_HEADERS

When proxying is enabled, otelop still stores incoming telemetry locally for the UI and also forwards the same traces, metrics, and logs to the configured upstream OTLP endpoint.

When debug = true or --debug is set, otelop exports its own telemetry back to its local receiver. This includes request and storage traces, process and collector metrics, DuckDB query/storage metrics, and application logs. Leave it disabled for normal use if you do not want otelop's own activity mixed into the data you are inspecting.

proxy.auth.type supports:

  • bearer: sends Authorization: Bearer <token>
  • basic: sends Authorization: Basic <base64(username:password)>
  • headers: sends the exact headers configured under [proxy.auth.headers] or --proxy-auth-headers

Do not embed credentials in proxy.url; otelop rejects URLs with userinfo such as https://user:pass@example.com.

Kubernetes health probes

The HTTP listener exposes GET /healthz (liveness, 200 ok) and GET /readyz (readiness, 200 ok or 503 not ready). Readiness requires completed initialization and a running Collector; it becomes unavailable when shutdown starts or the runtime context is canceled. These are lifecycle checks, not per-request database queries or upstream proxy connectivity checks.

For Kubernetes, bind HTTP to 0.0.0.0 so the kubelet can reach the Pod IP. The following container configuration uses the same named HTTP port for all probes:

env:
  - name: OTELOP_HTTP
    value: "0.0.0.0:4319"
ports:
  - name: http
    containerPort: 4319
startupProbe:
  httpGet:
    path: /readyz
    port: http
  periodSeconds: 2
  failureThreshold: 30
livenessProbe:
  httpGet:
    path: /healthz
    port: http
  periodSeconds: 10
readinessProbe:
  httpGet:
    path: /readyz
    port: http
  periodSeconds: 5

Development

The main development tasks are managed by mise:

mise run install # install Go and frontend dependencies
mise run dev     # start backend and frontend development servers
mise run check   # format, lint, and type-check
mise run test    # run Go and frontend tests
mise run build   # build the embedded frontend and Go binary

Component tasks can also be run directly, such as mise run dev:backend, mise run test:frontend, or mise run build:frontend.

The backend automatically restarts when Go sources, GraphQL schemas, bundled documentation, go.mod, or go.sum change, using Air managed by mise. Test-only Go files are excluded. Changes are debounced for 300 ms; frontend assets are built once at startup, not on each backend restart. Vite handles frontend updates separately. If Go compilation fails, fix the source and save again to retry. The isolated e2e-env uses the same watcher and retains its temporary database across restarts.

License

MIT

About

Local OpenTelemetry viewer for traces, metrics, and logs — single binary, persistent DuckDB storage, browser UI.

Topics

Resources

Stars

17 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages