import json import threading import time from datetime import datetime from pathlib import Path from fastapi import FastAPI from pydantic import BaseModel, Field class SearchRequest(BaseModel): keywords: list[str] = Field(min_length=1, max_length=100) class JsonlQueryLogger: def __init__(self, path: str | Path): self.path = Path(path) self.path.parent.mkdir(parents=True, exist_ok=True) self._lock = threading.Lock() def write(self, query: str, candidates: list[dict], elapsed_ms: float) -> None: entry = { "time": datetime.now().astimezone().isoformat(timespec="milliseconds"), "query": query, "candidates": candidates, "elapsed_ms": round(elapsed_ms, 3), } line = json.dumps(entry, ensure_ascii=False, separators=(",", ":")) + "\n" with self._lock: with self.path.open("a", encoding="utf-8", newline="") as file: file.write(line) def register_routes(app: FastAPI, matcher, query_logger): @app.post("/v1/search") def search(request: SearchRequest) -> dict[str, list[dict]]: results = [] for keyword in request.keywords: started_at = time.perf_counter() candidates = matcher.search(keyword, limit=3) elapsed_ms = (time.perf_counter() - started_at) * 1000 query_logger.write(keyword, candidates, elapsed_ms) results.append({"query": keyword, "candidates": candidates}) return {"results": results}