"""Journalisation client Léa — DETTE-021. Branche un handler **fichier** (`TimedRotatingFileHandler`) sur le logger racine, en plus de la console. Sans cela, sous `pythonw.exe` (pas de console), les logs partent sur stderr et sont **perdus** — diagnostic terrain impossible. Rotation quotidienne + rétention `retention_days` (Règlement IA Art. 12 : journalisation automatique + conservation minimum 180 j). """ import logging from logging.handlers import TimedRotatingFileHandler from pathlib import Path _FMT = "%(asctime)s %(levelname)-7s %(name)-25s %(message)s" def setup_logging(log_file, level=logging.INFO, retention_days=180): """Configure le logging racine : fichier (rotation quotidienne, `retention_days` fichiers conservés) + console. **Idempotent** : ne réempile pas nos handlers. Args: log_file: chemin du fichier de log (`config.LOG_FILE` en prod). level: niveau racine (INFO par défaut ; DEBUG géré par l'appelant). retention_days: nb de fichiers quotidiens conservés (180 = Règlement IA Art. 12). Returns: Le `TimedRotatingFileHandler` créé. """ log_file = Path(log_file) log_file.parent.mkdir(parents=True, exist_ok=True) root = logging.getLogger() root.setLevel(level) # Idempotence : retirer nos propres handlers posés par un appel précédent. for h in list(root.handlers): if getattr(h, "_lea_managed", False): h.close() root.removeHandler(h) file_handler = TimedRotatingFileHandler( str(log_file), when="midnight", backupCount=retention_days, encoding="utf-8" ) file_handler.setFormatter(logging.Formatter(_FMT, datefmt="%Y-%m-%d %H:%M:%S")) file_handler.setLevel(level) file_handler._lea_managed = True root.addHandler(file_handler) # Console conservée (utile en dev / si lancé avec une console). console = logging.StreamHandler() console.setFormatter(logging.Formatter(_FMT, datefmt="%H:%M:%S")) console.setLevel(level) console._lea_managed = True root.addHandler(console) return file_handler