refactor: overhaul logging system across backend and web frontend

- Switch __init__.py to use create_module_logger(); remove inline logging.getLogger calls
- Rewrite log_funcs.py: introduce module-name normalization (_normalize_module_name/_normalize_module_path/_resolve_module_name), ModuleLogger class, and global log-level switching (set_global_level replaces per-module set_module_level)
- Enhance logger.py: show [name:lineno] in console prefix, sanitize log file names for safe filesystem paths, wrap RotatingFileHandler setup with OSError handling to fall back to console-only logging, pass stacklevel through log() and propagate kwargs to exception()
- Update web/log_system/logger.js: dispatch to level-specific console methods (debug/info/warn/error) via CONSOLE_METHODS map; replace truncated t/m/l/a localStorage schema with serializeLogEntry/deserializeLogEntry (backward-compatible with short keys); load saved logs on construction via loadLogs()
This commit is contained in:
Dariusz L
2026-05-17 03:38:23 +02:00
parent 967afb609c
commit eccf6e9d1e
4 changed files with 283 additions and 66 deletions
+4 -9
View File
@@ -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
+173 -15
View File
@@ -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()
+41 -21
View File
@@ -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
+65 -21
View File
@@ -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) {