Files
college-infos/pronote_sync/utils/logging.py
Antoine Van Elstraete aaca78c55d docs+fix: renforcement de l'architecture agentique et corrections de sécurité
AGENTS.md :
- Rôles d'agents renforcés : @coder ne valide pas, @debugger ne code pas,
  @verifier ne modifie pas, etc.
- Table « Séparation des rôles » : tâche → agent responsable → ne pas confier à
- Workflow étape 5 : délégation explicite à @verifier pour la validation

GUIDE_DEV_PYTHON.md :
- 7 notes « Décision d'implémentation » ajoutées aux sections concernées :
  3.1.1 (XMPP_RECIPIENT→XMPP_TO, vars ajoutées), 3.1.2 (SYNC_PAST_DAYS→AppSettings),
  3.2 (Pydantic v2 style, defaults corrigés), 4.2.1 (redact_exception module function),
  4.2.2 (getLevelNamesMapping), 5.1.5 (normalize_pronote_uid, usedforsecurity=False),
  6 (ConfigDict, StrEnum, alias _date, external_info Optional)

Sécurité (audit @security-auditor, corrections @coder, validation @verifier) :
- RedactingFormatter : redaction APRÈS formatage (corrige TypeError %s + fuite traceback)
- redact_url : masquage des credentials dans userinfo URL (HTTP Basic Auth)
- redact_secrets : patterns étendus (api_key, access_token, authorization, auth)
- redact_secrets : support JSON-style « key: value » avec guillemets

Validations (@verifier) :
- ruff check : PASS | mypy strict : PASS | bandit : PASS (0 issue)
- %s formatting : OK (password=REDACTED, pas de TypeError)
- Traceback redaction : OK (icalsecurise=REDACTED)
- URL userinfo : OK (user:REDACTED@host)
- JSON-style redaction : OK ({"token": "REDACTED"})
- Régression red-to-green : OK (historical HEAD reproduction)

Co-authored-by: OpenCode/orchestrator <opencode-orchestrator@agents.invalid>
2026-09-05 23:52:02 +02:00

57 lines
2.1 KiB
Python

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