spaniel

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 28, 2026 License: MIT

README ΒΆ

spaniel

Local OpenTelemetry viewer. Postman for your traces.

Go OpenTelemetry


You run docker compose up. You hit an endpoint. It takes 800ms. You have no idea if it's Postgres, Redis, an N+1 query, or the downstream HTTP call. So you add print() statements. There has to be a better way.

spaniel is a single binary that receives OpenTelemetry traces, logs, and metrics from your local services and shows them in a beautiful UI β€” with automatic N+1 detection, a semantic convention linter, and session diffing so you can see exactly what your code change made better or worse.

No Docker required. No cloud account. Nothing leaves your machine.


Install

# macOS / Linux
brew install zfogg/tap/spaniel

# Go
go install github.com/zfogg/spaniel/cmd/spaniel@latest

# Docker
docker run -p 8080:8080 -p 4317:4317 -p 4318:4318 ghcr.io/zfogg/spaniel:latest

Quickstart

1. Start spaniel (browser opens automatically)

spaniel

2. Point your app at it

export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

3. Hit an endpoint. Traces appear live.

That's it. No config files, no API keys, no YAML pipelines.


Features

πŸ” Trace waterfall & flame graph

Click any trace to see a full waterfall view with parent-child span relationships, duration bars, and service color-coding. Toggle to flame graph mode to spot hot paths instantly. Click any span to inspect its attributes as a structured tree.

⚑ Automatic N+1 detection

spaniel fingerprints your DB spans and flags when the same query is called an excessive number of times within a single trace. It surfaces the offending parent span, the total wasted time, and the exact statement β€” without you having to count anything.

⚠ N+1 detected in GET /api/projects
  SELECT * FROM builds WHERE project_id = ? β€” called 47 times (220ms wasted)
  Likely origin: ProjectController.list [span_id: a3f2...]
🧹 Semantic convention linter

As spans arrive, spaniel validates them against the OpenTelemetry Semantic Conventions spec and flags violations in real time. Missing db.system on a database span? Wrong attribute name on an HTTP span? spaniel catches it before you ship.

12 spans with warnings this session:
  [error]  3 DB spans missing required attribute: db.system
  [warn]   8 spans with service.name = "unknown_service" β€” configure OTEL_SERVICE_NAME
  [warn]   1 span with zero duration β€” likely an instrumentation bug
πŸ”€ Session diff

Mark any point in time as a baseline. Make your code change. Run again. spaniel diffs the two sessions and shows exactly what changed: new spans, removed spans, duration deltas per operation, attribute changes.

Session diff: "before refactor" β†’ "after refactor"
  βœ“ GET /api/builds        βˆ’18% faster  (820ms β†’ 672ms)
  βœ“ SELECT builds          βˆ’52% fewer calls  (47 β†’ 3)
  β–³ POST /api/webhooks     +4% slower  (within noise)
spaniel session new "before refactor"
# ... make your change ...
spaniel session new "after refactor"
# open the diff view in the browser
πŸ—Ί Auto-generated service map

No config. spaniel builds a live dependency graph from your span data β€” which services are calling which, with call counts, average latency, and error rates on each edge.

πŸ“‹ Log correlation

Full log viewer with severity filtering and free-text search. If a log has a trace_id, click it to jump directly to that trace in the waterfall. From any span, see the logs emitted during its execution window.

πŸ“‘ OTLP proxy mode

Already sending traces to Grafana Tempo or Datadog? Run spaniel as a transparent proxy β€” it stores locally and forwards to your upstream simultaneously. Zero changes to your existing OTel pipeline.

spaniel --forward http://tempo:4318

How it works

spaniel is a single self-contained binary. No external services, no sidecar processes.

  • OTLP receiver β€” accepts traces/logs/metrics over gRPC (:4317) and HTTP (:4318)
  • DuckDB β€” stores everything locally in ~/.spaniel/spaniel.duckdb; persists across restarts
  • Ingestion pipeline β€” normalizes, lints, runs detectors, publishes live updates via WebSocket
  • Embedded React UI β€” served from the binary itself; opens in your browser at http://localhost:8080
your app  ──OTLP──►  spaniel :4317/:4318
                         β”‚
                      DuckDB  (~/.spaniel/)
                         β”‚
                    React UI  :8080

Data retention defaults to 7 days and 500MB. Configure with ~/.spaniel/config.yaml or flags.


CI integration

Run spaniel in GitHub Actions to catch regressions before they merge.

- name: Start spaniel
  run: spaniel serve --ci &

- name: Run integration tests
  run: pytest tests/integration/

- name: Check for regressions
  run: spaniel ci check --baseline ./spaniel-baseline.json --threshold 20

Commit spaniel-baseline.json to your repo. spaniel fails the build if p95 latency regresses more than 20%, new N+1s are introduced, or spans with ERROR status appear that weren't in the baseline.

Export a new baseline after intentional changes:

spaniel ci export --output ./spaniel-baseline.json

Configuration

# ~/.spaniel/config.yaml
port: 8080
db_path: ~/.spaniel/spaniel.duckdb
retention_days: 7
max_db_size_mb: 500
no_browser: false
forward:
  - url: http://tempo:4318

Or use a per-project .spaniel.yaml in your repo root. CLI flags always take precedence.


CLI reference

spaniel                          start the server (opens browser)
spaniel session new [label]      create and activate a new session
spaniel session list             list all sessions
spaniel session baseline [id]    mark a session as the diff baseline
spaniel import <file>            import a trace from OTLP/Jaeger JSON as a baseline
spaniel ci check                 compare current session against baseline (for CI)
spaniel ci export                export current session as a baseline JSON file
spaniel prune                    delete sessions older than retention period
spaniel reset                    wipe all data
spaniel config                   show current effective config

Docker Compose

Drop spaniel into your existing docker-compose.yml:

services:
  spaniel:
    image: ghcr.io/zfogg/spaniel:latest
    ports:
      - "8080:8080"
      - "4317:4317"
      - "4318:4318"
    volumes:
      - spaniel-data:/data
    environment:
      - SPANIEL_DB_PATH=/data/spaniel.duckdb

  your-api:
    # ... your existing service ...
    environment:
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://spaniel:4318
      - OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
      - OTEL_SERVICE_NAME=your-api

volumes:
  spaniel-data:

One engineer adds this. The whole team gets local observability. No individual setup required.


Why not Grafana / Jaeger / Datadog?

spaniel Grafana LGTM Jaeger Datadog
Single binary βœ“ βœ— (4+ containers) βœ— βœ—
Zero config βœ“ βœ— βœ— βœ—
N+1 detection βœ“ βœ— βœ— paid
Semconv linter βœ“ βœ— βœ— βœ—
Session diff βœ“ βœ— βœ— βœ—
Local only / private βœ“ βœ“ βœ“ βœ—
Cost free free free expensive

spaniel is specifically built for the local development loop, not production monitoring. Use it on your laptop. Use Grafana or Datadog in prod.


Roadmap

  • OTLP receiver (gRPC + HTTP)
  • DuckDB storage
  • Trace waterfall + flame graph
  • N+1 query detection
  • Semantic convention linter
  • Session diff
  • Service map
  • Log correlation
  • OTLP proxy mode
  • Production baseline import (Tempo, Jaeger export)
  • Instrumentation coverage heatmap
  • CI regression detection (spaniel ci)
  • Cloud baseline sync (team feature)

Contributing

git clone https://github.com/zfogg/spaniel
cd spaniel
make dev      # starts Go backend + Vite dev server with hot reload
make build    # builds production binary with embedded frontend
make test     # runs Go tests

Issues and PRs welcome.


License

MIT Β© Zachary Fogg

Directories ΒΆ

Path Synopsis
cmd
spaniel command
internal
api
ci
Package ci builds and compares baseline snapshots for the `spaniel ci` subcommand.
Package ci builds and compares baseline snapshots for the `spaniel ci` subcommand.
coverage
Package coverage computes "what fraction of an API surface has any instrumentation?" β€” see issue #18.
Package coverage computes "what fraction of an API surface has any instrumentation?" β€” see issue #18.
tui
Package tui is the shared foundation for Spaniel's terminal UIs (Bubble Tea + Lipgloss).
Package tui is the shared foundation for Spaniel's terminal UIs (Bubble Tea + Lipgloss).
ws

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL