feat(gui): log fichier rotatif V6 à chemin connu (E1)
This commit is contained in:
67
gui_v6/logging_setup.py
Normal file
67
gui_v6/logging_setup.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
"""Configuration du log fichier de la GUI V6 (E1).
|
||||||
|
|
||||||
|
Sans ceci, la GUI frozen fenêtrée (sans console) perd ses logs de diagnostic.
|
||||||
|
Le log est posé dans le même répertoire applicatif que la licence
|
||||||
|
(``%LOCALAPPDATA%/Aivanov/Anonymisation``) pour faciliter sa récupération (E2/E3).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_CONFIGURED = False
|
||||||
|
|
||||||
|
|
||||||
|
def _app_data_dir() -> Path:
|
||||||
|
base = os.environ.get("LOCALAPPDATA")
|
||||||
|
if base:
|
||||||
|
root = Path(base)
|
||||||
|
else: # Linux/dev
|
||||||
|
root = Path.home() / ".local" / "share"
|
||||||
|
return root / "Aivanov" / "Anonymisation"
|
||||||
|
|
||||||
|
|
||||||
|
def log_file_path() -> Path:
|
||||||
|
return _app_data_dir() / "logs" / "anonymisation.log"
|
||||||
|
|
||||||
|
|
||||||
|
def setup_file_logging() -> Path:
|
||||||
|
"""Configure un handler fichier rotatif sur le logger racine. Idempotent."""
|
||||||
|
global _CONFIGURED
|
||||||
|
path = log_file_path()
|
||||||
|
if _CONFIGURED:
|
||||||
|
return path
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
handler = RotatingFileHandler(
|
||||||
|
str(path), maxBytes=2_000_000, backupCount=3, encoding="utf-8"
|
||||||
|
)
|
||||||
|
handler.setFormatter(
|
||||||
|
logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
|
||||||
|
)
|
||||||
|
root = logging.getLogger()
|
||||||
|
if root.level == logging.NOTSET:
|
||||||
|
root.setLevel(logging.INFO)
|
||||||
|
root.addHandler(handler)
|
||||||
|
# Best-effort : si le cœur utilise loguru, on ajoute aussi un sink fichier.
|
||||||
|
try:
|
||||||
|
from loguru import logger as _loguru
|
||||||
|
|
||||||
|
_loguru.add(str(path), rotation="2 MB", retention=3, encoding="utf-8")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
_CONFIGURED = True
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def _reset_for_tests() -> None:
|
||||||
|
"""Réinitialise l'état pour l'isolation des tests (NE PAS appeler en prod)."""
|
||||||
|
global _CONFIGURED
|
||||||
|
root = logging.getLogger()
|
||||||
|
for h in list(root.handlers):
|
||||||
|
if isinstance(h, RotatingFileHandler):
|
||||||
|
root.removeHandler(h)
|
||||||
|
h.close()
|
||||||
|
_CONFIGURED = False
|
||||||
16
tests/unit/test_gui_v6_logging_setup.py
Normal file
16
tests/unit/test_gui_v6_logging_setup.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
def test_setup_file_logging_writes_to_known_path(tmp_path, monkeypatch):
|
||||||
|
monkeypatch.setenv("LOCALAPPDATA", str(tmp_path))
|
||||||
|
from gui_v6.logging_setup import setup_file_logging, _reset_for_tests
|
||||||
|
|
||||||
|
try:
|
||||||
|
log_path = setup_file_logging()
|
||||||
|
assert log_path.parent.exists()
|
||||||
|
logging.getLogger("test.e1").warning("ligne-temoin-42")
|
||||||
|
for h in logging.getLogger().handlers:
|
||||||
|
h.flush()
|
||||||
|
assert "ligne-temoin-42" in log_path.read_text(encoding="utf-8")
|
||||||
|
finally:
|
||||||
|
_reset_for_tests()
|
||||||
Reference in New Issue
Block a user