feat: add detailed request logging to /api/score and global 422 handler

- Log incoming request (client, content-type, metrics, has_gt) on each /api/score call
- Log scoring result (latency, skipped metrics, scores) on success
- Register global RequestValidationError handler: logs url/content-type/errors
  so 422 causes are visible in server log without checking HTTP response body
- Fix jsonable_encoder for exc.errors() to handle non-serializable ctx objects

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-06-22 18:14:01 +08:00
parent ebf1fc7be8
commit 5ced129ff7
2 changed files with 42 additions and 3 deletions

View File

@@ -7,15 +7,19 @@ the server starts even when the evaluation dependencies are not yet installed.
from __future__ import annotations
import logging
from pathlib import Path
from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi import FastAPI, Request
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from webapp.api import evaluations, llm_profiles, pipeline, runs, scenarios, score
STATIC_DIR = Path(__file__).resolve().parent / "static"
logger = logging.getLogger("webapp.server")
# OpenAPI tag metadata — controls the grouping and descriptions in /docs.
OPENAPI_TAGS = [
@@ -103,6 +107,21 @@ def create_app() -> FastAPI:
app.include_router(pipeline.router)
app.include_router(score.router)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
"""Log full validation error detail to help diagnose 422 responses."""
errors = jsonable_encoder(exc.errors())
logger.warning(
"[422] validation error url=%s content_type=%s errors=%s",
request.url.path,
request.headers.get("content-type", ""),
errors,
)
return JSONResponse(
status_code=422,
content={"detail": errors},
)
@app.get("/api/health", tags=["meta"])
def health() -> dict[str, str]:
"""Report basic liveness so the UI can confirm the server is reachable."""