From a288b26b6629fa4cd42e85a839bb69f25c9ab586 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Mon, 11 May 2026 02:41:30 +0200 Subject: [PATCH] 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 --- core/log_system/config.py | 6 + web/log_system/LoggerUtils.js | 6 + web/log_system/config.js | 12 ++ web/log_system/logger.js | 51 ++++-- web/utils/ErrorHandler.js | 313 ++++++++++++++++++++++++++++++++++ web/utils/ResourceManager.js | 51 ++++++ web/{ => utils}/css-loader.js | 4 +- 7 files changed, 427 insertions(+), 16 deletions(-) create mode 100644 web/utils/ErrorHandler.js create mode 100644 web/utils/ResourceManager.js rename web/{ => utils}/css-loader.js (92%) diff --git a/core/log_system/config.py b/core/log_system/config.py index 7fa2bf5..fa61453 100644 --- a/core/log_system/config.py +++ b/core/log_system/config.py @@ -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" diff --git a/web/log_system/LoggerUtils.js b/web/log_system/LoggerUtils.js index 8eb5294..e71dc23 100644 --- a/web/log_system/LoggerUtils.js +++ b/web/log_system/LoggerUtils.js @@ -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 diff --git a/web/log_system/config.js b/web/log_system/config.js index af79d03..a5c4828 100644 --- a/web/log_system/config.js +++ b/web/log_system/config.js @@ -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'; diff --git a/web/log_system/logger.js b/web/log_system/logger.js index 12e02bc..a068a82 100644 --- a/web/log_system/logger.js +++ b/web/log_system/logger.js @@ -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; diff --git a/web/utils/ErrorHandler.js b/web/utils/ErrorHandler.js new file mode 100644 index 0000000..5457398 --- /dev/null +++ b/web/utils/ErrorHandler.js @@ -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} operation - Operacja do wykonania + * @param {T} fallbackValue - Wartość fallback w przypadku błędu + * @param {string} context - Kontekst operacji + * @returns {Promise} 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} 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} 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; diff --git a/web/utils/ResourceManager.js b/web/utils/ResourceManager.js new file mode 100644 index 0000000..e3c28e5 --- /dev/null +++ b/web/utils/ResourceManager.js @@ -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'); diff --git a/web/css-loader.js b/web/utils/css-loader.js similarity index 92% rename from web/css-loader.js rename to web/utils/css-loader.js index ade6003..391ae81 100644 --- a/web/css-loader.js +++ b/web/utils/css-loader.js @@ -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');