feat: add detailed logging to all API routes and global access log middleware

Each API module now logs:
- evaluations: trigger (scenario path, task_id), status polls, list
- runs: list (count), detail (run_id, metrics, sample counts)
- scenarios: list (total, valid, error counts)
- pipeline: submit (docs_path, models, max_docs), status polls, list
- llm_profiles: CRUD ops (name, model, id), probe/test (model, ok, latency), apply (patched fields)
- score: already had per-request logging

Global middleware (webapp.access logger):
- Every API request: METHOD path -> status (latency_ms) at INFO
- Static file requests demoted to DEBUG to reduce noise

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-06-23 10:35:00 +08:00
parent 1304fec1c4
commit ac410e7a5d
6 changed files with 213 additions and 5 deletions

View File

@@ -8,6 +8,7 @@ the server starts even when the evaluation dependencies are not yet installed.
from __future__ import annotations
import logging
import time
from pathlib import Path
from fastapi import FastAPI, Request
@@ -20,6 +21,7 @@ from webapp.api import evaluations, llm_profiles, pipeline, runs, scenarios, sco
STATIC_DIR = Path(__file__).resolve().parent / "static"
logger = logging.getLogger("webapp.server")
access_logger = logging.getLogger("webapp.access")
# OpenAPI tag metadata — controls the grouping and descriptions in /docs.
OPENAPI_TAGS = [
@@ -107,6 +109,24 @@ def create_app() -> FastAPI:
app.include_router(pipeline.router)
app.include_router(score.router)
@app.middleware("http")
async def access_log_middleware(request: Request, call_next):
"""Log every API request with method, path, status code and latency.
Static file requests are logged at DEBUG level to keep the console clean.
"""
t0 = time.monotonic()
response = await call_next(request)
latency_ms = int((time.monotonic() - t0) * 1000)
path = request.url.path
is_static = path.startswith("/static/") or path in ("/", "/favicon.ico")
msg = "%s %s%d (%dms)", request.method, path, response.status_code, latency_ms
if is_static:
access_logger.debug(*msg)
else:
access_logger.info(*msg)
return response
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
"""Log full validation error detail to help diagnose 422 responses."""