Files
alpinebits_python/src/alpine_bits_python/logging_config.py
2025-10-17 19:47:15 +02:00

175 lines
6.3 KiB
Python

"""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)