P1.1 — Implement real ai_router.chat_completion / stream_chat_completion using Ollama Cloud primary + OpenRouter fallback. Fixes AttributeError on /api/v1/chat and /api/v1/wallet-clusters. P1.2 — Fix broken RAG call paths: telegram bot now hits /api/v1/rag/v2/search then ai_router.chat_completion; unified_wallet_scanner._rag_enrich uses async httpx + correct path. Mount app.domains.databus.router. P1.3 — Implement setup_otel / shutdown_otel in core/tracing.py and create core/langfuse.py with init_langfuse / flush_langfuse as safe no-ops when SDK is missing or env vars unset. P1.4 — Fix Prometheus alert rule metric name drift: rmi_requests_total -> rmi_http_requests_total (matches metrics.py). P1.5 — Move dead admin_backend.py (1691 LOC) to app/domains/admin/router.py and mount it. Admin login endpoint now reachable. P2.1 — Rename app/domains/auth/__init__.py (507 LOC router file that broke mypy) -> app/domains/auth/router.py. Mount app.domains.auth.router so /register, /login, /wallet/*, /user/me, /2fa/*, OAuth, Telegram are live. P2.1b — Add pyotp + qrcode to requirements.txt so 2FA endpoints don't 503.
86 lines
2.9 KiB
Python
86 lines
2.9 KiB
Python
"""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"
|
|
OTEL_ENDPOINT = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "")
|
|
|
|
|
|
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 setup_otel() -> bool:
|
|
"""Initialize OpenTelemetry tracing. Returns True on success.
|
|
|
|
No-op if OTEL_ENABLED != "true". If OTEL_ENABLED is true but the SDK
|
|
is not installed, logs and returns False.
|
|
"""
|
|
if not TRACING_ENABLED:
|
|
return False
|
|
try:
|
|
# Lazy import — opentelemetry is an optional dependency.
|
|
from opentelemetry import trace # noqa: F401
|
|
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter # noqa: F401
|
|
from opentelemetry.sdk.resources import Resource # noqa: F401
|
|
from opentelemetry.sdk.trace import TracerProvider # noqa: F401
|
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor # noqa: F401
|
|
|
|
resource = Resource.create({"service.name": "rmi-backend"})
|
|
provider = TracerProvider(resource=resource)
|
|
if OTEL_ENDPOINT:
|
|
exporter = OTLPSpanExporter(endpoint=OTEL_ENDPOINT)
|
|
provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
trace.set_tracer_provider(provider)
|
|
return True
|
|
except ImportError:
|
|
return False
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def shutdown_otel() -> None:
|
|
"""Flush and shut down OpenTelemetry tracer provider."""
|
|
try:
|
|
from opentelemetry import trace
|
|
|
|
provider = trace.get_tracer_provider()
|
|
if hasattr(provider, "shutdown"):
|
|
provider.shutdown()
|
|
except ImportError:
|
|
pass
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
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
|