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>
57 lines
2.1 KiB
Python
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)
|