Verify exact production traces and record rollout
This commit is contained in:
+64
-1
@@ -1,13 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from time import perf_counter
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI, Response
|
||||
from fastapi import FastAPI, Request, Response
|
||||
|
||||
from adapters.postgres import PostgresTraceRepository
|
||||
from bootstrap import build_runtime
|
||||
from config import Settings, get_settings
|
||||
from rag.answer import GroundedAnswerService
|
||||
from rag.metrics import REQUEST_DURATION, REQUESTS, NullMetrics
|
||||
from rag.telemetry import (
|
||||
annotate_current_span,
|
||||
configure_telemetry,
|
||||
correlation_context,
|
||||
current_trace_id,
|
||||
request_span,
|
||||
)
|
||||
from routers.rag import router as rag_router
|
||||
|
||||
|
||||
@@ -20,16 +29,65 @@ def create_app(
|
||||
metrics: Any | None = None,
|
||||
) -> FastAPI:
|
||||
configured = settings or get_settings()
|
||||
effective_metrics = metrics or NullMetrics()
|
||||
configure_telemetry(configured, effective_metrics)
|
||||
app = FastAPI(title=configured.app_name, version="0.1.0")
|
||||
app.state.answer_service = answer_service
|
||||
app.state.conversational = conversational
|
||||
app.state.trace_writer = trace_writer
|
||||
app.state.metrics = metrics
|
||||
|
||||
@app.middleware("http")
|
||||
async def correlate_and_trace(request: Request, call_next):
|
||||
route = _route_label(request.url.path)
|
||||
method = request.method.upper()
|
||||
started = perf_counter()
|
||||
status_code = 500
|
||||
with correlation_context(request.headers.get("x-correlation-id")) as correlation_id:
|
||||
with request_span(method, route, request.headers) as span:
|
||||
try:
|
||||
response = await call_next(request)
|
||||
status_code = response.status_code
|
||||
response.headers["X-Correlation-ID"] = correlation_id
|
||||
trace_id = current_trace_id()
|
||||
if trace_id:
|
||||
response.headers["X-Trace-ID"] = trace_id
|
||||
return response
|
||||
finally:
|
||||
status = f"{status_code // 100}xx"
|
||||
elapsed = perf_counter() - started
|
||||
effective_metrics.increment(
|
||||
REQUESTS, method=method, route=route, status=status
|
||||
)
|
||||
effective_metrics.observe(
|
||||
REQUEST_DURATION,
|
||||
elapsed,
|
||||
method=method,
|
||||
route=route,
|
||||
status=status,
|
||||
)
|
||||
if span is not None:
|
||||
span.set_attribute("http.response.status_code", status_code)
|
||||
annotate_current_span(
|
||||
**{
|
||||
"duocthu.http.status_class": status,
|
||||
"duocthu.duration_ms": elapsed * 1000,
|
||||
}
|
||||
)
|
||||
|
||||
@app.get("/health")
|
||||
def health() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.get("/ready")
|
||||
def ready() -> Response:
|
||||
# Runtime construction already verifies the Qdrant corpus manifest and
|
||||
# fails startup on mismatch. PostgreSQL trace/history are intentionally
|
||||
# fail-open and therefore must not make readiness flap.
|
||||
if app.state.answer_service is None and configured.embedding_provider != "disabled":
|
||||
return Response(status_code=503)
|
||||
return Response(content='{"status":"ready"}', media_type="application/json")
|
||||
|
||||
@app.get("/metrics")
|
||||
def prometheus_metrics() -> Response:
|
||||
exporter = getattr(app.state, "metrics", None)
|
||||
@@ -44,6 +102,11 @@ def create_app(
|
||||
return app
|
||||
|
||||
|
||||
def _route_label(path: str) -> str:
|
||||
known = {"/health", "/ready", "/metrics", "/v1/rag/query", "/v1/rag/suggest"}
|
||||
return path if path in known else "other"
|
||||
|
||||
|
||||
_settings = get_settings()
|
||||
_answer_service, _conversational, _trace_writer, _metrics = build_runtime(_settings)
|
||||
app = create_app(
|
||||
|
||||
Reference in New Issue
Block a user