Files
Nighthawk 5d19f9afc7 Version 2
Major rewrite to version 2.

Some features are broken.
2025-03-28 17:21:21 -04:00

124 lines
5.7 KiB
Python

# modules/log_manager.py
import os
import sys
import logging
from logging.handlers import RotatingFileHandler
from datetime import datetime
from typing import Optional, Any
# Make sure rich is installed: pip install rich
# Import only the module name, check for it later
_rich_logging_available = False
try:
from rich.logging import RichHandler
_rich_logging_available = True
except ImportError:
RichHandler = None # Keep None available for type hinting if needed later
# --- Constants ---
LOG_DIR = "log"; MAX_LOG_SIZE_BYTES = 5*1024*1024; LOG_BACKUP_COUNT = 3
FILE_LOG_FORMAT = "%(asctime)s - %(name)s - [%(levelname)s] - %(filename)s:%(lineno)d - %(message)s"
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
# --- Global Logger ---
logger = logging.getLogger("morpheus")
# --- Configuration Function ---
def setup_logging(
log_level: str = "INFO",
log_dir: str = LOG_DIR,
max_log_size: int = MAX_LOG_SIZE_BYTES,
backup_count: int = LOG_BACKUP_COUNT,
app_name: str = "morpheus"
) -> logging.Logger:
"""Configures logging with Rich console (if available) and file logging."""
global logger
logger = logging.getLogger(app_name)
level = getattr(logging, log_level.upper(), logging.INFO)
logger.setLevel(level)
# Prevent adding multiple handlers
if logger.hasHandlers(): logger.handlers.clear()
# --- Console Handler ---
console_handler: Optional[logging.Handler] = None
if _rich_logging_available and RichHandler is not None: # Check BOTH flag and type alias
try:
# --- Try creating Rich Handler ONLY if import succeeded ---
console_handler = RichHandler(
rich_tracebacks=True,
show_path=False,
markup=True,
show_level=True,
)
console_handler.setLevel(level)
logger.addHandler(console_handler)
# Log first message using potential Rich handler
logger.info("Rich console handler enabled. Console Level: %s.", log_level.upper())
except Exception as rich_err:
print(f"Warning: Failed to initialize RichHandler: {rich_err}. Falling back.", file=sys.stderr)
# Ensure handler is None if init failed
console_handler = None
# Remove potentially partially added handler if logger has it
if logger.hasHandlers():
logger.handlers[:] = [h for h in logger.handlers if not isinstance(h, RichHandler)]
# Fallback or if RichHandler failed/unavailable
if console_handler is None:
# Check if a basic handler is already added (e.g., by basicConfig guard)
if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(level)
basic_formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s', datefmt='%H:%M:%S')
console_handler.setFormatter(basic_formatter)
logger.addHandler(console_handler)
logger.info("Using basic console logging. Console Level: %s.", log_level.upper())
else:
# A basic handler (likely from basicConfig) already exists, use it
logger.info("Basic console handler already present. Console Level: %s.", log_level.upper())
# --- File Handlers ---
try:
os.makedirs(log_dir, exist_ok=True)
# General File Handler
log_filename = os.path.join(log_dir, f"{app_name}_{datetime.now().strftime('%Y%m%d')}.log")
file_handler = RotatingFileHandler(log_filename, maxBytes=max_log_size, backupCount=backup_count, encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
file_formatter = logging.Formatter(FILE_LOG_FORMAT, datefmt=DATE_FORMAT)
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
# Error File Handler
error_log_filename = os.path.join(log_dir, f"{app_name}_errors_{datetime.now().strftime('%Y%m%d')}.log")
error_handler = RotatingFileHandler(error_log_filename, maxBytes=max_log_size, backupCount=backup_count, encoding="utf-8")
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(file_formatter)
logger.addHandler(error_handler)
logger.debug("File logging initialized to directory: '%s'", log_dir)
except Exception as e:
logger.error("Failed to set up file logging: %s", e, exc_info=True)
logger.warning("File logging is disabled.")
# --- Capture warnings ---
logging.captureWarnings(True)
warnings_logger = logging.getLogger("py.warnings")
warnings_logger.propagate = False
# Clear any previous handlers from warnings logger
if warnings_logger.hasHandlers(): warnings_logger.handlers.clear()
# Add *current* handlers from main logger to warnings logger
for handler in logger.handlers:
# Avoid adding file handlers multiple times if setup is called again
if not any(isinstance(h, type(handler)) and getattr(h, 'baseFilename', None) == getattr(handler, 'baseFilename', ' ') for h in warnings_logger.handlers):
warnings_logger.addHandler(handler)
logger.info("Logging setup complete.")
return logger
# --- Initial Setup Guard REMOVED ---
# Removing this guard. If logger is accessed early, it might raise NoHandlerError,
# which is acceptable as setup_logging *must* be called by main() after config load.
# This prevents the fallback basicConfig from interfering with RichHandler setup.
# if not logger.hasHandlers():
# logging.basicConfig(level=logging.WARNING, format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%H:%M:%S')
# logger.warning("Logger accessed before explicit setup. Using basicConfig as fallback.")