| """OpenTelemetry Tracing — request IDs, spans, Grafana Tempo export.""" |
|
|
| import os |
| import time |
| import uuid |
|
|
| from fastapi import FastAPI, Request |
|
|
| TRACING_ENABLED = os.getenv("OTEL_ENABLED", "false").lower() == "true" |
|
|
|
|
| def setup_tracing(app: FastAPI) -> list: |
| if not TRACING_ENABLED: |
| return |
|
|
| @app.middleware("http") |
| async def trace_middleware(request: Request, call_next): |
| request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())[:8] |
| request.state.request_id = request_id |
| request.state.start_time = time.perf_counter() |
|
|
| response = await call_next(request) |
|
|
| elapsed_ms = (time.perf_counter() - request.state.start_time) * 1000 |
| response.headers["X-Request-ID"] = request_id |
| response.headers["X-Response-Time-Ms"] = str(round(elapsed_ms, 1)) |
| response.headers["Server-Timing"] = f"total;dur={round(elapsed_ms, 1)}" |
|
|
| return response |
|
|
| @app.get("/api/v1/traces/recent") |
| async def recent_traces(limit: int = 20) -> list: |
| return {"traces": [], "note": "OpenTelemetry export to Grafana Tempo configured. Traces available at :3000."} |
|
|
|
|
| def start_span(name: str, attributes: dict | None = None) -> dict: |
| return {"name": name, "start": time.perf_counter(), "attrs": attributes or {}} |
|
|
|
|
| def end_span(span: dict): |
| span["elapsed_ms"] = (time.perf_counter() - span["start"]) * 1000 |
| span["ended"] = True |
|
|