spaniel

module
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Jun 7, 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.

Even faster: spaniel run

Don't want to export env vars or start the server separately? Wrap any OpenTelemetry-instrumented command β€” spaniel boots itself if it isn't already running, injects the OTLP endpoint, runs your process in a fresh session, and prints a trace summary when it exits:

spaniel run -- pytest tests/integration/
spaniel run -- npm test
spaniel run -- ./my-server

Use spaniel record -- <cmd> to also mark the run as a baseline and pop open the diff view when it finishes.


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.

πŸ“ˆ Metrics with trace exemplars

spaniel ingests OTLP metrics too β€” gauges, counters, and histograms β€” and charts them with p50/p95/p99 percentile bands. When a metric point carries an exemplar, click it to jump straight to the trace that produced the outlier.

🎯 Instrumentation coverage

See which of your HTTP routes have ever been traced. Point spaniel at an OpenAPI/proto spec with --routes-file and it computes a coverage percentage and lists the dark routes β€” endpoints in your spec that no trace has ever exercised.

⌨️ Works in the terminal too

Not everything needs a browser. spaniel ships a full set of TUI commands for the keyboard-first:

spaniel tui              # all-in-one dashboard: spans + logs + issues, live
spaniel watch            # live span/issue ticker (table on a TTY, line-stream in a pipe)
spaniel logs tail        # follow logs with severity/service/trace filters
spaniel trace <id>       # open an interactive waterfall for one trace

On a pipe these stream plain lines, so they compose with grep, jq, and friends.

πŸ“‘ 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
πŸ€– MCP server for AI agents

Spaniel speaks MCP over a streamable-HTTP endpoint at /mcp, so a coding agent (Claude Code / Desktop) can read the traces it just generated β€” find the slow span, see the detected N+1, diff against a baseline β€” and fix the code, no screenshots required. Includes a read-only query_sql escape hatch for ad-hoc DuckDB queries. See MCP server.


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 500 MB; old sessions are pruned automatically. Configure with ~/.spaniel/config.yaml or flags.

A few things for the paranoid and the high-volume:

  • Local-only by default, but you can require a bearer token (--bearer-token, or SPANIEL_BEARER_TOKEN) and serve over TLS (--tls-cert / --tls-key) if you expose it on a network.
  • Back-pressure built in β€” optional per-source rate limiting (--source-rps) and sampling (--sample-rate) keep a chatty service from filling your disk, while always keeping errors, N+1s, and slow traces.

MCP server

Spaniel serves a Model Context Protocol endpoint at http://localhost:8080/mcp (streamable HTTP, enabled by default). It lets an AI agent query everything Spaniel knows about your run.

Add it to Claude Code:

claude mcp add --transport http spaniel http://localhost:8080/mcp

Or commit a project-scoped .mcp.json to your repo so collaborators get it automatically:

{
  "mcpServers": {
    "spaniel": {
      "type": "http",
      "url": "http://localhost:8080/mcp"
    }
  }
}

For Claude Desktop (or any client without native HTTP MCP support), bridge with mcp-remote:

{
  "mcpServers": {
    "spaniel": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:8080/mcp"]
    }
  }
}

The Settings page (βš™οΈ β†’ MCP) shows the endpoint and has copy buttons for both the command and the .mcp.json above.

Tools

All tools are read-only unless you start spaniel with --mcp-allow-writes.

Tool What it does
get_server_info version + how much data is stored
list_traces / get_trace recent traces; full waterfall + issues + correlated logs + lint for one trace
get_span / list_slow_spans one span's detail; the slowest spans in a session
list_issues / list_lint_warnings detected N+1/error-chain/etc.; semconv violations
query_logs / search logs by trace/span/service/severity; full-text + lint: search
get_service_map / list_services / get_stats dependency graph; services; counts
get_metrics / get_metric_series metric catalog and time series
list_sessions / diff_sessions sessions; before/after comparison
query_sql read-only raw SQL over the DuckDB tables (engine-enforced)
create_session / activate_session / set_baseline / prune_data write β€” only registered with --mcp-allow-writes

It also exposes resources (spaniel://schema, spaniel://guide) and prompts (debug_latest_trace, diff_against_baseline, find_bottlenecks).

Config: mcp_enabled (default true), mcp_allow_writes (default false), or the --mcp-enabled / --mcp-allow-writes flags.


CI integration

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

- name: Start spaniel
  run: spaniel --no-browser &

- name: Run integration tests
  run: pytest tests/integration/   # send OTLP to http://localhost:4318

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

Commit spaniel-baseline.json to your repo. spaniel ci check exits non-zero (failing the build) when p95 / root-duration regresses past --threshold percent, or when a fingerprint repeats more than --n1-threshold extra times β€” i.e. a new or worse N+1.

Generate the baseline once from a known-good run and commit it; regenerate it after intentional changes:

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

Tip: spaniel run -- <your test command> does the boot-server + fresh-session dance in one step, which is often cleaner than backgrounding the server yourself.


Configuration

# ~/.spaniel/config.yaml
port: 8080
db_path: ~/.spaniel/spaniel.duckdb
retention_days: 7
max_sessions: 50
max_db_size_mb: 500
no_browser: false
mcp_enabled: true        # serve the MCP endpoint at /mcp
mcp_allow_writes: false  # let MCP clients mutate state (sessions, prune)
forward:
  - http://tempo:4318
  - http://otelcollector:4318

Settings resolve highest priority first:

  1. CLI flags (--port, --forward, …)
  2. SPANIEL_* environment variables (e.g. SPANIEL_PORT, SPANIEL_DB_PATH, SPANIEL_BEARER_TOKEN)
  3. Project .spaniel.yaml in your repo root β€” commit per-project defaults
  4. Global ~/.spaniel/config.yaml
  5. Built-in defaults

spaniel config prints the effective, fully-resolved config; spaniel config path shows which file is active; spaniel config set <key> <value> edits the global file.


CLI reference

Server

spaniel                              start the server + UI (opens browser)
spaniel --no-browser                 ... without opening a browser (CI/servers)
spaniel --forward <url>              also forward OTLP upstream (repeatable)
spaniel --mcp-allow-writes           let MCP clients mutate state (off by default)

Sessions

spaniel session new [label]          create and activate a new session
spaniel session list                 interactive picker (activate / baseline / delete)
spaniel session activate <id|label>  switch the active session
spaniel session baseline [id|label]  toggle a session's diff-baseline flag
spaniel session delete <id|label>    delete a session
spaniel import <name> <file>         import OTLP/Jaeger JSON as a baseline session
                                     (use '-' to read from stdin)

Diff & CI

spaniel diff -b <a> -c <b>           diff two sessions (TUI, or --json for piping)
spaniel ci export -o <file>          export the active session as a baseline JSON
spaniel ci check -b <file>           compare active session vs baseline; exit 1 on regression

Run instrumented commands

spaniel run -- <cmd> [args...]       run a command wired to spaniel, print a trace summary
spaniel record -- <cmd> [args...]    same, but save as a baseline and open the diff view

Terminal viewers

spaniel tui                          all-in-one live dashboard
spaniel watch                        live span/issue ticker
spaniel logs tail                    follow logs (--service / --trace / --severity filters)
spaniel trace <id|short-id>          interactive waterfall for one trace

Maintenance

spaniel config [show|path|set]       show / locate / edit configuration
spaniel doctor                       diagnose ports, DB, config, embed, upstreams
spaniel prune                        apply the retention policy now
spaniel compact                      CHECKPOINT + VACUUM the database
spaniel reset --yes                  wipe all data and start fresh

Run spaniel <command> --help for the full flag list on any command.


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) β€” traces, logs, and metrics
  • DuckDB storage with automatic retention
  • Trace waterfall + flame graph
  • N+1 query detection
  • Semantic convention linter
  • Session diff
  • Service map
  • Log correlation
  • Metrics with trace exemplars
  • OTLP proxy mode
  • Baseline import from OTLP / Jaeger JSON (spaniel import)
  • Instrumentation coverage (--routes-file)
  • CI regression detection (spaniel ci)
  • Terminal UI (spaniel tui / watch / logs tail)
  • Live import from a running Tempo / Jaeger backend
  • More detectors (connection-pool exhaustion, retry storms)
  • Cloud baseline sync (team feature)

Contributing

git clone https://github.com/zfogg/spaniel
cd spaniel
make setup    # point git at the repo's pre-commit hooks (one-time)
make dev      # Go backend + Vite dev server with hot reload
make build    # production binary with the frontend embedded via go:embed
make test     # Go test suite

Frontend tests live in frontend/:

cd frontend
pnpm test     # vitest unit tests
pnpm e2e      # Playwright end-to-end 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.
diff
Package diff computes a structural/timing comparison between two sessions' spans.
Package diff computes a structural/timing comparison between two sessions' spans.
goroutine
Package goroutine provides a panic-safe goroutine launcher.
Package goroutine provides a panic-safe goroutine launcher.
logger
Package logger provides a structured slog-backed logger for spaniel.
Package logger provides a structured slog-backed logger for spaniel.
mcp
Package mcp exposes Spaniel's observability data to MCP (Model Context Protocol) clients such as Claude Code and Claude Desktop.
Package mcp exposes Spaniel's observability data to MCP (Model Context Protocol) clients such as Claude Code and Claude Desktop.
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