""" Centralized logging configuration for emb-explorer. Usage: from shared.utils.logging_config import get_logger logger = get_logger(__name__) logger.info("Message") """ import logging import os import sys from typing import Optional # Module-level flag to track if logging has been configured _logging_configured = False # Default log directory (relative to working directory) _LOG_DIR = os.environ.get("EMB_EXPLORER_LOG_DIR", "logs") _LOG_FILE = "emb_explorer.log" def configure_logging( level: int = logging.INFO, log_format: Optional[str] = None, log_to_file: bool = True, ): """ Configure the root logger for the application. Args: level: Logging level (default: INFO) log_format: Custom log format string (optional) log_to_file: Whether to also write logs to a file (default: True) """ global _logging_configured if _logging_configured: return if log_format is None: log_format = ( "[%(asctime)s] %(levelname)s " "[%(name)s.%(funcName)s:%(lineno)d] %(message)s" ) # Configure root logger root_logger = logging.getLogger() root_logger.setLevel(level) # Remove existing handlers to avoid duplicates for handler in root_logger.handlers[:]: root_logger.removeHandler(handler) formatter = logging.Formatter(log_format, datefmt="%Y-%m-%d %H:%M:%S") # Console handler console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(level) console_handler.setFormatter(formatter) root_logger.addHandler(console_handler) # File handler (append mode, rotates implicitly by date via log dir) file_handler = None if log_to_file: try: os.makedirs(_LOG_DIR, exist_ok=True) file_handler = logging.FileHandler( os.path.join(_LOG_DIR, _LOG_FILE), mode="a", encoding="utf-8" ) file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(formatter) root_logger.addHandler(file_handler) except OSError: # Non-fatal: skip file logging if directory can't be created pass # Prevent duplicate messages from Streamlit's root logger handler. # Our app loggers (shared.*, apps.*) get their own handlers and don't # propagate to root, avoiding double output from Streamlit's handler. for prefix in ("shared", "apps"): pkg_logger = logging.getLogger(prefix) pkg_logger.propagate = False if not pkg_logger.handlers: pkg_logger.addHandler(console_handler) if file_handler: pkg_logger.addHandler(file_handler) _logging_configured = True def get_logger(name: str) -> logging.Logger: """ Get a logger instance for the given module name. Automatically configures logging if not already done. Args: name: Logger name (typically __name__) Returns: Logger instance """ configure_logging() return logging.getLogger(name)