I built a lightweight OpenTelemetry viewer for local development

Published: (March 25, 2026 at 10:35 PM EDT)
3 min read
Source: Dev.to

Source: Dev.to

Juan Mesaglio

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

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 comparison

Logs correlated by trace_id directly from a trace view:

Traces + Logs

Logs

Logs

Metrics

Metrics

How it’s built

Constraint: 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

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 runs entirely in‑process, zero setup, and handles analytical queries well.

All spans, logs, and metrics land in DuckDB tables with JSON columns for attributes, e.g.:

CREATE TABLE spans (
    trace_id               TEXT,
    span_id                TEXT,
    name                   TEXT,
    start_time_unix_nano   BIGINT,
    end_time_unix_nano     BIGINT,
    attributes             JSON,
    ...
);

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

Docker

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

Binary (macOS / Linux, x86_64 & ARM64)

Download from the latest release.

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"

The UI includes a… (continue with the rest of your content).

# 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.

0 views
Back to Blog

Related posts

Read more »