# ============================================ # Projet : API-KA # Fichier : scripts/backfill_api_requests.py # Node : m3u96b # Author : Simon-Pierre Boucher # Contact : contact@spboucher.ai # Date : 2026-08-17 # ============================================ """Rattrapage : importe l'historique des requêtes HTTP déjà journalisées dans ``logs/apika.log*`` (lignes JSON ``http_request`` du middleware) vers la table ``api_requests`` qui alimente la page /stats. Idempotent : seules les lignes STRICTEMENT antérieures au plus ancien enregistrement déjà présent dans ``api_requests`` sont insérées (à la première exécution, tout l'historique des logs est repris ; aux suivantes, rien). Usage : venv/bin/python -m scripts.backfill_api_requests """ from __future__ import annotations import datetime import json from sqlalchemy import func, select from src.api.reqstats import normalize_endpoint from src.config import get_settings from src.database.db import init_db, session_scope from src.database.models import ApiRequest from src.utils.logger import get_logger def main() -> None: logger = get_logger("apika.backfill_requests") init_db() settings = get_settings() with session_scope() as session: cutoff = session.execute(select(func.min(ApiRequest.ts))).scalar() if cutoff is not None and cutoff.tzinfo is None: cutoff = cutoff.replace(tzinfo=datetime.UTC) if cutoff is None: cutoff = datetime.datetime.now(tz=datetime.UTC) entries: list[dict] = [] parsed = skipped = 0 for log_file in sorted(settings.logs_dir.glob("apika.log*")): with open(log_file, encoding="utf-8") as fh: for line in fh: line = line.strip() if not line or '"http_request"' not in line: continue try: rec = json.loads(line) except json.JSONDecodeError: continue if rec.get("message") != "http_request": continue parsed += 1 try: ts = datetime.datetime.fromisoformat(rec["timestamp"]) except (KeyError, ValueError): skipped += 1 continue if ts.tzinfo is None: ts = ts.replace(tzinfo=datetime.UTC) if ts >= cutoff: skipped += 1 continue entries.append( { "ts": ts, "method": rec.get("method", "GET"), "endpoint": normalize_endpoint(rec.get("path", "(autre)")), "status": int(rec.get("status_code", 0)), "duration_ms": round(float(rec.get("duration_ms", 0.0)), 2), } ) if entries: with session_scope() as session: session.bulk_insert_mappings(ApiRequest.__mapper__, entries) logger.info( "Backfill api_requests terminé", extra={"inserted": len(entries), "parsed": parsed, "skipped": skipped}, ) print(f"Backfill : {len(entries)} requêtes insérées ({parsed} lues, {skipped} ignorées)") if __name__ == "__main__": main()