Source code for genome_entropy.logging_config

"""Centralized logging configuration for genome_entropy.

This module provides a single source for configuring logging throughout the application.
It supports:
- Multiple log levels (DEBUG, INFO, WARNING, ERROR, CRITICAL)
- Output to file or STDOUT
- Consistent format across all modules
"""

import logging
import sys
from pathlib import Path
from typing import Optional, Union

# Default logging format
DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"

# Module logger cache to avoid re-configuration
_configured = False
_log_file: Optional[Path] = None
_log_level: int = logging.INFO


[docs] def configure_logging( level: Union[int, str] = logging.INFO, log_file: Optional[Union[str, Path]] = None, log_format: str = DEFAULT_LOG_FORMAT, date_format: str = DEFAULT_DATE_FORMAT, force: bool = False, ) -> None: """Configure logging for the entire application. This should be called once at application startup (e.g., in CLI main). Args: level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) as int or string log_file: Optional path to log file. If None, logs to STDOUT log_format: Format string for log messages date_format: Format string for timestamps force: If True, reconfigure even if already configured Examples: >>> configure_logging(level=logging.DEBUG, log_file="app.log") >>> configure_logging(level="INFO") # Log to STDOUT >>> configure_logging(level="DEBUG", log_file=None) # Debug to STDOUT """ global _configured, _log_file, _log_level # Convert string level to int if needed ori_level = level if isinstance(level, str): level = getattr(logging, level.upper(), logging.INFO) # We always reconfigure, whether or not we are forced to. # this is now here to prevent an error :) if force: logging.info("force is deprecated in logging - we always switch levels") # Clear any existing handlers root_logger = logging.getLogger() for handler in root_logger.handlers[:]: root_logger.removeHandler(handler) # Create formatter formatter = logging.Formatter(log_format, datefmt=date_format) # Create handler (file or stdout) if log_file: log_path = Path(log_file) log_path.parent.mkdir(parents=True, exist_ok=True) handler = logging.FileHandler(log_path, mode="a") else: handler = logging.StreamHandler(sys.stdout) handler.setFormatter(formatter) # Configure root logger root_logger.setLevel(level) root_logger.addHandler(handler) # Store configuration state _configured = True _log_file = Path(log_file) if log_file else None _log_level = level
[docs] def get_logger(name: str) -> logging.Logger: """Get a logger instance for a module. This is the preferred way to get loggers in the application. Args: name: Name of the logger (usually __name__ of the module) Returns: Configured logger instance Example: >>> logger = get_logger(__name__) >>> logger.info("Processing started") """ return logging.getLogger(name)
[docs] def is_configured() -> bool: """Check if logging has been configured. Returns: True if configure_logging() has been called """ return _configured
[docs] def get_log_file() -> Optional[Path]: """Get the current log file path. Returns: Path to log file, or None if logging to STDOUT """ return _log_file
[docs] def get_log_level() -> int: """Get the current logging level. Returns: Current logging level as integer """ return _log_level
[docs] def set_log_level(level: Union[int, str]) -> None: """Change the logging level at runtime. Args: level: New logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) Example: >>> set_log_level("DEBUG") >>> set_log_level(logging.WARNING) """ global _log_level # Convert string to int if needed if isinstance(level, str): level = getattr(logging, level.upper(), logging.INFO) _log_level = level logging.getLogger().setLevel(level)