mirror of
https://github.com/Nighthawk42/mOrpheus.git
synced 2026-08-30 09:22:26 +00:00
124 lines
5.7 KiB
Python
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.") |