feat: migrate Model Resolver logging system
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user