---
id: PRD-0017
title: "M13.2 — OTLP logs export (trace-log correlation in Grafana)"
status: done
owner: "Aaron Roney"
created: 2026-07-04
updated: 2026-07-04

depends_on:
- PRD-0014

principles:
- "One gate, both signals: `CONCLAVE_OTLP_ENDPOINT` already decides whether telemetry leaves the process — logs ride the same switch, no second env surface."
- "Telemetry must never feed itself: the exporter's own internals (opentelemetry, reqwest, hyper) are excluded from the log bridge, or every export POST births another log record."
- "Logs without trace IDs are just a second copy of stderr — the bridge must attach the active span context so Grafana can pivot log to trace and back."
- "Kinds-only privacy (PRD-0014) is inherited: events already carry frame kinds, paths, and channels — never payload bodies — so exporting them adds no new exposure."

acceptance_tests:
- id: uat-001
  name: "With CONCLAVE_OTLP_ENDPOINT set, serve POSTs log records to <base>/v1/logs with the OTEL_EXPORTER_OTLP_HEADERS auth header attached"
  command: cargo nextest run -E 'test(/otlp_logs/)'
  uat_status: verified
- id: uat-002
  name: "Full gate is green"
  command: cargo make ci
  uat_status: verified

tasks:
- id: T-001
  title: "Export tracing events as OTLP logs alongside spans (serve-only, same env gate)"
  priority: 1
  status: done
  notes: "opentelemetry-appender-tracing bridge layer + SdkLoggerProvider with a batch OTLP/HTTP LogExporter to {base}/v1/logs. Per-layer filter excludes opentelemetry/reqwest/hyper/h2 targets (feedback-loop guard). Enable the bridge's tracing-opentelemetry span-context integration so records carry trace_id/span_id. main() flushes both providers on shutdown."
- id: T-002
  title: "Docs: DESIGN observability section + fly.toml guidance mention logs"
  priority: 2
  status: done
  notes: "Same secrets, one more signal path; note that RUST_LOG gates what exports for logs exactly as for spans."
---

# Summary

Traces already export to Grafana Cloud (PRD-0014, fixed for TLS in v0.3.3); logs still live only
in `fly logs`. This milestone bridges `tracing` events into the same OTLP pipeline — one more
layer in `init_telemetry`, POSTing to `/v1/logs` under the existing `CONCLAVE_OTLP_ENDPOINT`
gate — so Grafana holds both signals and can correlate a log line to its owning trace.

# Non-Goals

- Metrics export (Grafana derives span metrics from traces already).
- Exporting from the bridge/CLI verbs — serve-only, like spans.
- Replacing the stderr fmt layer — `fly logs` keeps working unchanged.

# History

- 2026-07-04: Drafted and started — the "item 2" pick from the post-v0.3.3 roadmap.

- 2026-07-04: Both tasks landed; gate green (204 tests). The pre-existing spans e2e needed the
  same multi-request fake collector as the new logs test — both signals now POST to one base URL,
  so arrival order is nondeterministic (helper extracted, DRY).
