- Une ligne de log par requête (route modèle, statut, durée, code d'erreur, compte), identifiant X-Request-ID repris dans chaque log et affiché à l'utilisateur sous une erreur, exceptions non gérées tracées (500 JSON) - Journal d'accès d'uvicorn coupé : il exposait le jeton du formulaire public - Logs métier : paiement SumUp, connexion, import, plannings, solveur - Nom du bénévole dans la description du checkout SumUp - Code et identifiant de transaction SumUp enregistrés au paiement (migration 0023), affichés sur la fiche et dans l'export Excel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
45 lines
2.1 KiB
Python
45 lines
2.1 KiB
Python
import logging
|
|
from contextvars import ContextVar
|
|
|
|
from app.config import get_settings
|
|
|
|
# RGPD — RÈGLE STRICTE
|
|
# Ne jamais journaliser de données nominatives : pas de nom, prénom, email ni
|
|
# téléphone dans les logs. On journalise des identifiants (UUID/entier) et des
|
|
# codes. Toute nouvelle instruction de log doit respecter cette règle.
|
|
|
|
# Identifiant de la requête HTTP en cours (voir `app/request_logging.py`),
|
|
# repris sur chaque ligne de log émise pendant son traitement — y compris
|
|
# dans ses tâches de fond (`BackgroundTasks`, même contexte) — pour retrouver
|
|
# d'un `grep` tout ce qui s'est passé lors de l'erreur qu'un utilisateur
|
|
# signale (la référence lui est affichée, voir `frontend/.../state.tsx`).
|
|
request_id_var: ContextVar[str] = ContextVar("request_id", default="-")
|
|
|
|
|
|
class RequestIdFilter(logging.Filter):
|
|
def filter(self, record: logging.LogRecord) -> bool:
|
|
record.request_id = request_id_var.get()
|
|
return True
|
|
|
|
|
|
def configure_logging() -> None:
|
|
# `force=True` : on réinstalle le handler racine même si l'import de
|
|
# `app.main` se fait après la configuration de logging d'uvicorn/gunicorn,
|
|
# sinon les logs `app.*` n'apparaissent pas dans la sortie du conteneur.
|
|
logging.basicConfig(
|
|
level=get_settings().log_level,
|
|
format="%(asctime)s %(levelname)s [%(request_id)s] %(name)s %(message)s",
|
|
force=True,
|
|
)
|
|
# Filtre posé sur le handler (pas sur un logger) : il voit aussi les
|
|
# lignes des bibliothèques (uvicorn, sqlalchemy…) propagées à la racine.
|
|
for handler in logging.getLogger().handlers:
|
|
handler.addFilter(RequestIdFilter())
|
|
# Journal d'accès d'uvicorn remplacé par `app/request_logging.py` : le
|
|
# sien écrit l'URL complète, chaîne de requête comprise — donc le jeton
|
|
# `access_token` du formulaire public (CLAUDE.md § 2, `FormTemplate`).
|
|
logging.getLogger("uvicorn.access").disabled = True
|
|
# Bavard au niveau INFO (une ligne par appel SumUp/hCaptcha), sans
|
|
# intérêt à côté des logs de l'appli.
|
|
logging.getLogger("httpx").setLevel(logging.WARNING)
|