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:
+4
-9
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user