"""Centralized logging configuration for AlpineBits application. This module sets up logging based on config and provides a function to get loggers from anywhere in the application. """ import asyncio import logging import sys from pathlib import Path from typing import TYPE_CHECKING if TYPE_CHECKING: from alpine_bits_python.email_monitoring import ( DailyReportScheduler, EmailAlertHandler, ) from alpine_bits_python.email_service import EmailService from alpine_bits_python.pushover_service import PushoverService def setup_logging( config: dict | None = None, email_service: "EmailService | None" = None, pushover_service: "PushoverService | None" = None, loop: asyncio.AbstractEventLoop | None = None, enable_scheduler: bool = True, ) -> tuple[logging.Handler | None, object | None]: """Configure logging based on application config. Args: config: Application configuration dict with optional 'logger' section email_service: Optional email service for email alerts pushover_service: Optional pushover service for push notifications loop: Optional asyncio event loop for email alerts enable_scheduler: Whether to enable the daily report scheduler (should be False for non-primary workers) Returns: Tuple of (alert_handler, daily_report_scheduler) if monitoring is enabled, otherwise (None, None) Logger config format: logger: level: "INFO" # DEBUG, INFO, WARNING, ERROR, CRITICAL file: "alpinebits.log" # Optional, logs to console if not provided """ if config is None: config = {} logger_config = config.get("logger", {}) level = logger_config.get("level", "INFO").upper() log_file = logger_config.get("file") # Convert string level to logging constant numeric_level = getattr(logging, level, logging.INFO) # Create formatter with timestamp formatter = logging.Formatter( fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) # Get root logger root_logger = logging.getLogger() root_logger.setLevel(numeric_level) # Remove existing handlers to avoid duplicates root_logger.handlers.clear() # Console handler (always add this) console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(numeric_level) console_handler.setFormatter(formatter) root_logger.addHandler(console_handler) # File handler (optional) if log_file: log_path = Path(log_file) # Create logs directory if it doesn't exist if log_path.parent != Path(): log_path.parent.mkdir(parents=True, exist_ok=True) file_handler = logging.FileHandler(log_file, encoding="utf-8") file_handler.setLevel(numeric_level) file_handler.setFormatter(formatter) root_logger.addHandler(file_handler) root_logger.info("Logging to file: %s", log_file) root_logger.info("Logging configured at %s level", level) # Setup unified notification monitoring if configured alert_handler = None report_scheduler = None # Check if unified notifications are configured notifications_config = config.get("notifications", {}) if notifications_config and (email_service or pushover_service): try: # Import here to avoid circular dependencies from alpine_bits_python.notification_manager import ( get_notification_config, setup_notification_service, ) from alpine_bits_python.unified_monitoring import ( UnifiedAlertHandler, UnifiedDailyReportScheduler, ) # Setup unified notification service notification_service = setup_notification_service( config=config, email_service=email_service, pushover_service=pushover_service, ) if notification_service: # Setup error alert handler error_alerts_config = get_notification_config("error_alerts", config) if error_alerts_config.get("enabled", False): try: alert_handler = UnifiedAlertHandler( notification_service=notification_service, config=error_alerts_config, loop=loop, ) alert_handler.setLevel(logging.ERROR) root_logger.addHandler(alert_handler) root_logger.info("Unified alert handler enabled for error monitoring") except Exception: root_logger.exception("Failed to setup unified alert handler") # Setup daily report scheduler (only if enabled and this is primary worker) daily_report_config = get_notification_config("daily_report", config) if daily_report_config.get("enabled", False) and enable_scheduler: try: report_scheduler = UnifiedDailyReportScheduler( notification_service=notification_service, config=daily_report_config, ) root_logger.info("Unified daily report scheduler configured (primary worker)") except Exception: root_logger.exception("Failed to setup unified daily report scheduler") elif daily_report_config.get("enabled", False) and not enable_scheduler: root_logger.info( "Unified daily report scheduler disabled (non-primary worker)" ) except Exception: root_logger.exception("Failed to setup unified notification monitoring") return alert_handler, report_scheduler def get_logger(name: str) -> logging.Logger: """Get a logger instance for the given module name. Usage: from alpine_bits_python.logging_config import get_logger _LOGGER = get_logger(__name__) _LOGGER.info("Something happened") Args: name: Usually __name__ from the calling module Returns: Configured logger instance """ return logging.getLogger(name)