diff --git a/__init__.py b/__init__.py index 959d635..a33946d 100644 --- a/__init__.py +++ b/__init__.py @@ -7,11 +7,9 @@ """ import asyncio -import logging from .core.log_system.log_funcs import ( - log_debug, + create_module_logger, log_info, - log_warn, log_error, log_exception, ) @@ -31,7 +29,7 @@ class ModelLinkerExtension: def __init__(self): self.routes_setup = False - self.logger = logging.getLogger(__name__) + self.logger = create_module_logger(__name__) self.analysis_progress = {} def initialize(self): @@ -173,16 +171,13 @@ class ModelLinkerExtension: is_lora = missing.get("is_lora_v2") exists = missing.get("exists") name = missing.get("name") or missing.get("original_path", "") - # Log for debugging - import logging - - logging.getLogger(__name__).debug( + self.logger.debug( f"Filtering: {name} is_lora_v2={is_lora} exists={exists}" ) # Skip LoraManager lorAs that already exist locally if is_lora and exists: - logging.getLogger(__name__).info( + self.logger.info( f"Filtered out LoraManager lora: {name}" ) continue diff --git a/core/log_system/log_funcs.py b/core/log_system/log_funcs.py index fbfdff1..d8ea8f2 100644 --- a/core/log_system/log_funcs.py +++ b/core/log_system/log_funcs.py @@ -6,10 +6,15 @@ """ import os +import sys import traceback import logging _logger = logging.getLogger(__name__) +LOG_MODULE_NAME = None +_default_module_name = __name__ +_initialized = False +_project_root = None try: from .logger import logger, LogLevel, debug, info, warn, error, exception @@ -26,13 +31,14 @@ try: current = parent return os.path.dirname(os.path.dirname(os.path.abspath(start_path))) - _module_name = ( + _project_root = _find_project_root(__file__) + _default_module_name = ( LOG_MODULE_NAME if LOG_MODULE_NAME is not None - else os.path.basename(_find_project_root(__file__)) + else os.path.basename(_project_root) ) - logger.set_module_level(_module_name, LogLevel[LOG_LEVEL]) + logger.set_global_level(LogLevel[LOG_LEVEL]) logger.configure( { @@ -47,37 +53,189 @@ except ImportError as e: _logger.error(f"Failed to initialize logger: {e}") -def log_debug(*args, **kwargs): - if _initialized: - debug(_module_name, *args, **kwargs) +def _normalize_module_name(module_name): + module_name = str(module_name or "").strip() + if not module_name: + return _default_module_name + + normalized_path = module_name.replace("/", os.sep).replace("\\", os.sep) + + if os.path.isabs(normalized_path): + path_no_ext = os.path.splitext(normalized_path)[0] + if _project_root: + try: + relative_path = os.path.relpath(path_no_ext, _project_root) + except ValueError: + relative_path = os.path.basename(path_no_ext) + else: + relative_path = os.path.basename(path_no_ext) + + if relative_path.endswith("__init__"): + relative_path = os.path.dirname(relative_path) or os.path.basename( + os.path.dirname(path_no_ext) + ) + + cleaned = relative_path.replace(os.sep, ".").strip(".") + return cleaned or _default_module_name + + if module_name == "__main__": + return _default_module_name + + parts = [part for part in module_name.split(".") if part] + if not parts: + return _default_module_name + + if parts[-1] == "__init__": + parts = parts[:-1] + + if LOG_MODULE_NAME and parts[0] == LOG_MODULE_NAME: + parts = parts[1:] + + if "custom_nodes" in parts: + parts = parts[parts.index("custom_nodes") + 2 :] + + if "comfyui-model-linker" in parts: + parts = parts[parts.index("comfyui-model-linker") + 1 :] + + cleaned = ".".join(parts).strip(".") + return cleaned or _default_module_name + + +def _normalize_module_path(file_path): + if not file_path: + return _default_module_name + + normalized_path = os.path.normpath(file_path) + path_no_ext = os.path.splitext(normalized_path)[0] + + if _project_root: + try: + relative_path = os.path.relpath(path_no_ext, _project_root) + except ValueError: + relative_path = os.path.basename(path_no_ext) else: - print("[DEBUG]", *args) + relative_path = os.path.basename(path_no_ext) + + parts = [part for part in relative_path.split(os.sep) if part] + if not parts: + return _default_module_name + + if parts[-1] == "__init__": + parts = parts[:-1] + + package_name = LOG_MODULE_NAME or os.path.basename(_project_root or "") + if package_name: + parts.insert(0, package_name) + + cleaned = ".".join(parts).strip(".") + return cleaned or _default_module_name + + +def _resolve_module_name(stack_depth=2): + if not _initialized: + return _normalize_module_name(LOG_MODULE_NAME or __name__) + + try: + frame = sys._getframe(stack_depth) + except ValueError: + return _normalize_module_name(_default_module_name) + + file_path = frame.f_globals.get("__file__") + if file_path: + return _normalize_module_path(file_path) + + module_name = frame.f_globals.get("__name__") + return _normalize_module_name(module_name) + + +class ModuleLogger: + def __init__(self, module_name): + self.module_name = module_name + + def debug(self, *args, **kwargs): + if _initialized: + kwargs.setdefault("stacklevel", 4) + debug(self.module_name, *args, **kwargs) + else: + print(f"[DEBUG] [{self.module_name}]", *args) + + def info(self, *args, **kwargs): + if _initialized: + kwargs.setdefault("stacklevel", 4) + info(self.module_name, *args, **kwargs) + else: + print(f"[INFO] [{self.module_name}]", *args) + + def warning(self, *args, **kwargs): + if _initialized: + kwargs.setdefault("stacklevel", 4) + 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.setdefault("stacklevel", 4) + error(self.module_name, *args, **kwargs) + else: + print(f"[ERROR] [{self.module_name}]", *args) + + def exception(self, *args): + if _initialized: + exception(self.module_name, *args, stacklevel=4) + else: + print(f"[ERROR] [{self.module_name}]", *args) + traceback.print_exc() + + +def create_module_logger(module_name=None): + resolved_name = _normalize_module_name(module_name or _resolve_module_name(stack_depth=2)) + return ModuleLogger(resolved_name) + + +def log_debug(*args, **kwargs): + module_name = _resolve_module_name(stack_depth=2) + if _initialized: + kwargs.setdefault("stacklevel", 3) + debug(module_name, *args, **kwargs) + else: + print(f"[DEBUG] [{module_name}]", *args) def log_info(*args, **kwargs): + module_name = _resolve_module_name(stack_depth=2) if _initialized: - info(_module_name, *args, **kwargs) + kwargs.setdefault("stacklevel", 3) + info(module_name, *args, **kwargs) else: - print("[INFO]", *args) + print(f"[INFO] [{module_name}]", *args) def log_warn(*args, **kwargs): + module_name = _resolve_module_name(stack_depth=2) if _initialized: - warn(_module_name, *args, **kwargs) + kwargs.setdefault("stacklevel", 3) + warn(module_name, *args, **kwargs) else: - print("[WARN]", *args) + print(f"[WARN] [{module_name}]", *args) def log_error(*args, **kwargs): + module_name = _resolve_module_name(stack_depth=2) if _initialized: - error(_module_name, *args, **kwargs) + kwargs.setdefault("stacklevel", 3) + error(module_name, *args, **kwargs) else: - print("[ERROR]", *args) + print(f"[ERROR] [{module_name}]", *args) def log_exception(*args): + module_name = _resolve_module_name(stack_depth=2) if _initialized: - exception(_module_name, *args) + exception(module_name, *args, stacklevel=3) else: - print("[ERROR]", *args) + print(f"[ERROR] [{module_name}]", *args) traceback.print_exc() diff --git a/core/log_system/logger.py b/core/log_system/logger.py index a6da0b1..a080ba6 100644 --- a/core/log_system/logger.py +++ b/core/log_system/logger.py @@ -15,8 +15,8 @@ Features: import os import sys import json +import re import logging -import datetime from enum import IntEnum from logging.handlers import RotatingFileHandler import traceback @@ -78,8 +78,11 @@ class ColoredFormatter(logging.Formatter): levelname = record.levelname # Build the log prefix - prefix = "[{}] [{}] [{}]".format( - self.formatTime(record, self.datefmt), record.name, record.levelname + prefix = "[{}] [{}] [{}:{}]".format( + self.formatTime(record, self.datefmt), + record.levelname, + record.name, + record.lineno, ) # Apply color and bold styling to the prefix @@ -216,6 +219,11 @@ class AzLogsLogger: # 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 _get_logger(self, module): """Get or create a logger for the module""" if module in self.loggers: @@ -238,19 +246,28 @@ class AzLogsLogger: # 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) + safe_module = self._sanitize_logger_name(module) + log_file = os.path.join(self.config["log_dir"], f"azlogs_{safe_module}.log") + try: + 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) + except OSError as e: + self.config["log_to_file"] = False + logger.warning( + "AzLogs: disabling file logging after handler setup failed for %s: %s", + log_file, + e, + ) self.loggers[module] = logger return logger @@ -267,12 +284,13 @@ class AzLogsLogger: # Add exception info if provided exc_info = kwargs.get("exc_info", None) + stacklevel = kwargs.get("stacklevel", 1) # Map LogLevel to logging level log_level = LEVEL_MAP.get(level, logging.INFO) # Write log - logger.log(log_level, message, exc_info=exc_info) + logger.log(log_level, message, exc_info=exc_info, stacklevel=stacklevel) def debug(self, module, *args, **kwargs): """Log at DEBUG level""" @@ -290,9 +308,10 @@ class AzLogsLogger: """Log at ERROR level""" self.log(module, LogLevel.ERROR, *args, **kwargs) - def exception(self, module, *args): + def exception(self, module, *args, **kwargs): """Log exception at ERROR level""" - self.log(module, LogLevel.ERROR, *args, exc_info=True) + kwargs["exc_info"] = True + self.log(module, LogLevel.ERROR, *args, **kwargs) # Singleton @@ -320,9 +339,10 @@ def error(module, *args, **kwargs): logger.log(module, LogLevel.ERROR, *args, **kwargs) -def exception(module, *args): +def exception(module, *args, **kwargs): """Log exception at ERROR level""" - logger.log(module, LogLevel.ERROR, *args, exc_info=True) + kwargs["exc_info"] = True + logger.log(module, LogLevel.ERROR, *args, **kwargs) # Function to quickly enable/disable debugging diff --git a/web/log_system/logger.js b/web/log_system/logger.js index a068a82..d6c7ddd 100644 --- a/web/log_system/logger.js +++ b/web/log_system/logger.js @@ -73,12 +73,19 @@ const LEVEL_NAMES = { [LogLevel.WARN]: 'WARN', [LogLevel.ERROR]: 'ERROR', }; +const CONSOLE_METHODS = { + [LogLevel.DEBUG]: 'debug', + [LogLevel.INFO]: 'info', + [LogLevel.WARN]: 'warn', + [LogLevel.ERROR]: 'error', +}; class Logger { constructor() { this.config = { ...DEFAULT_CONFIG }; this.logs = []; this.enabled = true; this.loadConfig(); + this.loadLogs(); } /** * Configure the logger @@ -178,12 +185,62 @@ class Logger { printToConsole(logData) { const { timestamp, module, level, levelName, args } = logData; const prefix = `[${timestamp}] [${module}] [${levelName}]`; - if (this.config.useColors && typeof console.log === 'function') { + const consoleMethod = CONSOLE_METHODS[level] || 'log'; + const consoleFn = typeof console[consoleMethod] === 'function' + ? console[consoleMethod].bind(console) + : console.log.bind(console); + if (this.config.useColors && typeof consoleFn === 'function') { const color = COLORS[level] || '#000000'; - console.log(`%c${prefix}`, `color: ${color}; font-weight: bold;`, ...args); + consoleFn(`%c${prefix}`, `color: ${color}; font-weight: bold;`, ...args); return; } - console.log(prefix, ...args); + consoleFn(prefix, ...args); + } + serializeLogEntry(log) { + return { + timestamp: log.timestamp, + module: log.module, + level: log.level, + levelName: log.levelName, + args: log.args.map((arg) => { + if (typeof arg === 'object' && arg !== null) { + try { + return JSON.parse(JSON.stringify(arg)); + } + catch (e) { + return String(arg); + } + } + return arg; + }), + time: log.time instanceof Date ? log.time.toISOString() : log.time + }; + } + deserializeLogEntry(log) { + if (!log || typeof log !== 'object') { + return null; + } + if ('timestamp' in log && 'module' in log && 'level' in log) { + return { + timestamp: log.timestamp, + module: log.module, + level: log.level, + levelName: log.levelName || LEVEL_NAMES[log.level] || 'INFO', + args: Array.isArray(log.args) ? log.args : [], + time: log.time ? new Date(log.time) : new Date() + }; + } + if ('t' in log && 'm' in log && 'l' in log) { + return { + timestamp: log.t, + module: log.m, + level: log.l, + levelName: LEVEL_NAMES[log.l] || 'INFO', + args: Array.isArray(log.a) ? log.a : [], + time: new Date() + }; + } + return null; } /** * Save logs to localStorage @@ -191,23 +248,8 @@ class Logger { saveLogs() { if (typeof localStorage !== 'undefined' && this.config.saveToStorage) { try { - const simplifiedLogs = this.logs.map((log) => ({ - t: log.timestamp, - m: log.module, - l: log.level, - a: log.args.map((arg) => { - if (typeof arg === 'object') { - try { - return JSON.stringify(arg); - } - catch (e) { - return String(arg); - } - } - return arg; - }) - })); - localStorage.setItem(this.config.storageKey, JSON.stringify(simplifiedLogs)); + const storedLogs = this.logs.map((log) => this.serializeLogEntry(log)); + localStorage.setItem(this.config.storageKey, JSON.stringify(storedLogs)); } catch (e) { console.error('Failed to save logs to localStorage:', e); @@ -222,7 +264,9 @@ class Logger { try { const storedLogs = localStorage.getItem(this.config.storageKey); if (storedLogs) { - this.logs = JSON.parse(storedLogs); + this.logs = JSON.parse(storedLogs) + .map((log) => this.deserializeLogEntry(log)) + .filter(Boolean); } } catch (e) {