"""Journal des événements de connexion WGSecure.""" import os import json import logging from datetime import datetime from logging.handlers import RotatingFileHandler from app.utils.platform_utils import get_config_dir _FILE = "events.json" _MAX_ENTRIES = 200 _LOG_FILE = "wgsecure.log" _LOG_LEVELS = { "error": logging.ERROR, "warning": logging.WARNING, } _file_logger: logging.Logger | None = None def _path() -> str: return os.path.join(get_config_dir(), _FILE) def _get_file_logger() -> logging.Logger: """Logger fichier rotatif : trace texte des opérations, lisible sans passer par l'UI (diagnostic d'un échec quand le panneau Journal ne suffit pas).""" global _file_logger if _file_logger is None: logger = logging.getLogger("wgsecure") logger.setLevel(logging.INFO) handler = RotatingFileHandler( os.path.join(get_config_dir(), _LOG_FILE), maxBytes=1_000_000, backupCount=3, encoding="utf-8", ) handler.setFormatter(logging.Formatter( "%(asctime)s [%(levelname)s] %(message)s", "%Y-%m-%d %H:%M:%S", )) logger.addHandler(handler) logger.propagate = False _file_logger = logger return _file_logger def log_event(msg: str, kind: str = "info") -> None: """Ajoute un événement horodaté au journal.""" events = _load() events.append({ "ts": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "kind": kind, # info | success | error | warning "msg": msg, }) _save(events[-_MAX_ENTRIES:]) # Une ligne = un événement, même si msg contient des retours à la ligne # (ex. message de succès de connexion suivi d'un avertissement). _get_file_logger().log( _LOG_LEVELS.get(kind, logging.INFO), msg.replace("\n", " | ") ) def get_events(n: int = 50) -> list[dict]: return _load()[-n:] def clear() -> None: _save([]) def _load() -> list[dict]: p = _path() if not os.path.exists(p): return [] try: with open(p, "r", encoding="utf-8") as f: return json.load(f) except Exception: return [] def _save(events: list[dict]) -> None: try: with open(_path(), "w", encoding="utf-8") as f: json.dump(events, f, ensure_ascii=False) except Exception: pass