"""Configuration des logs backend : lisible, structure, sans donnee sensible.""" from __future__ import annotations import logging import re import sys _LOG_FORMAT = "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s" _DATE_FORMAT = "%Y-%m-%dT%H:%M:%S" # Champs qui ne doivent jamais apparaitre dans les logs. REDACTED_KEYS = frozenset({"management_token", "managementToken", "authorization"}) # Un share token est une capacite de lecture : le laisser dans les logs d'acces # revient a y ecrire le lien complet. On le remplace, sans toucher au reste du # chemin pour que les autres routes restent diagnosticables. _TOKEN_PATHS = re.compile(r"(?P/(?:s|api/shares)/)(?P[A-Za-z0-9_-]{6,})") REDACTED_TOKEN = "[REDACTED]" def redact_tokens(text: str) -> str: return _TOKEN_PATHS.sub(lambda m: f"{m.group('prefix')}{REDACTED_TOKEN}", text) class TokenRedactingFilter(logging.Filter): """Retire les jetons de partage des messages avant ecriture. Applique aux logs d'acces uvicorn, ou le chemin complet est journalise. """ def filter(self, record: logging.LogRecord) -> bool: if isinstance(record.args, tuple): record.args = tuple( redact_tokens(arg) if isinstance(arg, str) else arg for arg in record.args ) if isinstance(record.msg, str): record.msg = redact_tokens(record.msg) return True def configure_logging(level: str = "INFO") -> None: handler = logging.StreamHandler(sys.stdout) handler.setFormatter(logging.Formatter(_LOG_FORMAT, datefmt=_DATE_FORMAT)) root = logging.getLogger() root.handlers.clear() root.addHandler(handler) root.setLevel(level.upper()) redactor = TokenRedactingFilter() handler.addFilter(redactor) # Uvicorn possede ses propres handlers : on les aligne sur les notres. for name in ("uvicorn", "uvicorn.error", "uvicorn.access"): logger = logging.getLogger(name) logger.handlers.clear() logger.propagate = True logger.filters.clear() logger.addFilter(redactor) def safe_extra(data: dict[str, object]) -> dict[str, object]: """Retire les valeurs sensibles avant de les envoyer dans un log.""" return {k: ("***" if k in REDACTED_KEYS else v) for k, v in data.items()}