"""Configuration de la journalisation avec masquage automatique des secrets. Ce module fournit un formateur de logs qui rédige les secrets (tokens, mots de passe, URLs sensibles) ainsi que l'initialisation du logging global du pipeline ``pronote-sync``. """ from __future__ import annotations import logging import sys from pronote_sync.utils.redaction import redact_secrets _LOG_FORMAT = "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s" _DATE_FORMAT = "%Y-%m-%d %H:%M:%S" class RedactingFormatter(logging.Formatter): """Formateur de logs qui masque les secrets des messages et des arguments.""" def format(self, record: logging.LogRecord) -> str: """Formate un enregistrement de log en masquant les secrets. La rédaction est appliquée à la chaîne finale (message, arguments et traceback inclus) produite par :class:`logging.Formatter`. :param record: Enregistrement de log à formater. :return: Message formaté, avec les secrets remplacés par ``REDACTED``. :rtype: str """ formatted = super().format(record) return redact_secrets(formatted) def setup_logging(level: str = "INFO") -> None: """Configure la journalisation globale avec masquage des secrets. Les gestionnaires existants du logger racine sont supprimés, puis un gestionnaire unique écrivant sur ``sys.stdout`` est installé. Les loggers tiers ``urllib3`` et ``slixmpp`` sont ramenés au niveau ``WARNING``. :param level: Nom du niveau de log (ex: ``"DEBUG"``, ``"INFO"``) ; les noms inconnus sont ignorés au profit de ``INFO``. :rtype: None """ numeric_level: int = logging.getLevelNamesMapping().get(level.upper(), logging.INFO) handler = logging.StreamHandler(sys.stdout) handler.setFormatter(RedactingFormatter(fmt=_LOG_FORMAT, datefmt=_DATE_FORMAT)) root = logging.getLogger() for existing in list(root.handlers): root.removeHandler(existing) root.setLevel(numeric_level) root.addHandler(handler) for noisy_name in ("urllib3", "slixmpp"): logging.getLogger(noisy_name).setLevel(logging.WARNING)