refactor(logging): AzLogs v1.4.1 with dynamic storage and utilities

Enhance logging system with configurable module names and dynamic
localStorage keys to avoid conflicts between multiple plugin instances.

- Add LOG_MODULE_NAME and DEFAULT_LOGGER_NAME to web/log_system/config.js
- Update logger.js to use dynamic storage keys based on module name
- Change window property from ResolutionMasterLogger to dynamic key
- Add PyDoc metadata to core/log_system/config.py
- Add JSDoc metadata blocks to all web log_system files

Reorganize web utilities:
- Move css-loader.js from web/ to web/utils/
- Add ErrorHandler.js for centralized error handling
- Add ResourceManager.js for stylesheet and template loading

 rename: web/css-loader.js => web/utils/css-loader.js
This commit is contained in:
Dariusz L
2026-05-11 02:41:30 +02:00
parent bd7e40209f
commit a288b26b66
7 changed files with 427 additions and 16 deletions
+6
View File
@@ -1,3 +1,9 @@
"""
@author: Azornes
@title: AzLogs
@version: 1.4.1
@description: Logging Configuration
"""
# Log level for development.
# Possible values: 'DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE'
LOG_LEVEL = "DEBUG"
+6
View File
@@ -1,3 +1,9 @@
/**
@author: Azornes
@title: AzLogs
@version: 1.4.1
@description: Logging Initializator
*/
/**
* LoggerUtils - Centralization of logger initialization
* Eliminates repetitive logger initialization code in each module
+12
View File
@@ -1,3 +1,15 @@
/**
author: Azornes
title: AzLogs
version: 1.4.1
description: Logging Configuration
*/
// Log level for development.
// Possible values: 'DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE'
export const LOG_LEVEL = 'DEBUG';
// Logger module name. Set to null to use the default project name.
export const LOG_MODULE_NAME = null;
// Default project/logger name used when LOG_MODULE_NAME is not set.
export const DEFAULT_LOGGER_NAME = 'az-logs';
+37 -14
View File
@@ -1,13 +1,20 @@
/**
* Logger - Central logging system for ComfyUI-ResolutionMaster
*
* Features:
* - Multiple log levels (DEBUG, INFO, WARN, ERROR)
* - Ability to enable/disable logging globally or per module
* - Colorful logs in the console
* - Ability to save logs to localStorage
* - Ability to export logs
*/
author: Azornes
title: AzLogs
version: 1.4.1
description: Logging Setup - Central logging system
Features:
Logger - Central logging system for comfyui-model-linker
- Multiple log levels (DEBUG, INFO, WARN, ERROR)
- Ability to enable/disable logging globally or per module
- Colorful logs in the console
- Ability to save logs to localStorage
- Ability to export logs
*/
import { DEFAULT_LOGGER_NAME, LOG_MODULE_NAME } from './config.js';
function padStart(str, targetLength, padString) {
targetLength = targetLength >> 0;
padString = String(padString || ' ');
@@ -22,6 +29,22 @@ function padStart(str, targetLength, padString) {
return padString.slice(0, targetLength) + String(str);
}
}
function sanitizeKeyPart(value) {
return String(value).replace(/[^a-zA-Z0-9_-]+/g, '_');
}
function toPascalCase(value) {
return String(value)
.split(/[^a-zA-Z0-9]+/)
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join('');
}
const LOGGER_NAME = LOG_MODULE_NAME || DEFAULT_LOGGER_NAME;
const STORAGE_PREFIX = sanitizeKeyPart(LOGGER_NAME);
const LOGGER_CONFIG_KEY = `${STORAGE_PREFIX}_logger_config`;
const LOGGER_STORAGE_KEY = `${STORAGE_PREFIX}_logs`;
const LOGGER_EXPORT_PREFIX = STORAGE_PREFIX;
const WINDOW_LOGGER_KEY = `${toPascalCase(LOGGER_NAME)}Logger`;
export const LogLevel = {
DEBUG: 0,
INFO: 1,
@@ -36,7 +59,7 @@ const DEFAULT_CONFIG = {
saveToStorage: false,
maxStoredLogs: 1000,
timestampFormat: 'HH:mm:ss',
storageKey: 'ResolutionMaster_logs'
storageKey: LOGGER_STORAGE_KEY
};
const COLORS = {
[LogLevel.DEBUG]: '#9e9e9e',
@@ -213,7 +236,7 @@ class Logger {
saveConfig() {
if (typeof localStorage !== 'undefined') {
try {
localStorage.setItem('ResolutionMaster_logger_config', JSON.stringify(this.config));
localStorage.setItem(LOGGER_CONFIG_KEY, JSON.stringify(this.config));
}
catch (e) {
console.error('Failed to save logger config to localStorage:', e);
@@ -226,7 +249,7 @@ class Logger {
loadConfig() {
if (typeof localStorage !== 'undefined') {
try {
const storedConfig = localStorage.getItem('ResolutionMaster_logger_config');
const storedConfig = localStorage.getItem(LOGGER_CONFIG_KEY);
if (storedConfig) {
this.config = { ...this.config, ...JSON.parse(storedConfig) };
}
@@ -272,7 +295,7 @@ class Logger {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `ResolutionMaster_logs_${new Date().toISOString().replace(/[:.]/g, '-')}.${extension}`;
a.download = `${LOGGER_EXPORT_PREFIX}_logs_${new Date().toISOString().replace(/[:.]/g, '-')}.${extension}`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
@@ -317,6 +340,6 @@ export const info = (module, ...args) => logger.info(module, ...args);
export const warn = (module, ...args) => logger.warn(module, ...args);
export const error = (module, ...args) => logger.error(module, ...args);
if (typeof window !== 'undefined') {
window.ResolutionMasterLogger = logger;
window[WINDOW_LOGGER_KEY] = logger;
}
export default logger;
+313
View File
@@ -0,0 +1,313 @@
/**
* ErrorHandler - Centralna obsługa błędów
* Eliminuje powtarzalne wzorce obsługi błędów w całym projekcie
*/
import { createModuleLogger } from "../log_system/LoggerUtils.js";
const log = createModuleLogger('ErrorHandler');
/**
* Typy błędów w aplikacji
*/
export const ErrorTypes = {
VALIDATION: 'VALIDATION_ERROR',
NETWORK: 'NETWORK_ERROR',
FILE_IO: 'FILE_IO_ERROR',
CANVAS: 'CANVAS_ERROR',
IMAGE_PROCESSING: 'IMAGE_PROCESSING_ERROR',
STATE_MANAGEMENT: 'STATE_MANAGEMENT_ERROR',
USER_INPUT: 'USER_INPUT_ERROR',
SYSTEM: 'SYSTEM_ERROR'
};
/**
* Klasa błędu aplikacji z dodatkowymi informacjami
*/
export class AppError extends Error {
constructor(message, type = ErrorTypes.SYSTEM, details = null, originalError = null) {
super(message);
this.name = 'AppError';
this.type = type;
this.details = details;
this.originalError = originalError;
this.timestamp = new Date().toISOString();
if (Error.captureStackTrace) {
Error.captureStackTrace(this, AppError);
}
}
}
/**
* Handler błędów z automatycznym logowaniem i kategoryzacją
*/
export class ErrorHandler {
constructor() {
this.errorCounts = new Map();
this.errorHistory = [];
this.maxHistorySize = 100;
}
/**
* Obsługuje błąd z automatycznym logowaniem
* @param {Error | AppError | string} error - Błąd do obsłużenia
* @param {string} context - Kontekst wystąpienia błędu
* @param {object} additionalInfo - Dodatkowe informacje
* @returns {AppError} Znormalizowany błąd
*/
handle(error, context = 'Unknown', additionalInfo = {}) {
const normalizedError = this.normalizeError(error, context, additionalInfo);
this.logError(normalizedError, context);
this.recordError(normalizedError);
this.incrementErrorCount(normalizedError.type);
return normalizedError;
}
/**
* Normalizuje błąd do standardowego formatu
* @param {Error | AppError | string} error - Błąd do znormalizowania
* @param {string} context - Kontekst
* @param {object} additionalInfo - Dodatkowe informacje
* @returns {AppError} Znormalizowany błąd
*/
normalizeError(error, context, additionalInfo) {
if (error instanceof AppError) {
return error;
}
if (error instanceof Error) {
const type = this.categorizeError(error, context);
return new AppError(error.message, type, { context, ...additionalInfo }, error);
}
if (typeof error === 'string') {
return new AppError(error, ErrorTypes.SYSTEM, { context, ...additionalInfo });
}
return new AppError('Unknown error occurred', ErrorTypes.SYSTEM, { context, originalError: error, ...additionalInfo });
}
/**
* Kategoryzuje błąd na podstawie wiadomości i kontekstu
* @param {Error} error - Błąd do skategoryzowania
* @param {string} context - Kontekst
* @returns {ErrorType} Typ błędu
*/
categorizeError(error, context) {
const message = error.message.toLowerCase();
if (message.includes('fetch') || message.includes('network') ||
message.includes('connection') || message.includes('timeout')) {
return ErrorTypes.NETWORK;
}
if (message.includes('file') || message.includes('read') ||
message.includes('write') || message.includes('path')) {
return ErrorTypes.FILE_IO;
}
if (message.includes('invalid') || message.includes('required') ||
message.includes('validation') || message.includes('format')) {
return ErrorTypes.VALIDATION;
}
if (message.includes('image') || message.includes('canvas') ||
message.includes('blob') || message.includes('tensor')) {
return ErrorTypes.IMAGE_PROCESSING;
}
if (message.includes('state') || message.includes('cache') ||
message.includes('storage')) {
return ErrorTypes.STATE_MANAGEMENT;
}
if (context.toLowerCase().includes('canvas')) {
return ErrorTypes.CANVAS;
}
return ErrorTypes.SYSTEM;
}
/**
* Loguje błąd z odpowiednim poziomem
* @param {AppError} error - Błąd do zalogowania
* @param {string} context - Kontekst
*/
logError(error, context) {
const logMessage = `[${error.type}] ${error.message}`;
const logDetails = {
context,
timestamp: error.timestamp,
details: error.details,
stack: error.stack
};
switch (error.type) {
case ErrorTypes.VALIDATION:
case ErrorTypes.USER_INPUT:
log.warn(logMessage, logDetails);
break;
case ErrorTypes.NETWORK:
log.error(logMessage, logDetails);
break;
default:
log.error(logMessage, logDetails);
}
}
/**
* Zapisuje błąd w historii
* @param {AppError} error - Błąd do zapisania
*/
recordError(error) {
this.errorHistory.push({
timestamp: error.timestamp,
type: error.type,
message: error.message,
context: error.details?.context
});
if (this.errorHistory.length > this.maxHistorySize) {
this.errorHistory.shift();
}
}
/**
* Zwiększa licznik błędów dla danego typu
* @param {ErrorType} errorType - Typ błędu
*/
incrementErrorCount(errorType) {
const current = this.errorCounts.get(errorType) || 0;
this.errorCounts.set(errorType, current + 1);
}
/**
* Zwraca statystyki błędów
* @returns {ErrorStats} Statystyki błędów
*/
getErrorStats() {
const errorCountsObj = {};
for (const [key, value] of this.errorCounts.entries()) {
errorCountsObj[key] = value;
}
return {
totalErrors: this.errorHistory.length,
errorCounts: errorCountsObj,
recentErrors: this.errorHistory.slice(-10),
errorsByType: this.groupErrorsByType()
};
}
/**
* Grupuje błędy według typu
* @returns {{ [key: string]: ErrorHistoryEntry[] }} Błędy pogrupowane według typu
*/
groupErrorsByType() {
const grouped = {};
this.errorHistory.forEach((error) => {
if (!grouped[error.type]) {
grouped[error.type] = [];
}
grouped[error.type].push(error);
});
return grouped;
}
/**
* Czyści historię błędów
*/
clearHistory() {
this.errorHistory = [];
this.errorCounts.clear();
log.info('Error history cleared');
}
}
const errorHandler = new ErrorHandler();
/**
* Wrapper funkcji z automatyczną obsługą błędów
* @param {Function} fn - Funkcja do opakowania
* @param {string} context - Kontekst wykonania
* @returns {Function} Opakowana funkcja
*/
export function withErrorHandling(fn, context) {
return async function (...args) {
try {
return await fn.apply(this, args);
}
catch (error) {
const handledError = errorHandler.handle(error, context, {
functionName: fn.name,
arguments: args.length
});
throw handledError;
}
};
}
/**
* Decorator dla metod klasy z automatyczną obsługą błędów
* @param {string} context - Kontekst wykonania
*/
export function handleErrors(context) {
return function (target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args) {
try {
return await originalMethod.apply(this, args);
}
catch (error) {
const handledError = errorHandler.handle(error, `${context}.${propertyKey}`, {
className: target.constructor.name,
methodName: propertyKey,
arguments: args.length
});
throw handledError;
}
};
return descriptor;
};
}
/**
* Funkcja pomocnicza do tworzenia błędów walidacji
* @param {string} message - Wiadomość błędu
* @param {object} details - Szczegóły walidacji
* @returns {AppError} Błąd walidacji
*/
export function createValidationError(message, details = {}) {
return new AppError(message, ErrorTypes.VALIDATION, details);
}
/**
* Funkcja pomocnicza do tworzenia błędów sieciowych
* @param {string} message - Wiadomość błędu
* @param {object} details - Szczegóły sieci
* @returns {AppError} Błąd sieciowy
*/
export function createNetworkError(message, details = {}) {
return new AppError(message, ErrorTypes.NETWORK, details);
}
/**
* Funkcja pomocnicza do tworzenia błędów plików
* @param {string} message - Wiadomość błędu
* @param {object} details - Szczegóły pliku
* @returns {AppError} Błąd pliku
*/
export function createFileError(message, details = {}) {
return new AppError(message, ErrorTypes.FILE_IO, details);
}
/**
* Funkcja pomocnicza do bezpiecznego wykonania operacji
* @param {() => Promise<T>} operation - Operacja do wykonania
* @param {T} fallbackValue - Wartość fallback w przypadku błędu
* @param {string} context - Kontekst operacji
* @returns {Promise<T>} Wynik operacji lub wartość fallback
*/
export async function safeExecute(operation, fallbackValue, context = 'SafeExecute') {
try {
return await operation();
}
catch (error) {
errorHandler.handle(error, context);
return fallbackValue;
}
}
/**
* Funkcja do retry operacji z exponential backoff
* @param {() => Promise<T>} operation - Operacja do powtórzenia
* @param {number} maxRetries - Maksymalna liczba prób
* @param {number} baseDelay - Podstawowe opóźnienie w ms
* @param {string} context - Kontekst operacji
* @returns {Promise<T>} Wynik operacji
*/
export async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, context = 'RetryOperation') {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
}
catch (error) {
lastError = error;
if (attempt === maxRetries) {
break;
}
const delay = baseDelay * Math.pow(2, attempt);
log.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms`, { error: lastError.message, context });
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw errorHandler.handle(lastError, context, { attempts: maxRetries + 1 });
}
export { errorHandler };
export default errorHandler;
+51
View File
@@ -0,0 +1,51 @@
// @ts-ignore
import { $el } from "../../../scripts/ui.js";
import { createModuleLogger } from "../log_system/LoggerUtils.js";
import { withErrorHandling, createValidationError, createNetworkError } from "./ErrorHandler.js";
const log = createModuleLogger('ResourceManager');
export const addStylesheet = withErrorHandling(function (url) {
if (!url) {
throw createValidationError("URL is required", { url });
}
log.debug('Adding stylesheet:', { url });
if (url.endsWith(".js")) {
url = url.substr(0, url.length - 2) + "css";
}
$el("link", {
parent: document.head,
rel: "stylesheet",
type: "text/css",
href: url.startsWith("http") ? url : getUrl(url),
});
log.debug('Stylesheet added successfully:', { finalUrl: url });
}, 'addStylesheet');
export function getUrl(path, baseUrl) {
if (!path) {
throw createValidationError("Path is required", { path });
}
if (baseUrl) {
return new URL(path, baseUrl).toString();
}
else {
// @ts-ignore
return new URL("../" + path, import.meta.url).toString();
}
}
export const loadTemplate = withErrorHandling(async function (path, baseUrl) {
if (!path) {
throw createValidationError("Path is required", { path });
}
const url = getUrl(path, baseUrl);
log.debug('Loading template:', { path, url });
const response = await fetch(url);
if (!response.ok) {
throw createNetworkError(`Failed to load template: ${url}`, {
url,
status: response.status,
statusText: response.statusText
});
}
const content = await response.text();
log.debug('Template loaded successfully:', { path, contentLength: content.length });
return content;
}, 'loadTemplate');
@@ -3,8 +3,8 @@
* Loads all CSS files for the resolution master components
*/
import { addStylesheet, getUrl } from "./utils/ResourceManager.js";
import { createModuleLogger } from "./utils/LoggerUtils.js";
import { addStylesheet, getUrl } from "./ResourceManager.js";
import { createModuleLogger } from "../log_system/LoggerUtils.js";
const log = createModuleLogger('CSSLoader');