From 79b1bef93e173a6d9b4d07c6d1a44bceeea798ac Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Mon, 11 May 2026 02:17:25 +0200 Subject: [PATCH] Add centralized logging system with CSS loader - Implement core logger (logger.js) with log levels (DEBUG/INFO/WARN/ERROR) - Add LoggerUtils.js for module-specific logger initialization - Add configuration module (config.js) with DEBUG level default - Add CSS loader for dynamic stylesheet loading - Features: color-coded console output, localStorage persistence, log export, per-module log level control --- web/css-loader.js | 50 ++++++ web/log_system/LoggerUtils.js | 74 ++++++++ web/log_system/config.js | 3 + web/log_system/logger.js | 322 ++++++++++++++++++++++++++++++++++ 4 files changed, 449 insertions(+) create mode 100644 web/css-loader.js create mode 100644 web/log_system/LoggerUtils.js create mode 100644 web/log_system/config.js create mode 100644 web/log_system/logger.js diff --git a/web/css-loader.js b/web/css-loader.js new file mode 100644 index 0000000..ade6003 --- /dev/null +++ b/web/css-loader.js @@ -0,0 +1,50 @@ +/** + * CSS Loader + * Loads all CSS files for the resolution master components + */ + +import { addStylesheet, getUrl } from "./utils/ResourceManager.js"; +import { createModuleLogger } from "./utils/LoggerUtils.js"; + +const log = createModuleLogger('CSSLoader'); + +/** + * Loads all CSS files for the application + */ +export function loadAllStyles() { + try { + log.info('Loading CSS files...'); + + // Load all CSS files using getUrl for proper path resolution + const cssFiles = [ + './css/css-variables.css', // CSS Custom Properties - MUST BE FIRST! + './css/common-components.css', // Shared component styles using cascade + './css/aspect-ratio-columns.css', // Shared aspect ratio column styles + './css/preset-manager-dialog.css', + './css/searchable-dropdown.css', + './css/rename-dialog.css', + './css/json-editor-dialog.css', + './css/preset-ui-components.css', + './css/preset-list-renderer.css', + './css/preset-add-view.css', + './css/drag-drop.css' + ]; + + cssFiles.forEach(file => { + addStylesheet(getUrl(file)); + log.debug(`Loaded: ${file}`); + }); + + log.info('All CSS files loaded successfully'); + } catch (error) { + log.error('Error loading CSS files:', error); + } +} + +/** + * Call this function when the ResolutionMaster node is actually created/used + * to load styles only when needed, preventing global UI interference + */ +export function loadStylesWhenNeeded() { + loadAllStyles(); +} diff --git a/web/log_system/LoggerUtils.js b/web/log_system/LoggerUtils.js new file mode 100644 index 0000000..0101313 --- /dev/null +++ b/web/log_system/LoggerUtils.js @@ -0,0 +1,74 @@ +/** + * LoggerUtils - Centralizacja inicjalizacji loggerów + * Eliminuje powtarzalny kod inicjalizacji loggera w każdym module + */ +import { logger, LogLevel } from "./logger.js"; +import { LOG_LEVEL } from './config.js'; +/** + * Tworzy obiekt loggera dla modułu z predefiniowanymi metodami + * @param {string} moduleName - Nazwa modułu + * @returns {Logger} Obiekt z metodami logowania + */ +export function createModuleLogger(moduleName) { + logger.setModuleLevel(moduleName, LogLevel[LOG_LEVEL]); + return { + debug: (...args) => logger.debug(moduleName, ...args), + info: (...args) => logger.info(moduleName, ...args), + warn: (...args) => logger.warn(moduleName, ...args), + error: (...args) => logger.error(moduleName, ...args) + }; +} +/** + * Tworzy logger z automatycznym wykrywaniem nazwy modułu z URL + * @returns {Logger} Obiekt z metodami logowania + */ +export function createAutoLogger() { + const stack = new Error().stack; + const match = stack?.match(/\/([^\/]+)\.js/); + const moduleName = match ? match[1] : 'Unknown'; + return createModuleLogger(moduleName); +} +/** + * Wrapper dla operacji z automatycznym logowaniem błędów + * @param {Function} operation - Operacja do wykonania + * @param {Logger} log - Obiekt loggera + * @param {string} operationName - Nazwa operacji (dla logów) + * @returns {Function} Opakowana funkcja + */ +export function withErrorLogging(operation, log, operationName) { + return async function (...args) { + try { + log.debug(`Starting ${operationName}`); + const result = await operation.apply(this, args); + log.debug(`Completed ${operationName}`); + return result; + } + catch (error) { + log.error(`Error in ${operationName}:`, error); + throw error; + } + }; +} +/** + * Decorator dla metod klasy z automatycznym logowaniem + * @param {Logger} log - Obiekt loggera + * @param {string} methodName - Nazwa metody + */ +export function logMethod(log, methodName) { + return function (target, propertyKey, descriptor) { + const originalMethod = descriptor.value; + descriptor.value = async function (...args) { + try { + log.debug(`${methodName || propertyKey} started`); + const result = await originalMethod.apply(this, args); + log.debug(`${methodName || propertyKey} completed`); + return result; + } + catch (error) { + log.error(`${methodName || propertyKey} failed:`, error); + throw error; + } + }; + return descriptor; + }; +} diff --git a/web/log_system/config.js b/web/log_system/config.js new file mode 100644 index 0000000..af79d03 --- /dev/null +++ b/web/log_system/config.js @@ -0,0 +1,3 @@ +// Log level for development. +// Possible values: 'DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE' +export const LOG_LEVEL = 'DEBUG'; diff --git a/web/log_system/logger.js b/web/log_system/logger.js new file mode 100644 index 0000000..12e02bc --- /dev/null +++ b/web/log_system/logger.js @@ -0,0 +1,322 @@ +/** + * 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 + */ +function padStart(str, targetLength, padString) { + targetLength = targetLength >> 0; + padString = String(padString || ' '); + if (str.length > targetLength) { + return String(str); + } + else { + targetLength = targetLength - str.length; + if (targetLength > padString.length) { + padString += padString.repeat(targetLength / padString.length); + } + return padString.slice(0, targetLength) + String(str); + } +} +export const LogLevel = { + DEBUG: 0, + INFO: 1, + WARN: 2, + ERROR: 3, + NONE: 4 +}; +const DEFAULT_CONFIG = { + globalLevel: LogLevel.INFO, + moduleSettings: {}, + useColors: true, + saveToStorage: false, + maxStoredLogs: 1000, + timestampFormat: 'HH:mm:ss', + storageKey: 'ResolutionMaster_logs' +}; +const COLORS = { + [LogLevel.DEBUG]: '#9e9e9e', + [LogLevel.INFO]: '#2196f3', + [LogLevel.WARN]: '#ff9800', + [LogLevel.ERROR]: '#f44336', +}; +const LEVEL_NAMES = { + [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(); + } + /** + * Configure the logger + * @param {Partial} config - Configuration object + */ + configure(config) { + this.config = { ...this.config, ...config }; + this.saveConfig(); + return this; + } + /** + * Enable/disable logger globally + * @param {boolean} enabled - Whether the logger should be enabled + */ + setEnabled(enabled) { + this.enabled = enabled; + return this; + } + /** + * Set global logging level + * @param {LogLevels} level - Logging level + */ + setGlobalLevel(level) { + this.config.globalLevel = level; + this.saveConfig(); + return this; + } + /** + * Set logging level for a specific module + * @param {string} module - Module name + * @param {LogLevels} level - Logging level + */ + setModuleLevel(module, level) { + this.config.moduleSettings[module] = level; + this.saveConfig(); + return this; + } + /** + * Check if a given logging level is active for a module + * @param {string} module - Module name + * @param {LogLevels} level - Logging level to check + * @returns {boolean} - Whether the level is active + */ + isLevelEnabled(module, level) { + if (!this.enabled) + return false; + if (this.config.moduleSettings[module] !== undefined) { + return level >= this.config.moduleSettings[module]; + } + return level >= this.config.globalLevel; + } + /** + * Format timestamp + * @returns {string} - Formatted timestamp + */ + formatTimestamp() { + const now = new Date(); + const format = this.config.timestampFormat; + return format + .replace('HH', padStart(String(now.getHours()), 2, '0')) + .replace('mm', padStart(String(now.getMinutes()), 2, '0')) + .replace('ss', padStart(String(now.getSeconds()), 2, '0')) + .replace('SSS', padStart(String(now.getMilliseconds()), 3, '0')); + } + /** + * Save log + * @param {string} module - Module name + * @param {LogLevels} level - Logging level + * @param {any[]} args - Arguments to log + */ + log(module, level, ...args) { + if (!this.isLevelEnabled(module, level)) + return; + const timestamp = this.formatTimestamp(); + const levelName = LEVEL_NAMES[level]; + const logData = { + timestamp, + module, + level, + levelName, + args, + time: new Date() + }; + if (this.config.saveToStorage) { + this.logs.push(logData); + if (this.logs.length > this.config.maxStoredLogs) { + this.logs.shift(); + } + this.saveLogs(); + } + this.printToConsole(logData); + } + /** + * Display log to console + * @param {LogData} logData - Log data + */ + printToConsole(logData) { + const { timestamp, module, level, levelName, args } = logData; + const prefix = `[${timestamp}] [${module}] [${levelName}]`; + if (this.config.useColors && typeof console.log === 'function') { + const color = COLORS[level] || '#000000'; + console.log(`%c${prefix}`, `color: ${color}; font-weight: bold;`, ...args); + return; + } + console.log(prefix, ...args); + } + /** + * Save logs to localStorage + */ + 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)); + } + catch (e) { + console.error('Failed to save logs to localStorage:', e); + } + } + } + /** + * Load logs from localStorage + */ + loadLogs() { + if (typeof localStorage !== 'undefined' && this.config.saveToStorage) { + try { + const storedLogs = localStorage.getItem(this.config.storageKey); + if (storedLogs) { + this.logs = JSON.parse(storedLogs); + } + } + catch (e) { + console.error('Failed to load logs from localStorage:', e); + } + } + } + /** + * Save configuration to localStorage + */ + saveConfig() { + if (typeof localStorage !== 'undefined') { + try { + localStorage.setItem('ResolutionMaster_logger_config', JSON.stringify(this.config)); + } + catch (e) { + console.error('Failed to save logger config to localStorage:', e); + } + } + } + /** + * Load configuration from localStorage + */ + loadConfig() { + if (typeof localStorage !== 'undefined') { + try { + const storedConfig = localStorage.getItem('ResolutionMaster_logger_config'); + if (storedConfig) { + this.config = { ...this.config, ...JSON.parse(storedConfig) }; + } + } + catch (e) { + console.error('Failed to load logger config from localStorage:', e); + } + } + } + /** + * Clear all logs + */ + clearLogs() { + this.logs = []; + if (typeof localStorage !== 'undefined') { + localStorage.removeItem(this.config.storageKey); + } + return this; + } + /** + * Export logs to file + * @param {'json' | 'txt'} format - Export format + */ + exportLogs(format = 'json') { + if (this.logs.length === 0) { + console.warn('No logs to export'); + return; + } + let content; + let mimeType; + let extension; + if (format === 'json') { + content = JSON.stringify(this.logs, null, 2); + mimeType = 'application/json'; + extension = 'json'; + } + else { + content = this.logs.map((log) => `[${log.timestamp}] [${log.module}] [${log.levelName}] ${log.args.join(' ')}`).join('\n'); + mimeType = 'text/plain'; + extension = 'txt'; + } + const blob = new Blob([content], { type: mimeType }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `ResolutionMaster_logs_${new Date().toISOString().replace(/[:.]/g, '-')}.${extension}`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + /** + * Log at DEBUG level + * @param {string} module - Module name + * @param {any[]} args - Arguments to log + */ + debug(module, ...args) { + this.log(module, LogLevel.DEBUG, ...args); + } + /** + * Log at INFO level + * @param {string} module - Module name + * @param {any[]} args - Arguments to log + */ + info(module, ...args) { + this.log(module, LogLevel.INFO, ...args); + } + /** + * Log at WARN level + * @param {string} module - Module name + * @param {any[]} args - Arguments to log + */ + warn(module, ...args) { + this.log(module, LogLevel.WARN, ...args); + } + /** + * Log at ERROR level + * @param {string} module - Module name + * @param {any[]} args - Arguments to log + */ + error(module, ...args) { + this.log(module, LogLevel.ERROR, ...args); + } +} +export const logger = new Logger(); +export const debug = (module, ...args) => logger.debug(module, ...args); +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; +} +export default logger;