"""Module de logging centralisé pour l'application FabNum. Ce module fournit une configuration de logging standardisée pour tous les modules de l'application, avec support de la console et des fichiers. Usage: from utils.logger import setup_logger logger = setup_logger(__name__) logger.info("Message d'information") logger.warning("Avertissement") logger.error("Erreur", exc_info=True) """ import logging import sys from pathlib import Path def setup_logger( name: str, level: str = "INFO", log_to_file: bool = True ) -> logging.Logger: """Configure un logger avec formatage cohérent pour l'application. Args: name: Nom du logger (généralement __name__ du module appelant) level: Niveau de log (DEBUG, INFO, WARNING, ERROR, CRITICAL) log_to_file: Si True, écrit aussi dans un fichier logs/ Returns: Logger configuré avec handlers console et fichier Examples: >>> logger = setup_logger(__name__) >>> logger.info("Application démarrée") >>> logger.warning("Fichier non trouvé", extra={"file": "config.yaml"}) >>> logger.error("Erreur critique", exc_info=True) """ logger = logging.getLogger(name) # Éviter de reconfigurer si déjà fait if logger.handlers: return logger # Format structuré avec timestamp formatter = logging.Formatter( fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # Handler console (stdout) console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(formatter) console_handler.setLevel(logging.DEBUG) # Console affiche tout logger.addHandler(console_handler) # Handler fichier (optionnel) if log_to_file: try: log_dir = Path("logs") log_dir.mkdir(exist_ok=True) # Nom de fichier basé sur le module log_filename = f"{name.replace('.', '_')}.log" log_file = log_dir / log_filename file_handler = logging.FileHandler(log_file, encoding='utf-8') file_handler.setFormatter(formatter) file_handler.setLevel(logging.DEBUG) logger.addHandler(file_handler) except (OSError, PermissionError) as e: # Si impossible d'écrire dans logs/, continuer quand même console_handler.emit( logging.LogRecord( name=name, level=logging.WARNING, pathname=__file__, lineno=0, msg=f"Impossible de créer le fichier de log: {e}", args=(), exc_info=None ) ) # Définir le niveau global du logger logger.setLevel(getattr(logging, level.upper(), logging.INFO)) # Empêcher la propagation au logger root (évite doublons) logger.propagate = False return logger def get_logger(name: str) -> logging.Logger: """Récupère un logger existant ou en crée un nouveau. Args: name: Nom du logger Returns: Logger configuré """ logger = logging.getLogger(name) if not logger.handlers: return setup_logger(name) return logger