DEV Community

Juan Mesaglio
Juan Mesaglio

Posted on

I built a lightweight OpenTelemetry viewer for local development

The problem

Every time I wanted to test OpenTelemetry instrumentation locally, I had to spin up Jaeger or a full collector stack. docker-compose up, wait, configure exporters, hope nothing clashes on port 4317... It felt like too much ceremony for a "does my trace look right?" check.

So I built otel-front: a single binary that receives OTLP data and shows it in a web UI. No Docker, no external database, no config files.

brew tap mesaglio/otel-front
brew install mesaglio/otel-front/otel-front
otel-front
# → opens http://localhost:8000
Enter fullscreen mode Exit fullscreen mode

Point your app at localhost:4318 (HTTP) or localhost:4317 (gRPC) and that's it.


What it does

Dashboard

  • Traces — waterfall view, flame graph, side-by-side trace comparison, search by operation or trace ID
  • Logs — full-text search, severity/service filters, correlation with traces via trace_id
  • Metrics — query builder, time series charts, aggregations (avg, sum, min, max, count)

Traces

Traces

Side-by-side comparison to spot regressions between two runs:

Traces comparation

Logs correlated by trace_id directly from a trace view:

Traces + Logs

Logs

Logs

Metrics

Metrics


How it's built

The interesting constraint was: single binary, zero external dependencies.

Go + embedded frontend

The backend is Go (Gin). The React frontend is built with Vite and then embedded directly into the binary using Go's embed.FS:

//go:embed static/*
var staticFiles embed.FS
Enter fullscreen mode Exit fullscreen mode

One go build, one binary that serves everything.

DuckDB as in-memory store

I needed SQL (for flexible filtering and aggregations) without shipping a database. DuckDB fits perfectly — it runs entirely in-process, zero setup, and handles analytical queries well.

Traces, spans, logs, and metrics all land in DuckDB tables with JSON columns for attributes:

CREATE TABLE spans (
    trace_id TEXT,
    span_id  TEXT,
    name     TEXT,
    start_time_unix_nano BIGINT,
    end_time_unix_nano   BIGINT,
    attributes           JSON,
    ...
)
Enter fullscreen mode Exit fullscreen mode

Instead of writing protobuf parsers from scratch, I reused the OpenTelemetry Collector's pdata library. It handles deserialization; I just transform the result into my internal models.


Quick start

Homebrew

brew tap mesaglio/otel-front
brew install otel-front
otel-front
Enter fullscreen mode Exit fullscreen mode

Docker

docker run -p 8000:8000 -p 4317:4317 -p 4318:4318 \
  ghcr.io/mesaglio/otel-front:latest
Enter fullscreen mode Exit fullscreen mode

Binary (macOS / Linux, x86_64 & ARM64)

https://github.com/mesaglio/otel-front/releases/latest

Configure your app:

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_LOGS_EXPORTER="otlp"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
Enter fullscreen mode Exit fullscreen mode

The UI includes a copy-paste helper for these env vars.


Source

GitHub: mesaglio/otel-front

PRs and feedback welcome. If you're working with OpenTelemetry locally and
find it useful (or broken), let me know in the comments.

Top comments (1)

Collapse
 
didiviking profile image
didiViking

Great article and thanks for your contribution. I totally tested this regarding OTel instrumentation and gave it a star. It was the most "plug and play" dev tool I tried about my project with OTel instrumentation and lightweight dev viewer of metrics, logs and traces. This has a good future!