Three correctness fixes (all IMPORTANT from Thufir's review) + one MINOR:
1. Per-run isolated collector output dir (mktemp -d): stale data from a
prior run can no longer false-green the OTLP trace assertions. The dir
is passed into compose via BUZZ_OTEL_OUTPUT_DIR env var (replaces the
hard-coded /tmp/buzz-otel-e2e-output bind-mount). Cleaned on success;
preserved on failure for inspection.
2. Delete test_otlp_metrics_tagged_service_name_buzz_relay: descoped per
Tyler/Max decision — relay drops OTLP metrics export entirely; metrics
go out the Prometheus :9102 scrape only; OTLP carries traces only.
3. Remove OTEL_SERVICE_NAME from enabled relay start so the harness
exercises the relay's Finding-A fallback exactly as staging does.
Replace blob.contains("buzz-relay") substring check with a structural
has_resource_attr_in_spans() assertion that walks ResourceSpans[].
resource.attributes[]{key, value.stringValue} — a span/scope name
containing "buzz-relay" can no longer satisfy this check.
4. README: note Rust tests are harness internals, not standalone; update
manual-run example to match new env var shape.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
OTEL E2E Harness
Local end-to-end validation for the Buzz relay's observability export surfaces
(PR #1398 — crates/buzz-relay OTEL migration).
What it proves
| Assertion | What's checked |
|---|---|
| (a) Prometheus scrape | GET :9102/metrics contains buzz_ws_connections_total, buzz_events_received_total, buzz_auth_attempts_total with non-zero values; does not contain target_info (suppressed via .without_target_info()) |
| (b) OTLP traces | The otel-collector received spans named ws.auth and ws.event carrying a conn_id attribute; service.name=buzz-relay verified structurally via resource.attributes key/value (not substring) |
| (d) OTLP-disabled control | With OTEL_EXPORTER_OTLP_ENDPOINT unset the relay still serves /metrics correctly; the collector receives nothing |
OTLP metrics export is out of scope — the relay emits metrics via the Prometheus :9102 scrape only; OTLP carries traces only.
Prerequisites
- Docker (for compose services: postgres, redis, minio, otel-collector)
- Rust toolchain (builds the relay + test binary)
psqlon PATH, or it falls back todocker execinto the postgres container- Ports available:
3000(relay WS),4317(OTLP gRPC),5432,6379,9000,9102
One-command run
just otel-e2e
Or directly:
./test/otel-e2e/run.sh
Pass --skip-build to reuse an existing target/ci/buzz-relay binary:
just otel-e2e --skip-build
To test against a relay built from the duncan/otel-migration branch (PR #1398):
# Build the OTEL relay binary first:
cd /path/to/buzz/.worktrees/duncan-otel-migration
cargo build --profile ci -p buzz-relay
# Run the harness pointing at that binary:
RELAY_BINARY=/path/to/buzz/.worktrees/duncan-otel-migration/target/ci/buzz-relay \
just otel-e2e --skip-build
Architecture
┌─────────────────────── HOST ──────────────────────────────────┐
│ │
│ buzz-relay (host process) │
│ • WS :3000 ← test driver connects here │
│ • health :8080 │
│ • Prometheus :9102 ← test scrapes here │
│ • OTLP gRPC → http://localhost:4317 (collector below) │
│ │
└───────────────────────────────────────────────────────────────┘
│ OTLP gRPC │ psql/redis/s3
▼ ▼
┌─────── DOCKER (buzz-net) ──────────────────────────────────────┐
│ │
│ otel-collector :4317 ←── relay pushes traces here │
│ exports to: debug stdout + /tmp/otelcol-output/ │
│ │
│ postgres :5432 redis :6379 minio :9000 │
│ │
└─────────────────────────────────────────────────────────────────┘
The relay runs on the host (matching the existing host.docker.internal
Prometheus pattern in docker-compose.yml). Only the backing services and the
collector run in Docker. This avoids a slow full-image rebuild on each test run.
Files
| File | Purpose |
|---|---|
run.sh |
Main driver: boots stack, starts relay twice (OTLP on/off), runs assertions |
compose.otel-e2e.yml |
Compose overlay — adds otel-collector to the existing dev stack |
otelcol-config.yml |
Collector config: OTLP gRPC receiver on :4317, debug + file exporter |
README.md |
This file |
The Rust assertions live in:
crates/buzz-test-client/tests/e2e_otel.rs
All four tests are #[ignore] by default and selected by run.sh.
Running individual tests manually
Note: The Rust tests in
crates/buzz-test-client/tests/e2e_otel.rsare harness internals. They depend on the two-relay sequence orchestrated byrun.sh(OTLP-enabled run first, then OTLP-disabled run), and the OTLP trace assertions read collector output written during the first relay's run. Running the tests in isolation will fail or produce misleading results unless you manually replicate the same sequence. Usejust otel-e2eas the canonical entry point.
For development/debugging, you can reproduce the harness sequence manually:
# Start the stack first (per-run output dir is required):
export BUZZ_OTEL_OUTPUT_DIR=$(mktemp -d)
docker compose -f docker-compose.yml -f test/otel-e2e/compose.otel-e2e.yml \
up -d postgres redis minio minio-init otel-collector
# Start relay with OTLP enabled (no OTEL_SERVICE_NAME — relay defaults it):
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz \
REDIS_URL=redis://localhost:6379 \
RELAY_URL=ws://localhost:3000 \
BUZZ_REQUIRE_AUTH_TOKEN=false \
./target/ci/buzz-relay &
# Run the Prometheus + OTLP trace assertions:
RELAY_URL=ws://localhost:3000 \
METRICS_URL=http://localhost:9102/metrics \
OTEL_COLLECTOR_OUTPUT=${BUZZ_OTEL_OUTPUT_DIR}/telemetry.json \
cargo test -p buzz-test-client --test e2e_otel \
test_prometheus_contains_buzz_metrics test_otlp_traces -- --ignored
# Teardown:
docker compose -f docker-compose.yml -f test/otel-e2e/compose.otel-e2e.yml down -v
Relationship to the relay source
This harness is read-only with respect to crates/buzz-relay/src/**. The
relay binary is built from the duncan/otel-migration branch (PR #1398) which
is gate-cleared and frozen. This harness lives on a separate branch
(duncan/otel-e2e-harness) and adds only:
test/otel-e2e/— this directorycrates/buzz-test-client/tests/e2e_otel.rs— the Rust assertionsJustfile—just otel-e2etarget