Summary
Observability is fragmented: Prometheus counters exist (src/metrics/), tracing is initialized, there is a telemetry/ module and a monitoring/ directory — but there is no distributed tracing export, no trace context propagation, and no correlation between traces, logs, and metrics. When a tip request is slow, nothing connects the HTTP span to the SQL queries (db/slow_query_logger.rs logs in isolation), the Redis calls, the outbound webhook, and the Horizon verification that composed it.
Task
- Wire OpenTelemetry end-to-end:
tracing-opentelemetry + OTLP exporter (endpoint/protocol from typed config, gracefully disabled when unset), resource attributes (service name/version/instance), and parent-based + ratio tail-aware sampling configuration.
- Context propagation: extract/inject W3C
traceparent on inbound requests and all outbound calls (reqwest to Horizon, webhook deliveries, queue jobs — spans must link across the async job boundary via span links).
- Instrument the critical path with meaningful spans and attributes: request span (route template, status, principal id — never PII/secrets in attributes), SQLx query spans (statement summary, rows), Redis command spans, webhook delivery spans, job execution spans.
- Log correlation: every structured log line carries
trace_id/span_id; slow-query logs join the active span instead of logging standalone.
- Metrics with exemplars: RED metrics (rate/errors/duration histogram) per route template, exemplars linking histogram buckets to trace IDs where the Prometheus setup allows.
- Ship the local stack: extend
compose.yml with an OTLP collector + Jaeger (or Grafana Tempo) profile, plus a provisioned Grafana dashboard JSON in monitoring/ covering RED per route, DB latency, webhook success rate, and queue lag.
- Overhead guard: benchmark request throughput with tracing enabled vs disabled (
benches/), demonstrate <5% overhead at the default sampling rate.
- Tests: propagation round-trip (inbound
traceparent → outbound request carries child context), job-boundary span linkage, and log/trace ID correlation.
Acceptance criteria
PR requirements (mandatory)
- ✅ Your PR must pass all checks — PRs with failing or skipped checks will not be merged.
- 📸 You must attach a screenshot in the PR description demonstrating the result (for this issue: the end-to-end trace waterfall in Jaeger/Tempo and the Grafana dashboard).
- 🔗 You must link this issue number in your PR description (e.g.
Closes #<issue-number>). PRs without a linked issue will not be reviewed.
Summary
Observability is fragmented: Prometheus counters exist (
src/metrics/),tracingis initialized, there is atelemetry/module and amonitoring/directory — but there is no distributed tracing export, no trace context propagation, and no correlation between traces, logs, and metrics. When a tip request is slow, nothing connects the HTTP span to the SQL queries (db/slow_query_logger.rslogs in isolation), the Redis calls, the outbound webhook, and the Horizon verification that composed it.Task
tracing-opentelemetry+ OTLP exporter (endpoint/protocol from typed config, gracefully disabled when unset), resource attributes (service name/version/instance), and parent-based + ratio tail-aware sampling configuration.traceparenton inbound requests and all outbound calls (reqwest to Horizon, webhook deliveries, queue jobs — spans must link across the async job boundary via span links).trace_id/span_id; slow-query logs join the active span instead of logging standalone.compose.ymlwith an OTLP collector + Jaeger (or Grafana Tempo) profile, plus a provisioned Grafana dashboard JSON inmonitoring/covering RED per route, DB latency, webhook success rate, and queue lag.benches/), demonstrate <5% overhead at the default sampling rate.traceparent→ outbound request carries child context), job-boundary span linkage, and log/trace ID correlation.Acceptance criteria
/metricsPR requirements (mandatory)
Closes #<issue-number>). PRs without a linked issue will not be reviewed.