feat: migrate Model Resolver logging system
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
@author: Azornes
|
||||
@title: AzLogs
|
||||
@version: 2.0.1
|
||||
@description: Logging Initializer
|
||||
"""
|
||||
|
||||
from .log_funcs import create_module_logger
|
||||
from .logger import LogLevel, logger
|
||||
|
||||
__all__ = [
|
||||
"LogLevel",
|
||||
"create_module_logger",
|
||||
"logger",
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
@author: Azornes
|
||||
@title: AzLogs
|
||||
@version: 2.0.1
|
||||
@description: Logging Configuration
|
||||
"""
|
||||
# Log level for development.
|
||||
# Possible values: 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL', 'NONE'
|
||||
LOG_LEVEL = "ERROR"
|
||||
|
||||
# Logger module name. Set to None to auto-detect from folder name.
|
||||
LOG_MODULE_NAME = "LayerForge"
|
||||
|
||||
# Enable ANSI colors in console logs.
|
||||
USE_COLORS = True
|
||||
|
||||
# Project name aliases stripped during module name normalization.
|
||||
PROJECT_ALIASES = ["Comfyui-LayerForge", "comfyui-layerforge", "layerforge"]
|
||||
@@ -0,0 +1,201 @@
|
||||
"""
|
||||
@author: Azornes
|
||||
@title: AzLogs
|
||||
@version: 2.0.1
|
||||
@description: Logging Initializer
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import traceback
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
_initialized = False
|
||||
_project_root = None
|
||||
_default_module_name = __name__
|
||||
|
||||
try:
|
||||
from .config import LOG_LEVEL, LOG_MODULE_NAME, PROJECT_ALIASES, USE_COLORS
|
||||
from .logger import LogLevel, debug, error, exception, fatal, info, logger, warn
|
||||
|
||||
def _find_project_root(start_path):
|
||||
current = os.path.dirname(os.path.abspath(start_path))
|
||||
while current:
|
||||
if os.path.isdir(os.path.join(current, ".git")):
|
||||
return current
|
||||
parent = os.path.dirname(current)
|
||||
if parent == current:
|
||||
break
|
||||
current = parent
|
||||
return os.path.dirname(os.path.dirname(os.path.abspath(start_path)))
|
||||
|
||||
_project_root = _find_project_root(__file__)
|
||||
_default_module_name = (
|
||||
LOG_MODULE_NAME
|
||||
if LOG_MODULE_NAME is not None
|
||||
else os.path.basename(_project_root)
|
||||
)
|
||||
|
||||
logger.set_global_level(LogLevel[LOG_LEVEL])
|
||||
|
||||
logger.configure(
|
||||
{
|
||||
"log_to_file": True,
|
||||
"log_dir": os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs"),
|
||||
"use_colors": (
|
||||
logger.config["use_colors"]
|
||||
if "AZLOGS_USE_COLORS" in os.environ
|
||||
else USE_COLORS
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
_initialized = True
|
||||
except ImportError as e:
|
||||
_initialized = False
|
||||
_logger.error(f"Failed to initialize logger: {e}")
|
||||
# Provide fallback values when import fails
|
||||
LOG_MODULE_NAME = None
|
||||
PROJECT_ALIASES = []
|
||||
|
||||
|
||||
def _normalize_module_name(module_name):
|
||||
"""Normalize a dotted module name for display in logs.
|
||||
|
||||
Strips common prefixes (custom_nodes, project aliases) and ``__init__``
|
||||
suffixes to produce a clean, readable module path.
|
||||
|
||||
ComfyUI sets ``__name__`` to hybrid values like
|
||||
``E:\\...\\comfyui-model-resolver.core.sources.model_list`` where the
|
||||
first dotted segment is a full OS path. We detect this and convert
|
||||
the path portion to a dotted name before applying the normal logic.
|
||||
"""
|
||||
module_name = str(module_name or "").strip()
|
||||
if not module_name or module_name == "__main__":
|
||||
return _default_module_name
|
||||
|
||||
# --- path-to-dots conversion (required for ComfyUI's __name__) ---
|
||||
if os.sep in module_name or "/" in module_name:
|
||||
# Replace all path separators with dots so the rest of the logic
|
||||
# can work uniformly on dotted segments.
|
||||
normalized = module_name.replace("\\", ".").replace("/", ".")
|
||||
# Remove drive letter prefix (e.g. "E:." → "")
|
||||
if len(normalized) >= 2 and normalized[1] == ":":
|
||||
normalized = normalized[2:].lstrip(".")
|
||||
# Strip .py extension if present
|
||||
if normalized.endswith(".py"):
|
||||
normalized = normalized[:-3]
|
||||
module_name = normalized
|
||||
|
||||
parts = [p for p in module_name.split(".") if p]
|
||||
if not parts:
|
||||
return _default_module_name
|
||||
|
||||
# Strip __init__ suffix
|
||||
if parts[-1] == "__init__":
|
||||
parts = parts[:-1]
|
||||
|
||||
# Strip LOG_MODULE_NAME prefix if present (re-added later)
|
||||
if LOG_MODULE_NAME and parts and parts[0] == LOG_MODULE_NAME:
|
||||
parts = parts[1:]
|
||||
|
||||
# Strip custom_nodes.<package_name> prefix
|
||||
if "custom_nodes" in parts:
|
||||
idx = parts.index("custom_nodes")
|
||||
parts = parts[idx + 2:] # skip custom_nodes + package folder
|
||||
|
||||
# Strip project aliases from config
|
||||
for alias in PROJECT_ALIASES:
|
||||
if alias in parts:
|
||||
parts = parts[parts.index(alias) + 1:]
|
||||
break
|
||||
|
||||
# Re-insert LOG_MODULE_NAME prefix at the start
|
||||
package_name = LOG_MODULE_NAME or os.path.basename(_project_root or "")
|
||||
if package_name and (not parts or parts[0] != package_name):
|
||||
parts.insert(0, package_name)
|
||||
|
||||
cleaned = ".".join(parts).strip(".")
|
||||
return cleaned or _default_module_name
|
||||
|
||||
|
||||
# Number of internal call frames between ModuleLogger methods and
|
||||
# Python's logging.Logger.log(). Used to compute the correct
|
||||
# ``stacklevel`` so that log records report the *caller's* file and
|
||||
# line number instead of an internal wrapper.
|
||||
#
|
||||
# Call chain: ModuleLogger.method → module-level func → AzLogsLogger.log → logging.Logger.log
|
||||
_INTERNAL_FRAMES = 3
|
||||
|
||||
|
||||
class ModuleLogger:
|
||||
"""Per-module logger with a fixed module name.
|
||||
|
||||
Usage:
|
||||
log = create_module_logger(__name__)
|
||||
log.debug("message")
|
||||
log.info("message")
|
||||
log.info("downloaded", extra={"model": "xyz"})
|
||||
"""
|
||||
|
||||
def __init__(self, module_name):
|
||||
self.module_name = module_name
|
||||
|
||||
def debug(self, *args, **kwargs):
|
||||
if _initialized:
|
||||
kwargs["stacklevel"] = kwargs.pop("stacklevel", 1) + _INTERNAL_FRAMES
|
||||
debug(self.module_name, *args, **kwargs)
|
||||
else:
|
||||
print(f"[DEBUG] [{self.module_name}]", *args)
|
||||
|
||||
def info(self, *args, **kwargs):
|
||||
if _initialized:
|
||||
kwargs["stacklevel"] = kwargs.pop("stacklevel", 1) + _INTERNAL_FRAMES
|
||||
info(self.module_name, *args, **kwargs)
|
||||
else:
|
||||
print(f"[INFO] [{self.module_name}]", *args)
|
||||
|
||||
def warning(self, *args, **kwargs):
|
||||
if _initialized:
|
||||
kwargs["stacklevel"] = kwargs.pop("stacklevel", 1) + _INTERNAL_FRAMES
|
||||
warn(self.module_name, *args, **kwargs)
|
||||
else:
|
||||
print(f"[WARN] [{self.module_name}]", *args)
|
||||
|
||||
def warn(self, *args, **kwargs):
|
||||
self.warning(*args, **kwargs)
|
||||
|
||||
def error(self, *args, **kwargs):
|
||||
if _initialized:
|
||||
kwargs["stacklevel"] = kwargs.pop("stacklevel", 1) + _INTERNAL_FRAMES
|
||||
error(self.module_name, *args, **kwargs)
|
||||
else:
|
||||
print(f"[ERROR] [{self.module_name}]", *args)
|
||||
|
||||
def exception(self, *args, **kwargs):
|
||||
if _initialized:
|
||||
kwargs["stacklevel"] = kwargs.pop("stacklevel", 1) + _INTERNAL_FRAMES
|
||||
exception(self.module_name, *args, **kwargs)
|
||||
else:
|
||||
print(f"[ERROR] [{self.module_name}]", *args)
|
||||
traceback.print_exc()
|
||||
|
||||
def fatal(self, *args, **kwargs):
|
||||
if _initialized:
|
||||
kwargs["stacklevel"] = kwargs.pop("stacklevel", 1) + _INTERNAL_FRAMES
|
||||
fatal(self.module_name, *args, **kwargs)
|
||||
else:
|
||||
print(f"[FATAL] [{self.module_name}]", *args)
|
||||
|
||||
|
||||
def create_module_logger(module_name=None):
|
||||
"""Create a ModuleLogger with a normalized module name.
|
||||
|
||||
Args:
|
||||
module_name: Dotted module name, typically ``__name__``.
|
||||
Required for proper module identification.
|
||||
|
||||
Returns:
|
||||
A :class:`ModuleLogger` instance.
|
||||
"""
|
||||
resolved_name = _normalize_module_name(module_name or _default_module_name)
|
||||
return ModuleLogger(resolved_name)
|
||||
@@ -0,0 +1,658 @@
|
||||
"""
|
||||
@author: Azornes
|
||||
@title: AzLogs
|
||||
@version: 2.0.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 json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from enum import IntEnum
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
|
||||
# Log levels
|
||||
class LogLevel(IntEnum):
|
||||
DEBUG = 10
|
||||
INFO = 20
|
||||
WARN = 30
|
||||
ERROR = 40
|
||||
FATAL = 50
|
||||
NONE = 100
|
||||
|
||||
|
||||
# Level mapping
|
||||
LEVEL_MAP = {
|
||||
LogLevel.DEBUG: logging.DEBUG,
|
||||
LogLevel.INFO: logging.INFO,
|
||||
LogLevel.WARN: logging.WARNING,
|
||||
LogLevel.ERROR: logging.ERROR,
|
||||
LogLevel.FATAL: logging.CRITICAL,
|
||||
LogLevel.NONE: logging.CRITICAL + 1,
|
||||
}
|
||||
|
||||
# ANSI reset sequence (all coloring uses LEVEL_THEME RGB values)
|
||||
ANSI_RESET = "\033[0m"
|
||||
|
||||
# RGB color theme per log level
|
||||
LEVEL_THEME = {
|
||||
"DEBUG": (155, 89, 182), # #9B59B6
|
||||
"INFO": (46, 204, 113), # #2ECC71
|
||||
"WARN": (243, 156, 18), # #F39C12
|
||||
"ERROR": (192, 57, 43), # #C0392B
|
||||
"FATAL": (231, 76, 60), # #E74C3C — brighter red than ERROR
|
||||
}
|
||||
|
||||
# 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",
|
||||
"level_field_width": 5,
|
||||
"source_field_width": 0,
|
||||
}
|
||||
|
||||
|
||||
_WINDOWS_VT_PROCESSING_ENABLED = False
|
||||
|
||||
|
||||
def rotated_log_filename(default_name):
|
||||
"""Keep the .log extension at the end of rotated log filenames."""
|
||||
match = re.fullmatch(r"(.+)\.log\.(\d+)", str(default_name or ""))
|
||||
if not match:
|
||||
return default_name
|
||||
return f"{match.group(1)}.{match.group(2)}.log"
|
||||
|
||||
|
||||
def parse_rotated_log_filename(filename):
|
||||
"""Return the log family and rotation number for the current filename style."""
|
||||
name = os.path.basename(str(filename or ""))
|
||||
match = re.fullmatch(r"(.+)\.(\d+)\.log", name)
|
||||
if match:
|
||||
return match.group(1), int(match.group(2))
|
||||
|
||||
if name.endswith(".log"):
|
||||
return name[:-4], 0
|
||||
return None
|
||||
|
||||
|
||||
def _enable_windows_virtual_terminal_processing():
|
||||
"""Enable ANSI escape sequence handling for classic Windows consoles."""
|
||||
global _WINDOWS_VT_PROCESSING_ENABLED
|
||||
if _WINDOWS_VT_PROCESSING_ENABLED or os.name != "nt":
|
||||
return
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
|
||||
enable_virtual_terminal_processing = 0x0004
|
||||
std_handles = (-11, -12)
|
||||
invalid_handle_value = ctypes.c_void_p(-1).value
|
||||
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
kernel32.GetStdHandle.restype = ctypes.c_void_p
|
||||
enabled_any = False
|
||||
for std_handle in std_handles:
|
||||
handle = kernel32.GetStdHandle(std_handle)
|
||||
if handle in (None, 0, invalid_handle_value):
|
||||
continue
|
||||
|
||||
mode = ctypes.c_uint32()
|
||||
if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
|
||||
continue
|
||||
|
||||
new_mode = mode.value | enable_virtual_terminal_processing
|
||||
if new_mode != mode.value:
|
||||
kernel32.SetConsoleMode(handle, new_mode)
|
||||
|
||||
enabled_any = True
|
||||
|
||||
_WINDOWS_VT_PROCESSING_ENABLED = enabled_any
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
def _get_console_output_stream():
|
||||
"""Return the console stream that preserves ANSI colors under wrapped launchers."""
|
||||
for stream_name in ("__stdout__", "__stderr__", "stdout", "stderr"):
|
||||
stream = getattr(sys, stream_name, None)
|
||||
if stream is None:
|
||||
continue
|
||||
if getattr(stream, "closed", False):
|
||||
continue
|
||||
return stream
|
||||
return None
|
||||
|
||||
|
||||
class SafeRotatingFileHandler(RotatingFileHandler):
|
||||
"""Rotating file handler that disables itself if a launcher breaks its stream."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._disabled_by_stream_error = False
|
||||
super().__init__(*args, **kwargs)
|
||||
self.namer = rotated_log_filename
|
||||
|
||||
def emit(self, record):
|
||||
if self._disabled_by_stream_error:
|
||||
return
|
||||
|
||||
try:
|
||||
if self.stream is not None and self.maxBytes > 0:
|
||||
tell = getattr(self.stream, "tell", None)
|
||||
if not callable(tell):
|
||||
raise OSError(
|
||||
"Log file stream does not support tell(); disabling file logs"
|
||||
)
|
||||
super().emit(record)
|
||||
except Exception:
|
||||
self.handleError(record)
|
||||
|
||||
def handleError(self, record):
|
||||
self._disabled_by_stream_error = True
|
||||
try:
|
||||
self.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class DirectConsoleHandler(logging.Handler):
|
||||
"""Console handler that writes directly to the original console stream."""
|
||||
|
||||
terminator = "\n"
|
||||
|
||||
def __init__(self, stream=None):
|
||||
super().__init__()
|
||||
self.stream = stream
|
||||
|
||||
def emit(self, record):
|
||||
try:
|
||||
stream = self.stream or _get_console_output_stream()
|
||||
if stream is None:
|
||||
return
|
||||
stream.write(self.format(record) + self.terminator)
|
||||
stream.flush()
|
||||
except Exception:
|
||||
self.handleError(record)
|
||||
|
||||
|
||||
class ColoredFormatter(logging.Formatter):
|
||||
"""Formatter that adds colors to console logs"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
fmt=None,
|
||||
datefmt=None,
|
||||
use_colors=True,
|
||||
level_field_width=DEFAULT_CONFIG["level_field_width"],
|
||||
source_field_width=DEFAULT_CONFIG["source_field_width"],
|
||||
include_milliseconds=True,
|
||||
include_brackets=True,
|
||||
):
|
||||
super().__init__(fmt, datefmt)
|
||||
self.use_colors = use_colors
|
||||
self.level_field_width = self._field_width(level_field_width)
|
||||
self.source_field_width = self._field_width(source_field_width)
|
||||
self.include_milliseconds = include_milliseconds
|
||||
self.include_brackets = include_brackets
|
||||
|
||||
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 = self._display_level_name(record.levelname)
|
||||
level = self._format_level(levelname)
|
||||
logger_name = self._display_logger_name(record.name)
|
||||
logger_root, logger_detail = self._split_logger_name(logger_name)
|
||||
source = (
|
||||
f"({logger_detail}:{record.lineno})"
|
||||
if logger_detail
|
||||
else f"({logger_root}:{record.lineno})"
|
||||
)
|
||||
if self.source_field_width:
|
||||
source = source.ljust(self.source_field_width)
|
||||
|
||||
timestamp = self._format_time(record)
|
||||
separator = " "
|
||||
root_label = logger_root
|
||||
if self.include_brackets:
|
||||
timestamp = f"[{timestamp}]"
|
||||
root_label = f"[{root_label}]"
|
||||
if self.use_colors:
|
||||
level = self._color_level_badge(levelname, level)
|
||||
timestamp = self._color_context_badge(timestamp)
|
||||
root_label = self._color_context_badge(root_label)
|
||||
separator = self._color_separator(levelname, separator)
|
||||
message = self._color_message(levelname, message)
|
||||
return f"{level}{timestamp}{separator}{root_label}{separator} {message} {source}"
|
||||
|
||||
return f"{level} {timestamp} {root_label} {message} {source}"
|
||||
|
||||
def _field_width(self, value):
|
||||
try:
|
||||
return max(0, int(value))
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
|
||||
def _display_logger_name(self, name):
|
||||
return str(name or "").removeprefix("azlogs.")
|
||||
|
||||
def _split_logger_name(self, name):
|
||||
parts = str(name or "").split(".", 1)
|
||||
if len(parts) == 1:
|
||||
return parts[0], ""
|
||||
return parts[0], parts[1]
|
||||
|
||||
def _display_level_name(self, levelname):
|
||||
name = str(levelname or "")
|
||||
if name == "WARNING":
|
||||
return "WARN"
|
||||
if name == "CRITICAL":
|
||||
return "FATAL"
|
||||
return name
|
||||
|
||||
def _format_level(self, levelname):
|
||||
if self.include_brackets:
|
||||
width = self.level_field_width + 2
|
||||
return f"[{levelname}]".ljust(width)
|
||||
return levelname.ljust(self.level_field_width)
|
||||
|
||||
def _format_time(self, record):
|
||||
timestamp = time.strftime(
|
||||
self.datefmt or "%H:%M:%S", self.converter(record.created)
|
||||
)
|
||||
if self.include_milliseconds:
|
||||
return f"{timestamp}.{int(record.msecs):03d}"
|
||||
return timestamp
|
||||
|
||||
def _color_level_badge(self, levelname, text):
|
||||
rgb = LEVEL_THEME.get(levelname)
|
||||
if not rgb:
|
||||
return text
|
||||
r, g, b = rgb
|
||||
return f"\033[1;97;48;2;{r};{g};{b}m {text} {ANSI_RESET}"
|
||||
|
||||
def _color_context_badge(self, text):
|
||||
return f"\033[97;48;2;38;63;76m {text} {ANSI_RESET}"
|
||||
|
||||
def _color_separator(self, levelname, text):
|
||||
rgb = LEVEL_THEME.get(levelname)
|
||||
if not rgb:
|
||||
return text
|
||||
r, g, b = rgb
|
||||
return f"\033[48;2;{r};{g};{b}m{text}{ANSI_RESET}"
|
||||
|
||||
def _color_message(self, levelname, text):
|
||||
rgb = LEVEL_THEME.get(levelname)
|
||||
if not rgb:
|
||||
# Fallback: cyan for unknown levels
|
||||
return f"\033[96m{text}{ANSI_RESET}"
|
||||
r, g, b = rgb
|
||||
return f"\033[38;2;{r};{g};{b}m{text}{ANSI_RESET}"
|
||||
|
||||
|
||||
class AzLogsLogger:
|
||||
"""Main logger class for AzLogs"""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__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 = {}
|
||||
self.file_handlers = {}
|
||||
self._lock = threading.RLock()
|
||||
|
||||
# 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)
|
||||
self.reset_loggers()
|
||||
|
||||
if self.config.get("use_colors"):
|
||||
_enable_windows_virtual_terminal_processing()
|
||||
|
||||
# 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 fatal situation, so use print
|
||||
print(
|
||||
f"[FATAL] 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 _sanitize_logger_name(self, value):
|
||||
"""Sanitize logger/module names for safe filesystem usage."""
|
||||
sanitized = re.sub(r'[<>:"/\\|?*\s]+', "_", str(value or "")).strip("._")
|
||||
return sanitized or "default"
|
||||
|
||||
def _root_module_name(self, module):
|
||||
"""Return the top-level logger name used for shared log files."""
|
||||
return str(module or "default").split(".", 1)[0] or "default"
|
||||
|
||||
def _get_log_file(self, module):
|
||||
safe_root = self._sanitize_logger_name(self._root_module_name(module))
|
||||
return os.path.join(self.config["log_dir"], f"azlogs_{safe_root}.log")
|
||||
|
||||
def _get_file_handler(self, module):
|
||||
"""Get or create a shared rotating file handler for the root module."""
|
||||
log_file = self._get_log_file(module)
|
||||
# Fast path without lock
|
||||
if log_file in self.file_handlers:
|
||||
return self.file_handlers[log_file]
|
||||
|
||||
with self._lock:
|
||||
# Double-check after acquiring lock
|
||||
if log_file in self.file_handlers:
|
||||
return self.file_handlers[log_file]
|
||||
|
||||
os.makedirs(self.config["log_dir"], exist_ok=True)
|
||||
file_handler = SafeRotatingFileHandler(
|
||||
log_file,
|
||||
maxBytes=self.config["max_file_size_mb"] * 1024 * 1024,
|
||||
backupCount=self.config["backup_count"],
|
||||
encoding="utf-8",
|
||||
)
|
||||
file_formatter = ColoredFormatter(
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
use_colors=False,
|
||||
source_field_width=self.config["source_field_width"],
|
||||
include_milliseconds=True,
|
||||
)
|
||||
file_handler.setFormatter(file_formatter)
|
||||
self.file_handlers[log_file] = file_handler
|
||||
return file_handler
|
||||
|
||||
def reset_loggers(self):
|
||||
"""Remove configured handlers so new settings are applied cleanly."""
|
||||
with self._lock:
|
||||
handlers_to_close = set()
|
||||
|
||||
for module in self.loggers:
|
||||
configured_logger = logging.getLogger(f"azlogs.{module}")
|
||||
for handler in list(configured_logger.handlers):
|
||||
configured_logger.removeHandler(handler)
|
||||
if isinstance(handler, RotatingFileHandler):
|
||||
handlers_to_close.add(handler)
|
||||
|
||||
for handler in self.file_handlers.values():
|
||||
handlers_to_close.add(handler)
|
||||
|
||||
for handler in handlers_to_close:
|
||||
handler.close()
|
||||
|
||||
self.loggers = {}
|
||||
self.file_handlers = {}
|
||||
return self
|
||||
|
||||
def _get_logger(self, module):
|
||||
"""Get or create a logger for the module"""
|
||||
# Fast path without lock
|
||||
if module in self.loggers:
|
||||
return self.loggers[module]
|
||||
|
||||
with self._lock:
|
||||
# Double-check after acquiring lock
|
||||
if module in self.loggers:
|
||||
return self.loggers[module]
|
||||
|
||||
# Create new logger
|
||||
new_logger = logging.getLogger(f"azlogs.{module}")
|
||||
new_logger.setLevel(logging.DEBUG) # Set lowest level, filtering will be done later
|
||||
new_logger.propagate = False
|
||||
|
||||
# Add console handler
|
||||
if self.config["use_colors"]:
|
||||
_enable_windows_virtual_terminal_processing()
|
||||
|
||||
console_stream = _get_console_output_stream()
|
||||
console_handler = DirectConsoleHandler(console_stream)
|
||||
console_formatter = ColoredFormatter(
|
||||
fmt="[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s",
|
||||
datefmt=self.config["timestamp_format"],
|
||||
use_colors=self.config["use_colors"],
|
||||
level_field_width=self.config["level_field_width"],
|
||||
source_field_width=self.config["source_field_width"],
|
||||
include_brackets=False,
|
||||
)
|
||||
console_handler.setFormatter(console_formatter)
|
||||
new_logger.addHandler(console_handler)
|
||||
|
||||
# Add file handler if file logging is enabled
|
||||
if self.config["log_to_file"]:
|
||||
try:
|
||||
file_handler = self._get_file_handler(module)
|
||||
new_logger.addHandler(file_handler)
|
||||
except OSError as e:
|
||||
self.config["log_to_file"] = False
|
||||
new_logger.warning(
|
||||
"AzLogs: disabling file logging after handler setup failed for %s: %s",
|
||||
self._get_log_file(module),
|
||||
e,
|
||||
)
|
||||
|
||||
self.loggers[module] = new_logger
|
||||
return new_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)
|
||||
|
||||
# Extract known kwargs
|
||||
exc_info = kwargs.get("exc_info")
|
||||
stacklevel = kwargs.get("stacklevel", 1)
|
||||
extra = kwargs.get("extra")
|
||||
|
||||
# Map LogLevel to logging level
|
||||
log_level = LEVEL_MAP.get(level, logging.INFO)
|
||||
|
||||
# Write log
|
||||
logger.log(log_level, message, exc_info=exc_info, stacklevel=stacklevel, extra=extra)
|
||||
|
||||
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, **kwargs):
|
||||
"""Log exception at ERROR level"""
|
||||
kwargs["exc_info"] = True
|
||||
self.log(module, LogLevel.ERROR, *args, **kwargs)
|
||||
|
||||
def fatal(self, module, *args, **kwargs):
|
||||
"""Log at FATAL level"""
|
||||
self.log(module, LogLevel.FATAL, *args, **kwargs)
|
||||
|
||||
|
||||
# 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, **kwargs):
|
||||
"""Log exception at ERROR level"""
|
||||
kwargs["exc_info"] = True
|
||||
logger.log(module, LogLevel.ERROR, *args, **kwargs)
|
||||
|
||||
|
||||
def fatal(module, *args, **kwargs):
|
||||
"""Log at FATAL level"""
|
||||
logger.log(module, LogLevel.FATAL, *args, **kwargs)
|
||||
|
||||
|
||||
# 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.reset_loggers()
|
||||
return logger
|
||||
+54
-318
@@ -1,323 +1,59 @@
|
||||
"""
|
||||
Logger - Centralny system logowania dla ComfyUI-LayerForge (Python)
|
||||
"""Backward-compatible exports for the LayerForge logging system.
|
||||
|
||||
Funkcje:
|
||||
- Różne poziomy logowania (DEBUG, INFO, WARN, ERROR)
|
||||
- Możliwość włączania/wyłączania logów globalnie lub per moduł
|
||||
- Kolorowe logi w konsoli
|
||||
- Rotacja plików logów
|
||||
- Konfiguracja przez zmienne środowiskowe
|
||||
The implementation lives in :mod:`python.log_system`, matching the Model
|
||||
Resolver layout. Existing LayerForge imports can continue using
|
||||
``python.logger`` while all callers share the same logger instance.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import datetime
|
||||
from enum import IntEnum
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import traceback
|
||||
from .log_system.logger import (
|
||||
ANSI_RESET,
|
||||
DEFAULT_CONFIG,
|
||||
LEVEL_MAP,
|
||||
LEVEL_THEME,
|
||||
AzLogsLogger,
|
||||
ColoredFormatter,
|
||||
DirectConsoleHandler,
|
||||
LogLevel,
|
||||
SafeRotatingFileHandler,
|
||||
logger,
|
||||
parse_rotated_log_filename,
|
||||
rotated_log_filename,
|
||||
set_debug,
|
||||
set_file_logging,
|
||||
)
|
||||
from .log_system.log_funcs import (
|
||||
create_module_logger,
|
||||
debug,
|
||||
error,
|
||||
exception,
|
||||
fatal,
|
||||
info,
|
||||
warn,
|
||||
)
|
||||
|
||||
# Poziomy logowania
|
||||
class LogLevel(IntEnum):
|
||||
DEBUG = 10
|
||||
INFO = 20
|
||||
WARN = 30
|
||||
ERROR = 40
|
||||
NONE = 100
|
||||
LayerForgeLogger = AzLogsLogger
|
||||
|
||||
# Mapowanie poziomów logowania
|
||||
LEVEL_MAP = {
|
||||
LogLevel.DEBUG: logging.DEBUG,
|
||||
LogLevel.INFO: logging.INFO,
|
||||
LogLevel.WARN: logging.WARNING,
|
||||
LogLevel.ERROR: logging.ERROR,
|
||||
LogLevel.NONE: logging.CRITICAL + 1
|
||||
}
|
||||
|
||||
# Kolory ANSI dla różnych poziomów logowania
|
||||
COLORS = {
|
||||
LogLevel.DEBUG: '\033[90m', # Szary
|
||||
LogLevel.INFO: '\033[94m', # Niebieski
|
||||
LogLevel.WARN: '\033[93m', # Żółty
|
||||
LogLevel.ERROR: '\033[91m', # Czerwony
|
||||
'RESET': '\033[0m' # Reset
|
||||
}
|
||||
|
||||
# Konfiguracja domyślna
|
||||
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 dodający kolory do logów w konsoli"""
|
||||
|
||||
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 LayerForgeLogger:
|
||||
"""Główna klasa loggera dla LayerForge"""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(LayerForgeLogger, 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 = {}
|
||||
|
||||
# Załaduj konfigurację ze zmiennych środowiskowych
|
||||
self._load_config_from_env()
|
||||
|
||||
self._initialized = True
|
||||
|
||||
def _load_config_from_env(self):
|
||||
"""Załaduj konfigurację ze zmiennych środowiskowych"""
|
||||
|
||||
# Poziom globalny
|
||||
if 'LAYERFORGE_LOG_LEVEL' in os.environ:
|
||||
level_name = os.environ['LAYERFORGE_LOG_LEVEL'].upper()
|
||||
if hasattr(LogLevel, level_name):
|
||||
self.config['global_level'] = getattr(LogLevel, level_name)
|
||||
|
||||
# Ustawienia modułów
|
||||
if 'LAYERFORGE_MODULE_LEVELS' in os.environ:
|
||||
try:
|
||||
module_settings = json.loads(os.environ['LAYERFORGE_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
|
||||
|
||||
# Inne ustawienia
|
||||
if 'LAYERFORGE_USE_COLORS' in os.environ:
|
||||
self.config['use_colors'] = os.environ['LAYERFORGE_USE_COLORS'].lower() == 'true'
|
||||
|
||||
if 'LAYERFORGE_LOG_TO_FILE' in os.environ:
|
||||
self.config['log_to_file'] = os.environ['LAYERFORGE_LOG_TO_FILE'].lower() == 'true'
|
||||
|
||||
if 'LAYERFORGE_LOG_DIR' in os.environ:
|
||||
self.config['log_dir'] = os.environ['LAYERFORGE_LOG_DIR']
|
||||
|
||||
if 'LAYERFORGE_MAX_FILE_SIZE_MB' in os.environ:
|
||||
try:
|
||||
self.config['max_file_size_mb'] = int(os.environ['LAYERFORGE_MAX_FILE_SIZE_MB'])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if 'LAYERFORGE_BACKUP_COUNT' in os.environ:
|
||||
try:
|
||||
self.config['backup_count'] = int(os.environ['LAYERFORGE_BACKUP_COUNT'])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def configure(self, config):
|
||||
"""Konfiguracja loggera"""
|
||||
self.config.update(config)
|
||||
|
||||
# Jeśli włączono logowanie do pliku, upewnij się, że katalog istnieje
|
||||
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:
|
||||
# To jest sytuacja krytyczna, więc użyjmy print
|
||||
print(f"[CRITICAL] Could not create log directory: {self.config['log_dir']}. Error: {e}")
|
||||
traceback.print_exc()
|
||||
# Wyłącz logowanie do pliku, aby uniknąć dalszych błędów
|
||||
self.config['log_to_file'] = False
|
||||
|
||||
return self
|
||||
|
||||
def set_enabled(self, enabled):
|
||||
"""Włącz/wyłącz logger globalnie"""
|
||||
self.enabled = enabled
|
||||
return self
|
||||
|
||||
def set_global_level(self, level):
|
||||
"""Ustaw globalny poziom logowania"""
|
||||
self.config['global_level'] = level
|
||||
return self
|
||||
|
||||
def set_module_level(self, module, level):
|
||||
"""Ustaw poziom logowania dla konkretnego modułu"""
|
||||
self.config['module_settings'][module] = level
|
||||
return self
|
||||
|
||||
def is_level_enabled(self, module, level):
|
||||
"""Sprawdź, czy dany poziom logowania jest aktywny dla modułu"""
|
||||
if not self.enabled:
|
||||
return False
|
||||
|
||||
# Ustal efektywny poziom logowania, biorąc pod uwagę ustawienia modułu i globalne
|
||||
effective_level = self.config['module_settings'].get(module, self.config['global_level'])
|
||||
|
||||
# Jeśli efektywny poziom to NONE, logowanie jest całkowicie wyłączone
|
||||
if effective_level == LogLevel.NONE:
|
||||
return False
|
||||
|
||||
# W przeciwnym razie sprawdź, czy poziom loga jest wystarczająco wysoki
|
||||
return level >= effective_level
|
||||
|
||||
def _get_logger(self, module):
|
||||
"""Pobierz lub utwórz logger dla modułu"""
|
||||
if module in self.loggers:
|
||||
return self.loggers[module]
|
||||
|
||||
# Utwórz nowy logger
|
||||
logger = logging.getLogger(f"layerforge.{module}")
|
||||
logger.setLevel(logging.DEBUG) # Ustaw najniższy poziom, filtrowanie będzie później
|
||||
logger.propagate = False
|
||||
|
||||
# Dodaj handler dla konsoli
|
||||
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)
|
||||
|
||||
# Dodaj handler dla pliku, jeśli włączono logowanie do pliku
|
||||
if self.config['log_to_file']:
|
||||
log_file = os.path.join(self.config['log_dir'], f"layerforge_{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):
|
||||
"""Zapisz log"""
|
||||
if not self.is_level_enabled(module, level):
|
||||
return
|
||||
|
||||
logger = self._get_logger(module)
|
||||
|
||||
# Konwertuj argumenty na string
|
||||
message = " ".join(str(arg) for arg in args)
|
||||
|
||||
# Dodaj informacje o wyjątku, jeśli podano
|
||||
exc_info = kwargs.get('exc_info', None)
|
||||
|
||||
# Mapuj poziom LogLevel na poziom logging
|
||||
log_level = LEVEL_MAP.get(level, logging.INFO)
|
||||
|
||||
# Zapisz log
|
||||
logger.log(log_level, message, exc_info=exc_info)
|
||||
|
||||
def debug(self, module, *args, **kwargs):
|
||||
"""Log na poziomie DEBUG"""
|
||||
self.log(module, LogLevel.DEBUG, *args, **kwargs)
|
||||
|
||||
def info(self, module, *args, **kwargs):
|
||||
"""Log na poziomie INFO"""
|
||||
self.log(module, LogLevel.INFO, *args, **kwargs)
|
||||
|
||||
def warn(self, module, *args, **kwargs):
|
||||
"""Log na poziomie WARN"""
|
||||
self.log(module, LogLevel.WARN, *args, **kwargs)
|
||||
|
||||
def error(self, module, *args, **kwargs):
|
||||
"""Log na poziomie ERROR"""
|
||||
self.log(module, LogLevel.ERROR, *args, **kwargs)
|
||||
|
||||
def exception(self, module, *args):
|
||||
"""Log wyjątku na poziomie ERROR"""
|
||||
self.log(module, LogLevel.ERROR, *args, exc_info=True)
|
||||
|
||||
# Singleton
|
||||
logger = LayerForgeLogger()
|
||||
|
||||
# Funkcje pomocnicze
|
||||
def debug(module, *args, **kwargs):
|
||||
"""Log na poziomie DEBUG"""
|
||||
logger.debug(module, *args, **kwargs)
|
||||
|
||||
def info(module, *args, **kwargs):
|
||||
"""Log na poziomie INFO"""
|
||||
logger.info(module, *args, **kwargs)
|
||||
|
||||
def warn(module, *args, **kwargs):
|
||||
"""Log na poziomie WARN"""
|
||||
logger.warn(module, *args, **kwargs)
|
||||
|
||||
def error(module, *args, **kwargs):
|
||||
"""Log na poziomie ERROR"""
|
||||
logger.error(module, *args, **kwargs)
|
||||
|
||||
def exception(module, *args):
|
||||
"""Log wyjątku na poziomie ERROR"""
|
||||
logger.exception(module, *args)
|
||||
|
||||
# Funkcja do szybkiego włączania/wyłączania debugowania
|
||||
def set_debug(enabled=True):
|
||||
"""Włącz/wyłącz debugowanie globalnie"""
|
||||
if enabled:
|
||||
logger.set_global_level(LogLevel.DEBUG)
|
||||
else:
|
||||
logger.set_global_level(LogLevel.INFO)
|
||||
return logger
|
||||
|
||||
# Funkcja do włączania/wyłączania logowania do pliku
|
||||
def set_file_logging(enabled=True, log_dir=None):
|
||||
"""Włącz/wyłącz logowanie do pliku"""
|
||||
logger.config['log_to_file'] = enabled
|
||||
if log_dir:
|
||||
logger.config['log_dir'] = log_dir
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
# Zresetuj loggery, aby zastosować nowe ustawienia
|
||||
logger.loggers = {}
|
||||
return logger
|
||||
__all__ = [
|
||||
"ANSI_RESET",
|
||||
"DEFAULT_CONFIG",
|
||||
"LEVEL_MAP",
|
||||
"LEVEL_THEME",
|
||||
"AzLogsLogger",
|
||||
"ColoredFormatter",
|
||||
"DirectConsoleHandler",
|
||||
"LayerForgeLogger",
|
||||
"LogLevel",
|
||||
"SafeRotatingFileHandler",
|
||||
"create_module_logger",
|
||||
"debug",
|
||||
"error",
|
||||
"exception",
|
||||
"fatal",
|
||||
"info",
|
||||
"logger",
|
||||
"parse_rotated_log_filename",
|
||||
"rotated_log_filename",
|
||||
"set_debug",
|
||||
"set_file_logging",
|
||||
"warn",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user