Files
Azornes-Comfyui-Model-Resolver/core/log_system/logger.py
T
Dariusz L 3687d26f17 Bump log version; use project root for module name
Bump logging version to 1.4.1 and improve module name detection by adding _find_project_root in log_funcs to locate the repository root (searches for a .git directory) and use its basename when LOG_MODULE_NAME is not set, with a fallback to the previous behavior. Also update the same version string in logger.py. Additionally, comment out two noisy debug log lines in core/scanner.py to reduce verbose output when directories are missing or after scanning.
2026-04-22 03:44:41 +02:00

349 lines
10 KiB
Python

"""
@author: Azornes
@title: AzLogs
@version: 1.4.1
@description: Logging Setup - Central logging system
Features:
- Different log levels (DEBUG, INFO, WARN, ERROR)
- Ability to enable/disable logs globally or per module
- Colored logs in console
- Log file rotation
- Configuration via environment variables
"""
import os
import sys
import json
import logging
import datetime
from enum import IntEnum
from logging.handlers import RotatingFileHandler
import traceback
# Log levels
class LogLevel(IntEnum):
DEBUG = 10
INFO = 20
WARN = 30
ERROR = 40
NONE = 100
# Level mapping
LEVEL_MAP = {
LogLevel.DEBUG: logging.DEBUG,
LogLevel.INFO: logging.INFO,
LogLevel.WARN: logging.WARNING,
LogLevel.ERROR: logging.ERROR,
LogLevel.NONE: logging.CRITICAL + 1,
}
# ANSI colors for different log levels
COLORS = {
LogLevel.DEBUG: "\033[90m", # Gray
LogLevel.INFO: "\033[94m", # Blue
LogLevel.WARN: "\033[93m", # Yellow
LogLevel.ERROR: "\033[91m", # Red
"RESET": "\033[0m", # Reset
}
# Default configuration
DEFAULT_CONFIG = {
"global_level": LogLevel.INFO,
"module_settings": {},
"use_colors": True,
"log_to_file": False,
"log_dir": "logs",
"max_file_size_mb": 10,
"backup_count": 5,
"timestamp_format": "%H:%M:%S",
}
class ColoredFormatter(logging.Formatter):
"""Formatter that adds colors to console logs"""
def __init__(self, fmt=None, datefmt=None, use_colors=True):
super().__init__(fmt, datefmt)
self.use_colors = use_colors
def format(self, record):
# Get the formatted message from the record
message = record.getMessage()
if record.exc_info:
message += "\n" + self.formatException(record.exc_info)
levelname = record.levelname
# Build the log prefix
prefix = "[{}] [{}] [{}]".format(
self.formatTime(record, self.datefmt), record.name, record.levelname
)
# Apply color and bold styling to the prefix
if self.use_colors and hasattr(LogLevel, levelname):
level_enum = getattr(LogLevel, levelname)
if level_enum in COLORS:
# Apply bold (\033[1m) and color, then reset
prefix = f"\033[1m{COLORS[level_enum]}{prefix}{COLORS['RESET']}"
return f"{prefix} {message}"
class AzLogsLogger:
"""Main logger class for AzLogs"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(AzLogsLogger, cls).__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self.config = DEFAULT_CONFIG.copy()
self.enabled = True
self.loggers = {}
# Load configuration from environment variables
self._load_config_from_env()
self._initialized = True
def _load_config_from_env(self):
"""Load configuration from environment variables"""
# Global level
if "AZLOGS_LOG_LEVEL" in os.environ:
level_name = os.environ["AZLOGS_LOG_LEVEL"].upper()
if hasattr(LogLevel, level_name):
self.config["global_level"] = getattr(LogLevel, level_name)
# Module settings
if "AZLOGS_MODULE_LEVELS" in os.environ:
try:
module_settings = json.loads(os.environ["AZLOGS_MODULE_LEVELS"])
for module, level_name in module_settings.items():
if hasattr(LogLevel, level_name.upper()):
self.config["module_settings"][module] = getattr(
LogLevel, level_name.upper()
)
except json.JSONDecodeError:
pass
# Other settings
if "AZLOGS_USE_COLORS" in os.environ:
self.config["use_colors"] = (
os.environ["AZLOGS_USE_COLORS"].lower() == "true"
)
if "AZLOGS_LOG_TO_FILE" in os.environ:
self.config["log_to_file"] = (
os.environ["AZLOGS_LOG_TO_FILE"].lower() == "true"
)
if "AZLOGS_LOG_DIR" in os.environ:
self.config["log_dir"] = os.environ["AZLOGS_LOG_DIR"]
if "AZLOGS_MAX_FILE_SIZE_MB" in os.environ:
try:
self.config["max_file_size_mb"] = int(
os.environ["AZLOGS_MAX_FILE_SIZE_MB"]
)
except ValueError:
pass
if "AZLOGS_BACKUP_COUNT" in os.environ:
try:
self.config["backup_count"] = int(os.environ["AZLOGS_BACKUP_COUNT"])
except ValueError:
pass
def configure(self, config):
"""Configure the logger"""
self.config.update(config)
# If file logging is enabled, ensure the directory exists
if self.config.get("log_to_file") and self.config.get("log_dir"):
try:
os.makedirs(self.config["log_dir"], exist_ok=True)
except OSError as e:
# This is a critical situation, so use print
print(
f"[CRITICAL] Could not create log directory: {self.config['log_dir']}. Error: {e}"
)
traceback.print_exc()
# Disable file logging to avoid further errors
self.config["log_to_file"] = False
return self
def set_enabled(self, enabled):
"""Enable/disable the logger globally"""
self.enabled = enabled
return self
def set_global_level(self, level):
"""Set global logging level"""
self.config["global_level"] = level
return self
def set_module_level(self, module, level):
"""Set logging level for a specific module"""
self.config["module_settings"][module] = level
return self
def is_level_enabled(self, module, level):
"""Check if a given logging level is active for the module"""
if not self.enabled:
return False
# Determine effective logging level, considering module and global settings
effective_level = self.config["module_settings"].get(
module, self.config["global_level"]
)
# If effective level is NONE, logging is completely disabled
if effective_level == LogLevel.NONE:
return False
# Otherwise check if log level is high enough
return level >= effective_level
def _get_logger(self, module):
"""Get or create a logger for the module"""
if module in self.loggers:
return self.loggers[module]
# Create new logger
logger = logging.getLogger(f"azlogs.{module}")
logger.setLevel(logging.DEBUG) # Set lowest level, filtering will be done later
logger.propagate = False
# Add console handler
console_handler = logging.StreamHandler(sys.stdout)
console_formatter = ColoredFormatter(
fmt="[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s",
datefmt=self.config["timestamp_format"],
use_colors=self.config["use_colors"],
)
console_handler.setFormatter(console_formatter)
logger.addHandler(console_handler)
# Add file handler if file logging is enabled
if self.config["log_to_file"]:
log_file = os.path.join(self.config["log_dir"], f"azlogs_{module}.log")
file_handler = RotatingFileHandler(
log_file,
maxBytes=self.config["max_file_size_mb"] * 1024 * 1024,
backupCount=self.config["backup_count"],
encoding="utf-8",
)
file_formatter = logging.Formatter(
fmt="[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
self.loggers[module] = logger
return logger
def log(self, module, level, *args, **kwargs):
"""Write log"""
if not self.is_level_enabled(module, level):
return
logger = self._get_logger(module)
# Convert arguments to string
message = " ".join(str(arg) for arg in args)
# Add exception info if provided
exc_info = kwargs.get("exc_info", None)
# Map LogLevel to logging level
log_level = LEVEL_MAP.get(level, logging.INFO)
# Write log
logger.log(log_level, message, exc_info=exc_info)
def debug(self, module, *args, **kwargs):
"""Log at DEBUG level"""
self.log(module, LogLevel.DEBUG, *args, **kwargs)
def info(self, module, *args, **kwargs):
"""Log at INFO level"""
self.log(module, LogLevel.INFO, *args, **kwargs)
def warn(self, module, *args, **kwargs):
"""Log at WARN level"""
self.log(module, LogLevel.WARN, *args, **kwargs)
def error(self, module, *args, **kwargs):
"""Log at ERROR level"""
self.log(module, LogLevel.ERROR, *args, **kwargs)
def exception(self, module, *args):
"""Log exception at ERROR level"""
self.log(module, LogLevel.ERROR, *args, exc_info=True)
# Singleton
logger = AzLogsLogger()
# Helper functions
def debug(module, *args, **kwargs):
"""Log at DEBUG level"""
logger.log(module, LogLevel.DEBUG, *args, **kwargs)
def info(module, *args, **kwargs):
"""Log at INFO level"""
logger.log(module, LogLevel.INFO, *args, **kwargs)
def warn(module, *args, **kwargs):
"""Log at WARN level"""
logger.log(module, LogLevel.WARN, *args, **kwargs)
def error(module, *args, **kwargs):
"""Log at ERROR level"""
logger.log(module, LogLevel.ERROR, *args, **kwargs)
def exception(module, *args):
"""Log exception at ERROR level"""
logger.log(module, LogLevel.ERROR, *args, exc_info=True)
# Function to quickly enable/disable debugging
def set_debug(enabled=True):
"""Enable/disable debugging globally"""
if enabled:
logger.set_global_level(LogLevel.DEBUG)
else:
logger.set_global_level(LogLevel.INFO)
return logger
# Function to enable/disable file logging
def set_file_logging(enabled=True, log_dir=None):
"""Enable/disable logging to file"""
logger.config["log_to_file"] = enabled
if log_dir:
logger.config["log_dir"] = log_dir
os.makedirs(log_dir, exist_ok=True)
# Reset loggers to apply new settings
logger.loggers = {}
return logger