Files
rpa_vision_v3/agent_v0/agent_v1/logging_setup.py
Dom 29cb466595 fix(lea): journalisation client vers fichier (DETTE-021)
setup_logging() branche un TimedRotatingFileHandler vers LOG_FILE (rotation
quotidienne + rétention 180j, Règlement IA Art.12) + console. Sous pythonw
(sans console), basicConfig->stderr était perdu => diagnostic terrain aveugle.
main.py appelle setup_logging au démarrage, avec fallback console si le fichier
est indisponible (ne jamais empêcher Léa de démarrer).

TDD: tests/unit/test_agent_v1_logging.py (3 tests RED->GREEN ; module chargé par
chemin pour éviter les imports lourds DETTE-011/013). py_compile main.py OK.

refs DETTE-021

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:44:31 +02:00

57 lines
2.1 KiB
Python

"""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