diff --git a/README_LOGGER.md b/README_LOGGER.md new file mode 100644 index 0000000..bc8ba7f --- /dev/null +++ b/README_LOGGER.md @@ -0,0 +1,316 @@ +# System Logowania dla ComfyUI-LayerForge + +Ten dokument opisuje system logowania zaimplementowany dla projektu ComfyUI-LayerForge, który umożliwia łatwe zarządzanie debugowaniem kodu zarówno w części JavaScript, jak i Python. + +## Spis treści + +1. [Wprowadzenie](#wprowadzenie) +2. [Konfiguracja](#konfiguracja) +3. [Użycie w JavaScript](#użycie-w-javascript) +4. [Użycie w Python](#użycie-w-python) +5. [Poziomy logowania](#poziomy-logowania) +6. [Zarządzanie logami](#zarządzanie-logami) +7. [Przykłady](#przykłady) + +## Wprowadzenie + +System logowania ComfyUI-LayerForge został zaprojektowany, aby zapewnić: + +- Spójne logowanie w całym projekcie (JavaScript i Python) +- Możliwość włączania/wyłączania logów globalnie lub per moduł +- Różne poziomy logowania (DEBUG, INFO, WARN, ERROR) +- Kolorowe logi w konsoli +- Możliwość zapisywania logów do plików +- Eksport logów do analizy + +## Konfiguracja + +### Konfiguracja JavaScript + +```javascript +// Importuj logger +import {logger, LogLevel} from "./logger.js"; + +// Konfiguracja globalna +logger.configure({ + globalLevel: LogLevel.INFO, // Domyślny poziom logowania + useColors: true, // Kolorowe logi w konsoli + saveToStorage: true, // Zapisywanie logów do localStorage + maxStoredLogs: 1000 // Maksymalna liczba przechowywanych logów +}); + +// Konfiguracja per moduł +logger.setModuleLevel('Canvas', LogLevel.DEBUG); +logger.setModuleLevel('API', LogLevel.WARN); + +// Włączanie/wyłączanie globalnie +logger.setEnabled(true); // Włącz wszystkie logi +logger.setEnabled(false); // Wyłącz wszystkie logi +``` + +### Konfiguracja Python + +```python +# Importuj logger +from python.logger import logger, LogLevel, set_debug, set_file_logging + +# Konfiguracja globalna +logger.configure({ + 'global_level': LogLevel.INFO, + 'use_colors': True, + 'log_to_file': True, + 'log_dir': 'logs', + 'max_file_size_mb': 10, + 'backup_count': 5 +}) + +# Konfiguracja per moduł +logger.set_module_level('canvas_node', LogLevel.DEBUG) +logger.set_module_level('api', LogLevel.WARN) + +# Szybkie włączanie debugowania +set_debug(True) # Ustawia globalny poziom na DEBUG +set_debug(False) # Ustawia globalny poziom na INFO + +# Włączanie/wyłączanie logowania do pliku +set_file_logging(True, 'custom_logs') +``` + +### Konfiguracja przez zmienne środowiskowe (Python) + +Możesz również skonfigurować logger Python za pomocą zmiennych środowiskowych: + +```bash +# Poziom globalny (DEBUG, INFO, WARN, ERROR, NONE) +export LAYERFORGE_LOG_LEVEL=DEBUG + +# Ustawienia modułów (format JSON) +export LAYERFORGE_MODULE_LEVELS='{"canvas_node": "DEBUG", "api": "WARN"}' + +# Inne ustawienia +export LAYERFORGE_USE_COLORS=true +export LAYERFORGE_LOG_TO_FILE=true +export LAYERFORGE_LOG_DIR=logs +export LAYERFORGE_MAX_FILE_SIZE_MB=10 +export LAYERFORGE_BACKUP_COUNT=5 +``` + +## Użycie w JavaScript + +### Podstawowe użycie + +```javascript +import {logger, LogLevel} from "./logger.js"; + +// Inicjalizacja loggera dla modułu +const log = { + debug: (...args) => logger.debug('MojModul', ...args), + info: (...args) => logger.info('MojModul', ...args), + warn: (...args) => logger.warn('MojModul', ...args), + error: (...args) => logger.error('MojModul', ...args) +}; + +// Używanie loggera +log.debug("To jest wiadomość debugowania"); +log.info("To jest informacja"); +log.warn("To jest ostrzeżenie"); +log.error("To jest błąd"); + +// Logowanie obiektów +log.debug("Dane użytkownika:", { id: 123, name: "Jan Kowalski" }); + +// Logowanie błędów +try { + // Kod, który może rzucić wyjątek +} catch (error) { + log.error("Wystąpił błąd:", error); +} +``` + +### Dostęp z konsoli przeglądarki + +Logger jest dostępny globalnie w przeglądarce jako `window.LayerForgeLogger`, co umożliwia łatwe debugowanie: + +```javascript +// W konsoli przeglądarki +LayerForgeLogger.setGlobalLevel(LayerForgeLogger.LogLevel.DEBUG); +LayerForgeLogger.setModuleLevel('Canvas', LayerForgeLogger.LogLevel.DEBUG); +LayerForgeLogger.exportLogs('json'); // Eksportuje logi do pliku JSON +``` + +## Użycie w Python + +### Podstawowe użycie + +```python +from python.logger import debug, info, warn, error, exception + +# Używanie funkcji pomocniczych +debug('canvas_node', 'To jest wiadomość debugowania') +info('canvas_node', 'To jest informacja') +warn('canvas_node', 'To jest ostrzeżenie') +error('canvas_node', 'To jest błąd') + +# Logowanie wyjątków +try: + # Kod, który może rzucić wyjątek +except Exception as e: + exception('canvas_node', f'Wystąpił błąd: {str(e)}') +``` + +### Używanie funkcji pomocniczych w module + +```python +from python.logger import logger, LogLevel, debug, info, warn, error, exception + +# Funkcje pomocnicze dla modułu +log_debug = lambda *args, **kwargs: debug('moj_modul', *args, **kwargs) +log_info = lambda *args, **kwargs: info('moj_modul', *args, **kwargs) +log_warn = lambda *args, **kwargs: warn('moj_modul', *args, **kwargs) +log_error = lambda *args, **kwargs: error('moj_modul', *args, **kwargs) +log_exception = lambda *args: exception('moj_modul', *args) + +# Używanie funkcji pomocniczych +log_debug("To jest wiadomość debugowania") +log_info("To jest informacja") +log_warn("To jest ostrzeżenie") +log_error("To jest błąd") +``` + +## Poziomy logowania + +System logowania obsługuje następujące poziomy: + +- **DEBUG** - Szczegółowe informacje, przydatne podczas debugowania +- **INFO** - Ogólne informacje o działaniu aplikacji +- **WARN** - Ostrzeżenia, które nie powodują błędów, ale mogą prowadzić do problemów +- **ERROR** - Błędy, które uniemożliwiają wykonanie operacji +- **NONE** - Wyłącza wszystkie logi + +## Zarządzanie logami + +### JavaScript + +```javascript +// Eksport logów do pliku +logger.exportLogs('json'); // Eksportuje logi do pliku JSON +logger.exportLogs('txt'); // Eksportuje logi do pliku tekstowego + +// Czyszczenie logów +logger.clearLogs(); +``` + +### Python + +Logi Python są automatycznie zapisywane do plików w katalogu `logs` (lub innym skonfigurowanym), jeśli włączono opcję `log_to_file`. Pliki logów są rotowane, gdy osiągną określony rozmiar. + +## Przykłady + +### Przykład użycia w JavaScript + +```javascript +import {logger, LogLevel} from "./logger.js"; + +// Inicjalizacja loggera dla modułu Canvas +const log = { + debug: (...args) => logger.debug('Canvas', ...args), + info: (...args) => logger.info('Canvas', ...args), + warn: (...args) => logger.warn('Canvas', ...args), + error: (...args) => logger.error('Canvas', ...args) +}; + +// Konfiguracja loggera dla modułu Canvas +logger.setModuleLevel('Canvas', LogLevel.DEBUG); + +class Canvas { + constructor() { + log.info("Inicjalizacja Canvas"); + this.width = 512; + this.height = 512; + } + + render() { + log.debug(`Renderowanie canvas o wymiarach ${this.width}x${this.height}`); + // Kod renderowania... + log.info("Renderowanie zakończone"); + } + + saveToServer(fileName) { + log.info(`Zapisywanie do serwera: ${fileName}`); + try { + // Kod zapisywania... + log.debug("Zapisano pomyślnie"); + return true; + } catch (error) { + log.error("Błąd podczas zapisywania:", error); + return false; + } + } +} +``` + +### Przykład użycia w Python + +```python +from python.logger import logger, LogLevel, debug, info, warn, error, exception + +# Konfiguracja loggera dla modułu canvas_node +logger.set_module_level('canvas_node', LogLevel.DEBUG) + +# Funkcje pomocnicze dla modułu +log_debug = lambda *args, **kwargs: debug('canvas_node', *args, **kwargs) +log_info = lambda *args, **kwargs: info('canvas_node', *args, **kwargs) +log_warn = lambda *args, **kwargs: warn('canvas_node', *args, **kwargs) +log_error = lambda *args, **kwargs: error('canvas_node', *args, **kwargs) +log_exception = lambda *args: exception('canvas_node', *args) + +class CanvasNode: + def __init__(self): + log_info("Inicjalizacja CanvasNode") + self.flow_id = "123456" + + def process_canvas_image(self, canvas_image, trigger, output_switch, cache_enabled, input_image=None, input_mask=None): + try: + log_info(f"Przetwarzanie obrazu canvas - ID wykonania: {self.flow_id}, trigger: {trigger}") + log_debug(f"Nazwa pliku canvas: {canvas_image}") + log_debug(f"Output switch: {output_switch}, Cache enabled: {cache_enabled}") + + # Kod przetwarzania... + + log_info("Pomyślnie zwrócono przetworzony obraz i maskę") + return (processed_image, processed_mask) + except Exception as e: + log_exception(f"Błąd w process_canvas_image: {str(e)}") + return () +``` + +## Podsumowanie + +System logowania ComfyUI-LayerForge zapewnia spójne i elastyczne rozwiązanie do debugowania i monitorowania aplikacji. Dzięki możliwości konfiguracji poziomów logowania per moduł, możesz skupić się na konkretnych częściach aplikacji bez zaśmiecania konsoli niepotrzebnymi informacjami. + +Aby włączyć pełne debugowanie, ustaw poziom globalny na `DEBUG`: + +```javascript +// JavaScript +logger.setGlobalLevel(LogLevel.DEBUG); +``` + +```python +# Python +logger.set_global_level(LogLevel.DEBUG) +# lub +set_debug(True) +``` + +Aby wyłączyć wszystkie logi: + +```javascript +// JavaScript +logger.setEnabled(false); +// lub +logger.setGlobalLevel(LogLevel.NONE); +``` + +```python +# Python +logger.set_global_level(LogLevel.NONE) \ No newline at end of file diff --git a/canvas_node.py b/canvas_node.py index 2b861fc..22471a1 100644 --- a/canvas_node.py +++ b/canvas_node.py @@ -16,6 +16,45 @@ import time import base64 from PIL import Image import io +import sys +import os + +# Dodaj ścieżkę do katalogu python/ do sys.path +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'python')) + +# Importuj logger +try: + from python.logger import logger, LogLevel, debug, info, warn, error, exception + + # Konfiguracja loggera dla modułu canvas_node + logger.set_module_level('canvas_node', LogLevel.INFO) # Domyślnie INFO, można zmienić na DEBUG + + # Włącz logowanie do pliku + logger.configure({ + 'log_to_file': True, + 'log_dir': os.path.join(os.path.dirname(os.path.abspath(__file__)), 'logs') + }) + + # Funkcje pomocnicze dla modułu + log_debug = lambda *args, **kwargs: debug('canvas_node', *args, **kwargs) + log_info = lambda *args, **kwargs: info('canvas_node', *args, **kwargs) + log_warn = lambda *args, **kwargs: warn('canvas_node', *args, **kwargs) + log_error = lambda *args, **kwargs: error('canvas_node', *args, **kwargs) + log_exception = lambda *args: exception('canvas_node', *args) + + log_info("Logger initialized for canvas_node") +except ImportError as e: + # Fallback jeśli logger nie jest dostępny + print(f"Warning: Logger module not available: {e}") + + # Proste funkcje zastępcze + def log_debug(*args): print("[DEBUG]", *args) + def log_info(*args): print("[INFO]", *args) + def log_warn(*args): print("[WARN]", *args) + def log_error(*args): print("[ERROR]", *args) + def log_exception(*args): + print("[ERROR]", *args) + traceback.print_exc() torch.set_float32_matmul_precision('high') @@ -74,7 +113,7 @@ class CanvasNode: current_execution = self.get_execution_id() if current_execution != self.__class__._canvas_cache['last_execution_id']: - print(f"New execution detected: {current_execution}") + log_info(f"New execution detected: {current_execution}") self.__class__._canvas_cache['image'] = None self.__class__._canvas_cache['mask'] = None self.__class__._canvas_cache['last_execution_id'] = current_execution @@ -82,12 +121,12 @@ class CanvasNode: if persistent.get('image') is not None: self.__class__._canvas_cache['image'] = persistent['image'] - print("Restored image from persistent cache") + log_info("Restored image from persistent cache") if persistent.get('mask') is not None: self.__class__._canvas_cache['mask'] = persistent['mask'] - print("Restored mask from persistent cache") + log_info("Restored mask from persistent cache") except Exception as e: - print(f"Error restoring cache: {str(e)}") + log_error(f"Error restoring cache: {str(e)}") def get_execution_id(self): @@ -95,7 +134,7 @@ class CanvasNode: return str(int(time.time() * 1000)) except Exception as e: - print(f"Error getting execution ID: {str(e)}") + log_error(f"Error getting execution ID: {str(e)}") return None def update_persistent_cache(self): @@ -105,9 +144,9 @@ class CanvasNode: 'image': self.__class__._canvas_cache['image'], 'mask': self.__class__._canvas_cache['mask'] } - print("Updated persistent cache") + log_debug("Updated persistent cache") except Exception as e: - print(f"Error updating persistent cache: {str(e)}") + log_error(f"Error updating persistent cache: {str(e)}") def track_data_flow(self, stage, status, data_info=None): @@ -117,9 +156,9 @@ class CanvasNode: 'status': status, 'data_info': data_info } - print(f"Data Flow [{self.flow_id}] - Stage: {stage}, Status: {status}") + log_debug(f"Data Flow [{self.flow_id}] - Stage: {stage}, Status: {status}") if data_info: - print(f"Data Info: {data_info}") + log_debug(f"Data Info: {data_info}") self.__class__._canvas_cache['data_flow_status'][self.flow_id] = flow_status @@ -159,7 +198,7 @@ class CanvasNode: return input_image except Exception as e: - print(f"Error in add_image_to_canvas: {str(e)}") + log_error(f"Error in add_image_to_canvas: {str(e)}") return None def add_mask_to_canvas(self, input_mask, input_image): @@ -187,7 +226,7 @@ class CanvasNode: return input_mask except Exception as e: - print(f"Error in add_mask_to_canvas: {str(e)}") + log_error(f"Error in add_mask_to_canvas: {str(e)}") return None # Zmienna blokująca równoczesne wykonania @@ -198,30 +237,30 @@ class CanvasNode: try: # Sprawdź czy już trwa przetwarzanie if self.__class__._processing_lock is not None: - print(f"[OUTPUT_LOG] Process already in progress, waiting for completion...") + log_warn(f"Process already in progress, waiting for completion...") return () # Zwróć pusty wynik, aby uniknąć równoczesnych przetworzeń # Ustaw blokadę self.__class__._processing_lock = True current_execution = self.get_execution_id() - print(f"[OUTPUT_LOG] Starting process_canvas_image - execution ID: {current_execution}, trigger: {trigger}") - print(f"[OUTPUT_LOG] Canvas image filename: {canvas_image}") - print(f"[OUTPUT_LOG] Output switch: {output_switch}, Cache enabled: {cache_enabled}") - print(f"[OUTPUT_LOG] Input image provided: {input_image is not None}") - print(f"[OUTPUT_LOG] Input mask provided: {input_mask is not None}") + log_info(f"Starting process_canvas_image - execution ID: {current_execution}, trigger: {trigger}") + log_debug(f"Canvas image filename: {canvas_image}") + log_debug(f"Output switch: {output_switch}, Cache enabled: {cache_enabled}") + log_debug(f"Input image provided: {input_image is not None}") + log_debug(f"Input mask provided: {input_mask is not None}") if current_execution != self.__class__._canvas_cache['last_execution_id']: - print(f"[OUTPUT_LOG] New execution detected: {current_execution} (previous: {self.__class__._canvas_cache['last_execution_id']})") + log_info(f"New execution detected: {current_execution} (previous: {self.__class__._canvas_cache['last_execution_id']})") self.__class__._canvas_cache['image'] = None self.__class__._canvas_cache['mask'] = None self.__class__._canvas_cache['last_execution_id'] = current_execution else: - print(f"[OUTPUT_LOG] Same execution ID, using cached data") + log_debug(f"Same execution ID, using cached data") if input_image is not None: - print("Input image received, converting to PIL Image...") + log_info("Input image received, converting to PIL Image...") if isinstance(input_image, torch.Tensor): if input_image.dim() == 4: @@ -240,17 +279,17 @@ class CanvasNode: try: pil_image = Image.fromarray(image_array, 'RGB') - print("Successfully converted to PIL Image") + log_debug("Successfully converted to PIL Image") self.__class__._canvas_cache['image'] = pil_image - print(f"Image stored in cache with size: {pil_image.size}") + log_debug(f"Image stored in cache with size: {pil_image.size}") except Exception as e: - print(f"Error converting to PIL Image: {str(e)}") - print(f"Array shape: {image_array.shape}, dtype: {image_array.dtype}") + log_error(f"Error converting to PIL Image: {str(e)}") + log_debug(f"Array shape: {image_array.shape}, dtype: {image_array.dtype}") raise if input_mask is not None: - print("Input mask received, converting to PIL Image...") + log_info("Input mask received, converting to PIL Image...") if isinstance(input_mask, torch.Tensor): if input_mask.dim() == 4: input_mask = input_mask.squeeze(0) @@ -259,10 +298,10 @@ class CanvasNode: mask_array = (input_mask.cpu().numpy() * 255).astype(np.uint8) pil_mask = Image.fromarray(mask_array, 'L') - print("Successfully converted mask to PIL Image") + log_debug("Successfully converted mask to PIL Image") self.__class__._canvas_cache['mask'] = pil_mask - print(f"Mask stored in cache with size: {pil_mask.size}") + log_debug(f"Mask stored in cache with size: {pil_mask.size}") self.__class__._canvas_cache['cache_enabled'] = cache_enabled @@ -270,7 +309,7 @@ class CanvasNode: # Wczytaj obraz bez maski path_image_without_mask = folder_paths.get_annotated_filepath( canvas_image.replace('.png', '_without_mask.png')) - print(f"[OUTPUT_LOG] Loading image without mask from: {path_image_without_mask}") + log_debug(f"Loading image without mask from: {path_image_without_mask}") i = Image.open(path_image_without_mask) i = ImageOps.exif_transpose(i) if i.mode not in ['RGB', 'RGBA']: @@ -281,56 +320,55 @@ class CanvasNode: alpha = image[..., 3:] image = rgb * alpha + (1 - alpha) * 0.5 processed_image = torch.from_numpy(image)[None,] - print(f"[OUTPUT_LOG] Successfully loaded image without mask, shape: {processed_image.shape}") + log_debug(f"Successfully loaded image without mask, shape: {processed_image.shape}") except Exception as e: - print(f"[OUTPUT_LOG] Error loading image without mask: {str(e)}") + log_error(f"Error loading image without mask: {str(e)}") processed_image = torch.ones((1, 512, 512, 3), dtype=torch.float32) - print(f"[OUTPUT_LOG] Using default image, shape: {processed_image.shape}") + log_debug(f"Using default image, shape: {processed_image.shape}") try: # Wczytaj maskę path_image = folder_paths.get_annotated_filepath(canvas_image) path_mask = path_image.replace('.png', '_mask.png') - print(f"[OUTPUT_LOG] Looking for mask at: {path_mask}") + log_debug(f"Looking for mask at: {path_mask}") if os.path.exists(path_mask): - print(f"[OUTPUT_LOG] Mask file exists, loading...") + log_debug(f"Mask file exists, loading...") mask = Image.open(path_mask).convert('L') mask = np.array(mask).astype(np.float32) / 255.0 processed_mask = torch.from_numpy(mask)[None,] - print(f"[OUTPUT_LOG] Successfully loaded mask, shape: {processed_mask.shape}") + log_debug(f"Successfully loaded mask, shape: {processed_mask.shape}") else: - print(f"[OUTPUT_LOG] Mask file does not exist, creating default mask") + log_debug(f"Mask file does not exist, creating default mask") processed_mask = torch.ones((1, processed_image.shape[1], processed_image.shape[2]), dtype=torch.float32) - print(f"[OUTPUT_LOG] Default mask created, shape: {processed_mask.shape}") + log_debug(f"Default mask created, shape: {processed_mask.shape}") except Exception as e: - print(f"[OUTPUT_LOG] Error loading mask: {str(e)}") + log_error(f"Error loading mask: {str(e)}") processed_mask = torch.ones((1, processed_image.shape[1], processed_image.shape[2]), dtype=torch.float32) - print(f"[OUTPUT_LOG] Fallback mask created, shape: {processed_mask.shape}") + log_debug(f"Fallback mask created, shape: {processed_mask.shape}") if not output_switch: - print(f"[OUTPUT_LOG] Output switch is OFF, returning empty tuple") + log_debug(f"Output switch is OFF, returning empty tuple") return () - print(f"[OUTPUT_LOG] About to return output - Image shape: {processed_image.shape}, Mask shape: {processed_mask.shape}") - print(f"[OUTPUT_LOG] Image tensor info - dtype: {processed_image.dtype}, device: {processed_image.device}") - print(f"[OUTPUT_LOG] Mask tensor info - dtype: {processed_mask.dtype}, device: {processed_mask.device}") + log_debug(f"About to return output - Image shape: {processed_image.shape}, Mask shape: {processed_mask.shape}") + log_debug(f"Image tensor info - dtype: {processed_image.dtype}, device: {processed_image.device}") + log_debug(f"Mask tensor info - dtype: {processed_mask.dtype}, device: {processed_mask.device}") self.update_persistent_cache() - print(f"[OUTPUT_LOG] Successfully returning processed image and mask") + log_info(f"Successfully returning processed image and mask") return (processed_image, processed_mask) except Exception as e: - print(f"Error in process_canvas_image: {str(e)}") - traceback.print_exc() + log_exception(f"Error in process_canvas_image: {str(e)}") return () finally: # Zwolnij blokadę self.__class__._processing_lock = None - print(f"[OUTPUT_LOG] Process completed, lock released") + log_debug(f"Process completed, lock released") def get_cached_data(self): return { @@ -378,11 +416,11 @@ class CanvasNode: async def get_canvas_data(request): try: node_id = request.match_info["node_id"] - print(f"Received request for node: {node_id}") + log_debug(f"Received request for node: {node_id}") cache_data = cls._canvas_cache - print(f"Cache content: {cache_data}") - print(f"Image in cache: {cache_data['image'] is not None}") + log_debug(f"Cache content: {cache_data}") + log_debug(f"Image in cache: {cache_data['image'] is not None}") response_data = { 'success': True, @@ -409,7 +447,7 @@ class CanvasNode: return web.json_response(response_data) except Exception as e: - print(f"Error in get_canvas_data: {str(e)}") + log_error(f"Error in get_canvas_data: {str(e)}") return web.json_response({ 'success': False, 'error': str(e) @@ -471,7 +509,7 @@ class BiRefNetMatting: full_model_path = os.path.join(self.base_path, "BiRefNet") - print(f"Loading BiRefNet model from {full_model_path}...") + log_info(f"Loading BiRefNet model from {full_model_path}...") try: @@ -486,23 +524,23 @@ class BiRefNetMatting: self.model = self.model.cuda() self.model_cache[model_path] = self.model - print("Model loaded successfully from Hugging Face") - print(f"Model type: {type(self.model)}") - print(f"Model device: {next(self.model.parameters()).device}") + log_info("Model loaded successfully from Hugging Face") + log_debug(f"Model type: {type(self.model)}") + log_debug(f"Model device: {next(self.model.parameters()).device}") except Exception as e: - print(f"Failed to load model: {str(e)}") + log_error(f"Failed to load model: {str(e)}") raise else: self.model = self.model_cache[model_path] - print("Using cached model") + log_debug("Using cached model") return True except Exception as e: - print(f"Error loading model: {str(e)}") - traceback.print_exc() + log_error(f"Error loading model: {str(e)}") + log_exception("Model loading failed") return False def preprocess_image(self, image): @@ -528,7 +566,7 @@ class BiRefNetMatting: return image_tensor except Exception as e: - print(f"Error preprocessing image: {str(e)}") + log_error(f"Error preprocessing image: {str(e)}") return None def execute(self, image, model_path, threshold=0.5, refinement=1): @@ -544,25 +582,25 @@ class BiRefNetMatting: else: original_size = image.size[::-1] - print(f"Original size: {original_size}") + log_debug(f"Original size: {original_size}") processed_image = self.preprocess_image(image) if processed_image is None: raise Exception("Failed to preprocess image") - print(f"Processed image shape: {processed_image.shape}") + log_debug(f"Processed image shape: {processed_image.shape}") with torch.no_grad(): outputs = self.model(processed_image) result = outputs[-1].sigmoid().cpu() - print(f"Model output shape: {result.shape}") + log_debug(f"Model output shape: {result.shape}") if result.dim() == 3: result = result.unsqueeze(1) # 添加通道维度 elif result.dim() == 2: result = result.unsqueeze(0).unsqueeze(0) # 添加batch和通道维度 - print(f"Reshaped result shape: {result.shape}") + log_debug(f"Reshaped result shape: {result.shape}") result = F.interpolate( result, @@ -570,7 +608,7 @@ class BiRefNetMatting: mode='bilinear', align_corners=True ) - print(f"Resized result shape: {result.shape}") + log_debug(f"Resized result shape: {result.shape}") result = result.squeeze() # 移除多余的维度 ma = torch.max(result) @@ -618,7 +656,7 @@ async def matting(request): # Sprawdź czy już trwa przetwarzanie if _matting_lock is not None: - print("Matting already in progress, rejecting request") + log_warn("Matting already in progress, rejecting request") return web.json_response({ "error": "Another matting operation is in progress", "details": "Please wait for the current operation to complete" @@ -628,13 +666,13 @@ async def matting(request): _matting_lock = True try: - print("Received matting request") + log_info("Received matting request") data = await request.json() matting = BiRefNetMatting() image_tensor, original_alpha = convert_base64_to_tensor(data["image"]) - print(f"Input image shape: {image_tensor.shape}") + log_debug(f"Input image shape: {image_tensor.shape}") matted_image, alpha_mask = matting.execute( image_tensor, @@ -652,9 +690,7 @@ async def matting(request): }) except Exception as e: - print(f"Error in matting endpoint: {str(e)}") - import traceback - traceback.print_exc() + log_exception(f"Error in matting endpoint: {str(e)}") return web.json_response({ "error": str(e), "details": traceback.format_exc() @@ -662,7 +698,7 @@ async def matting(request): finally: # Zwolnij blokadę _matting_lock = None - print("Matting lock released") + log_debug("Matting lock released") def convert_base64_to_tensor(base64_str): @@ -695,7 +731,7 @@ def convert_base64_to_tensor(base64_str): return img_tensor, None except Exception as e: - print(f"Error in convert_base64_to_tensor: {str(e)}") + log_error(f"Error in convert_base64_to_tensor: {str(e)}") raise @@ -742,6 +778,6 @@ def convert_tensor_to_base64(tensor, alpha_mask=None, original_alpha=None): return f"data:image/png;base64,{img_str}" except Exception as e: - print(f"Error in convert_tensor_to_base64: {str(e)}") - print(f"Tensor shape: {tensor.shape}, dtype: {tensor.dtype}") + log_error(f"Error in convert_tensor_to_base64: {str(e)}") + log_debug(f"Tensor shape: {tensor.shape}, dtype: {tensor.dtype}") raise diff --git a/js/Canvas.js b/js/Canvas.js index 9ff7791..43beded 100644 --- a/js/Canvas.js +++ b/js/Canvas.js @@ -1,5 +1,17 @@ import {getCanvasState, setCanvasState, removeCanvasState, saveImage, getImage, removeImage} from "./db.js"; import {MaskTool} from "./Mask_tool.js"; +import {logger, LogLevel} from "./logger.js"; + +// Inicjalizacja loggera dla modułu Canvas +const log = { + debug: (...args) => logger.debug('Canvas', ...args), + info: (...args) => logger.info('Canvas', ...args), + warn: (...args) => logger.warn('Canvas', ...args), + error: (...args) => logger.error('Canvas', ...args) +}; + +// Konfiguracja loggera dla modułu Canvas +logger.setModuleLevel('Canvas', LogLevel.DEBUG); // Domyślnie INFO, można zmienić na DEBUG dla szczegółowych logów // Prosta funkcja generująca UUID function generateUUID() { @@ -101,13 +113,13 @@ export class Canvas { async loadStateFromDB() { // Sprawdź czy już trwa ładowanie if (this._loadInProgress) { - console.log("Load already in progress, waiting..."); + log.warn("Load already in progress, waiting..."); return this._loadInProgress; } - console.log("Attempting to load state from IndexedDB for node:", this.node.id); + log.info("Attempting to load state from IndexedDB for node:", this.node.id); if (!this.node.id) { - console.error("Node ID is not available for loading state from DB."); + log.error("Node ID is not available for loading state from DB."); return false; } @@ -125,35 +137,35 @@ export class Canvas { try { const savedState = await getCanvasState(this.node.id); if (!savedState) { - console.log("No saved state found in IndexedDB for node:", this.node.id); + log.info("No saved state found in IndexedDB for node:", this.node.id); return false; } - console.log("Found saved state in IndexedDB."); + log.info("Found saved state in IndexedDB."); this.width = savedState.width || 512; this.height = savedState.height || 512; this.viewport = savedState.viewport || {x: -(this.width / 4), y: -(this.height / 4), zoom: 0.8}; this.updateCanvasSize(this.width, this.height, false); - console.log(`Canvas resized to ${this.width}x${this.height} and viewport set.`); + log.debug(`Canvas resized to ${this.width}x${this.height} and viewport set.`); const imagePromises = savedState.layers.map((layerData, index) => { return new Promise((resolve) => { if (layerData.imageId) { - console.log(`Layer ${index}: Loading image with id: ${layerData.imageId}`); + log.debug(`Layer ${index}: Loading image with id: ${layerData.imageId}`); // Sprawdź, czy obraz jest już w pamięci podręcznej if (this.imageCache.has(layerData.imageId)) { - console.log(`Layer ${index}: Image found in cache.`); + log.debug(`Layer ${index}: Image found in cache.`); const imageSrc = this.imageCache.get(layerData.imageId); const img = new Image(); img.onload = () => { - console.log(`Layer ${index}: Image loaded successfully.`); + log.debug(`Layer ${index}: Image loaded successfully.`); const newLayer = {...layerData, image: img}; delete newLayer.imageId; resolve(newLayer); }; img.onerror = () => { - console.error(`Layer ${index}: Failed to load image from src.`); + log.error(`Layer ${index}: Failed to load image from src.`); resolve(null); }; img.src = imageSrc; @@ -161,54 +173,54 @@ export class Canvas { // Wczytaj obraz z IndexedDB getImage(layerData.imageId).then(imageSrc => { if (imageSrc) { - console.log(`Layer ${index}: Loading image from data:URL...`); + log.debug(`Layer ${index}: Loading image from data:URL...`); const img = new Image(); img.onload = () => { - console.log(`Layer ${index}: Image loaded successfully.`); + log.debug(`Layer ${index}: Image loaded successfully.`); this.imageCache.set(layerData.imageId, imageSrc); // Zapisz w pamięci podręcznej jako imageSrc const newLayer = {...layerData, image: img}; delete newLayer.imageId; resolve(newLayer); }; img.onerror = () => { - console.error(`Layer ${index}: Failed to load image from src.`); + log.error(`Layer ${index}: Failed to load image from src.`); resolve(null); }; img.src = imageSrc; } else { - console.error(`Layer ${index}: Image not found in IndexedDB.`); + log.error(`Layer ${index}: Image not found in IndexedDB.`); resolve(null); } }).catch(err => { - console.error(`Layer ${index}: Error loading image from IndexedDB:`, err); + log.error(`Layer ${index}: Error loading image from IndexedDB:`, err); resolve(null); }); } } else if (layerData.imageSrc) { // Obsługa starego formatu z imageSrc - console.log(`Layer ${index}: Found imageSrc, converting to new format with imageId.`); + log.info(`Layer ${index}: Found imageSrc, converting to new format with imageId.`); const imageId = generateUUID(); saveImage(imageId, layerData.imageSrc).then(() => { - console.log(`Layer ${index}: Image saved to IndexedDB with id: ${imageId}`); + log.info(`Layer ${index}: Image saved to IndexedDB with id: ${imageId}`); this.imageCache.set(imageId, layerData.imageSrc); // Zapisz w pamięci podręcznej jako imageSrc const img = new Image(); img.onload = () => { - console.log(`Layer ${index}: Image loaded successfully from imageSrc.`); + log.debug(`Layer ${index}: Image loaded successfully from imageSrc.`); const newLayer = {...layerData, image: img, imageId}; delete newLayer.imageSrc; resolve(newLayer); }; img.onerror = () => { - console.error(`Layer ${index}: Failed to load image from imageSrc.`); + log.error(`Layer ${index}: Failed to load image from imageSrc.`); resolve(null); }; img.src = layerData.imageSrc; }).catch(err => { - console.error(`Layer ${index}: Error saving image to IndexedDB:`, err); + log.error(`Layer ${index}: Error saving image to IndexedDB:`, err); resolve(null); }); } else { - console.error(`Layer ${index}: No imageId or imageSrc found, skipping layer.`); + log.error(`Layer ${index}: No imageId or imageSrc found, skipping layer.`); resolve(null); // Pomiń warstwy bez obrazu } }); @@ -216,35 +228,35 @@ export class Canvas { const loadedLayers = await Promise.all(imagePromises); this.layers = loadedLayers.filter(l => l !== null); - console.log(`Loaded ${this.layers.length} layers.`); + log.info(`Loaded ${this.layers.length} layers.`); if (this.layers.length === 0) { - console.warn("No valid layers loaded, state may be corrupted."); + log.warn("No valid layers loaded, state may be corrupted."); return false; } this.updateSelectionAfterHistory(); this.render(); - console.log("Canvas state loaded successfully from IndexedDB for node", this.node.id); + log.info("Canvas state loaded successfully from IndexedDB for node", this.node.id); return true; } catch (e) { - console.error("Error loading canvas state from IndexedDB:", e); - await removeCanvasState(this.node.id).catch(err => console.error("Failed to remove corrupted state:", err)); + log.error("Error loading canvas state from IndexedDB:", e); + await removeCanvasState(this.node.id).catch(err => log.error("Failed to remove corrupted state:", err)); return false; } } async saveStateToDB(immediate = false) { - console.log("Preparing to save state to IndexedDB for node:", this.node.id); + log.info("Preparing to save state to IndexedDB for node:", this.node.id); if (!this.node.id) { - console.error("Node ID is not available for saving state to DB."); + log.error("Node ID is not available for saving state to DB."); return; } // Oblicz sygnaturę obecnego stanu const currentStateSignature = this.getStateSignature(this.layers); if (this.lastSavedStateSignature === currentStateSignature) { - console.log("State unchanged, skipping save to IndexedDB."); + log.debug("State unchanged, skipping save to IndexedDB."); return; } @@ -259,7 +271,7 @@ export class Canvas { layers: await Promise.all(this.layers.map(async (layer, index) => { const newLayer = {...layer}; if (layer.image instanceof HTMLImageElement) { - console.log(`Layer ${index}: Using imageId instead of serializing image.`); + log.debug(`Layer ${index}: Using imageId instead of serializing image.`); if (!layer.imageId) { // Jeśli obraz nie ma jeszcze imageId, zapisz go do IndexedDB layer.imageId = generateUUID(); @@ -268,7 +280,7 @@ export class Canvas { } newLayer.imageId = layer.imageId; } else if (!layer.imageId) { - console.error(`Layer ${index}: No image or imageId found, skipping layer.`); + log.error(`Layer ${index}: No image or imageId found, skipping layer.`); return null; // Pomiń warstwy bez obrazu } delete newLayer.image; @@ -281,14 +293,14 @@ export class Canvas { // Filtruj warstwy, które nie mają obrazu state.layers = state.layers.filter(layer => layer !== null); if (state.layers.length === 0) { - console.warn("No valid layers to save, skipping save to IndexedDB."); + log.warn("No valid layers to save, skipping save to IndexedDB."); return; } await setCanvasState(this.node.id, state); - console.log("Canvas state saved to IndexedDB."); + log.info("Canvas state saved to IndexedDB."); this.lastSavedStateSignature = currentStateSignature; // Zaktualizuj sygnaturę zapisanego stanu } catch (e) { - console.error("Error saving canvas state to IndexedDB:", e); + log.error("Error saving canvas state to IndexedDB:", e); } }; @@ -302,10 +314,10 @@ export class Canvas { } async loadInitialState() { - console.log("Loading initial state for node:", this.node.id); + log.info("Loading initial state for node:", this.node.id); const loaded = await this.loadStateFromDB(); if (!loaded) { - console.log("No saved state found, initializing from node data."); + log.info("No saved state found, initializing from node data."); await this.initNodeData(); } this.saveState(); // Save initial state to undo stack @@ -508,16 +520,16 @@ export class Canvas { async copySelectedLayers() { if (this.selectedLayers.length === 0) return; this.internalClipboard = this.selectedLayers.map(layer => ({...layer})); - console.log(`Copied ${this.internalClipboard.length} layer(s) to internal clipboard.`); + log.info(`Copied ${this.internalClipboard.length} layer(s) to internal clipboard.`); try { const blob = await this.getFlattenedSelectionAsBlob(); if (blob) { const item = new ClipboardItem({'image/png': blob}); await navigator.clipboard.write([item]); - console.log("Flattened selection copied to the system clipboard."); + log.info("Flattened selection copied to the system clipboard."); } } catch (error) { - console.error("Failed to copy image to system clipboard:", error); + log.error("Failed to copy image to system clipboard:", error); } } @@ -541,14 +553,14 @@ export class Canvas { this.updateSelection(newLayers); this.render(); - console.log(`Pasted ${newLayers.length} layer(s).`); + log.info(`Pasted ${newLayers.length} layer(s).`); } async handlePaste() { try { if (!navigator.clipboard?.read) { - console.log("Browser does not support clipboard read API. Falling back to internal paste."); + log.info("Browser does not support clipboard read API. Falling back to internal paste."); this.pasteLayers(); return; } @@ -582,7 +594,7 @@ export class Canvas { } } catch (err) { - console.error("Paste operation failed, falling back to internal paste. Error:", err); + log.error("Paste operation failed, falling back to internal paste. Error:", err); this.pasteLayers(); } } @@ -1202,7 +1214,7 @@ export class Canvas { async addLayerWithImage(image, layerProps = {}) { try { - console.log("Adding layer with image:", image); + log.debug("Adding layer with image:", image); // Wygeneruj unikalny identyfikator dla obrazu i zapisz go do IndexedDB const imageId = generateUUID(); @@ -1228,10 +1240,10 @@ export class Canvas { this.render(); this.saveState(); - console.log("Layer added successfully"); + log.info("Layer added successfully"); return layer; } catch (error) { - console.error("Error adding layer:", error); + log.error("Error adding layer:", error); throw error; } } @@ -1732,13 +1744,13 @@ export class Canvas { async saveToServer(fileName) { // Sprawdź czy już trwa zapis if (this._saveInProgress) { - console.log(`[CANVAS_OUTPUT_LOG] Save already in progress, waiting...`); + log.warn(`Save already in progress, waiting...`); return this._saveInProgress; } - console.log(`[CANVAS_OUTPUT_LOG] Starting saveToServer with fileName: ${fileName}`); - console.log(`[CANVAS_OUTPUT_LOG] Canvas dimensions: ${this.width}x${this.height}`); - console.log(`[CANVAS_OUTPUT_LOG] Number of layers: ${this.layers.length}`); + log.info(`Starting saveToServer with fileName: ${fileName}`); + log.debug(`Canvas dimensions: ${this.width}x${this.height}`); + log.debug(`Number of layers: ${this.layers.length}`); // Utwórz Promise dla aktualnego zapisu this._saveInProgress = this._performSave(fileName); @@ -1748,6 +1760,7 @@ export class Canvas { return result; } finally { this._saveInProgress = null; + log.debug(`Save completed, lock released`); } } @@ -1772,15 +1785,15 @@ export class Canvas { maskCtx.fillStyle = '#ffffff'; // Białe tło dla wolnych przestrzeni maskCtx.fillRect(0, 0, this.width, this.height); - console.log(`[CANVAS_OUTPUT_LOG] Canvas contexts created, starting layer rendering`); + log.debug(`Canvas contexts created, starting layer rendering`); // Rysowanie warstw const sortedLayers = this.layers.sort((a, b) => a.zIndex - b.zIndex); - console.log(`[CANVAS_OUTPUT_LOG] Processing ${sortedLayers.length} layers in order`); + log.debug(`Processing ${sortedLayers.length} layers in order`); sortedLayers.forEach((layer, index) => { - console.log(`[CANVAS_OUTPUT_LOG] Processing layer ${index}: zIndex=${layer.zIndex}, size=${layer.width}x${layer.height}, pos=(${layer.x},${layer.y})`); - console.log(`[CANVAS_OUTPUT_LOG] Layer ${index}: blendMode=${layer.blendMode || 'normal'}, opacity=${layer.opacity !== undefined ? layer.opacity : 1}`); + log.debug(`Processing layer ${index}: zIndex=${layer.zIndex}, size=${layer.width}x${layer.height}, pos=(${layer.x},${layer.y})`); + log.debug(`Layer ${index}: blendMode=${layer.blendMode || 'normal'}, opacity=${layer.opacity !== undefined ? layer.opacity : 1}`); tempCtx.save(); tempCtx.globalCompositeOperation = layer.blendMode || 'normal'; @@ -1790,7 +1803,7 @@ export class Canvas { tempCtx.drawImage(layer.image, -layer.width / 2, -layer.height / 2, layer.width, layer.height); tempCtx.restore(); - console.log(`[CANVAS_OUTPUT_LOG] Layer ${index} rendered successfully`); + log.debug(`Layer ${index} rendered successfully`); maskCtx.save(); maskCtx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); @@ -1875,10 +1888,10 @@ export class Canvas { // Zapisz obraz bez maski const fileNameWithoutMask = fileName.replace('.png', '_without_mask.png'); - console.log(`[CANVAS_OUTPUT_LOG] Saving image without mask as: ${fileNameWithoutMask}`); + log.info(`Saving image without mask as: ${fileNameWithoutMask}`); tempCanvas.toBlob(async (blobWithoutMask) => { - console.log(`[CANVAS_OUTPUT_LOG] Created blob for image without mask, size: ${blobWithoutMask.size} bytes`); + log.debug(`Created blob for image without mask, size: ${blobWithoutMask.size} bytes`); const formDataWithoutMask = new FormData(); formDataWithoutMask.append("image", blobWithoutMask, fileNameWithoutMask); formDataWithoutMask.append("overwrite", "true"); @@ -1888,16 +1901,16 @@ export class Canvas { method: "POST", body: formDataWithoutMask, }); - console.log(`[CANVAS_OUTPUT_LOG] Image without mask upload response: ${response.status}`); + log.debug(`Image without mask upload response: ${response.status}`); } catch (error) { - console.error(`[CANVAS_OUTPUT_LOG] Error uploading image without mask:`, error); + log.error(`Error uploading image without mask:`, error); } }, "image/png"); // Zapisz obraz z maską - console.log(`[CANVAS_OUTPUT_LOG] Saving main image as: ${fileName}`); + log.info(`Saving main image as: ${fileName}`); tempCanvas.toBlob(async (blob) => { - console.log(`[CANVAS_OUTPUT_LOG] Created blob for main image, size: ${blob.size} bytes`); + log.debug(`Created blob for main image, size: ${blob.size} bytes`); const formData = new FormData(); formData.append("image", blob, fileName); formData.append("overwrite", "true"); @@ -1907,14 +1920,14 @@ export class Canvas { method: "POST", body: formData, }); - console.log(`[CANVAS_OUTPUT_LOG] Main image upload response: ${resp.status}`); + log.debug(`Main image upload response: ${resp.status}`); if (resp.status === 200) { const maskFileName = fileName.replace('.png', '_mask.png'); - console.log(`[CANVAS_OUTPUT_LOG] Saving mask as: ${maskFileName}`); + log.info(`Saving mask as: ${maskFileName}`); maskCanvas.toBlob(async (maskBlob) => { - console.log(`[CANVAS_OUTPUT_LOG] Created blob for mask, size: ${maskBlob.size} bytes`); + log.debug(`Created blob for mask, size: ${maskBlob.size} bytes`); const maskFormData = new FormData(); maskFormData.append("image", maskBlob, maskFileName); maskFormData.append("overwrite", "true"); @@ -1924,28 +1937,28 @@ export class Canvas { method: "POST", body: maskFormData, }); - console.log(`[CANVAS_OUTPUT_LOG] Mask upload response: ${maskResp.status}`); + log.debug(`Mask upload response: ${maskResp.status}`); if (maskResp.status === 200) { const data = await resp.json(); this.widget.value = data.name; - console.log(`[CANVAS_OUTPUT_LOG] All files saved successfully, widget value set to: ${data.name}`); + log.info(`All files saved successfully, widget value set to: ${data.name}`); resolve(true); } else { - console.error(`[CANVAS_OUTPUT_LOG] Error saving mask: ${maskResp.status}`); + log.error(`Error saving mask: ${maskResp.status}`); resolve(false); } } catch (error) { - console.error(`[CANVAS_OUTPUT_LOG] Error saving mask:`, error); + log.error(`Error saving mask:`, error); resolve(false); } }, "image/png"); } else { - console.error(`[CANVAS_OUTPUT_LOG] Main image upload failed: ${resp.status} - ${resp.statusText}`); + log.error(`Main image upload failed: ${resp.status} - ${resp.statusText}`); resolve(false); } } catch (error) { - console.error(`[CANVAS_OUTPUT_LOG] Error uploading main image:`, error); + log.error(`Error uploading main image:`, error); resolve(false); } }, "image/png"); @@ -2241,7 +2254,7 @@ export class Canvas { return dataUrl; } catch (error) { - console.error("Error getting layer image data:", error); + log.error("Error getting layer image data:", error); throw error; } } @@ -2289,7 +2302,7 @@ export class Canvas { async addInputToCanvas(inputImage, inputMask) { try { - console.log("Adding input to canvas:", {inputImage}); + log.debug("Adding input to canvas:", {inputImage}); const tempCanvas = document.createElement('canvas'); const tempCtx = tempCanvas.getContext('2d'); @@ -2326,18 +2339,18 @@ export class Canvas { layer.mask = inputMask.data; } - console.log("Layer added successfully"); + log.info("Layer added successfully"); return true; } catch (error) { - console.error("Error in addInputToCanvas:", error); + log.error("Error in addInputToCanvas:", error); throw error; } } async convertTensorToImage(tensor) { try { - console.log("Converting tensor to image:", tensor); + log.debug("Converting tensor to image:", tensor); if (!tensor || !tensor.data || !tensor.width || !tensor.height) { throw new Error("Invalid tensor data"); @@ -2363,7 +2376,7 @@ export class Canvas { img.src = canvas.toDataURL(); }); } catch (error) { - console.error("Error converting tensor to image:", error); + log.error("Error converting tensor to image:", error); throw error; } } @@ -2383,10 +2396,10 @@ export class Canvas { async initNodeData() { try { - console.log("Starting node data initialization..."); + log.info("Starting node data initialization..."); if (!this.node || !this.node.inputs) { - console.log("Node or inputs not ready"); + log.debug("Node or inputs not ready"); return this.scheduleDataCheck(); } @@ -2395,11 +2408,11 @@ export class Canvas { const imageData = app.nodeOutputs[imageLinkId]; if (imageData) { - console.log("Found image data:", imageData); + log.debug("Found image data:", imageData); await this.processImageData(imageData); this.dataInitialized = true; } else { - console.log("Image data not available yet"); + log.debug("Image data not available yet"); return this.scheduleDataCheck(); } } @@ -2409,13 +2422,13 @@ export class Canvas { const maskData = app.nodeOutputs[maskLinkId]; if (maskData) { - console.log("Found mask data:", maskData); + log.debug("Found mask data:", maskData); await this.processMaskData(maskData); } } } catch (error) { - console.error("Error in initNodeData:", error); + log.error("Error in initNodeData:", error); return this.scheduleDataCheck(); } } @@ -2437,7 +2450,7 @@ export class Canvas { try { if (!imageData) return; - console.log("Processing image data:", { + log.debug("Processing image data:", { type: typeof imageData, isArray: Array.isArray(imageData), shape: imageData.shape, @@ -2465,10 +2478,10 @@ export class Canvas { const image = await this.createImageFromData(convertedData); this.addScaledLayer(image, scale); - console.log("Image layer added successfully with scale:", scale); + log.info("Image layer added successfully with scale:", scale); } } catch (error) { - console.error("Error processing image data:", error); + log.error("Error processing image data:", error); throw error; } } @@ -2494,13 +2507,13 @@ export class Canvas { this.selectedLayer = layer; this.render(); - console.log("Scaled layer added:", { + log.debug("Scaled layer added:", { originalSize: `${image.width}x${image.height}`, scaledSize: `${scaledWidth}x${scaledHeight}`, scale: scale }); } catch (error) { - console.error("Error adding scaled layer:", error); + log.error("Error adding scaled layer:", error); throw error; } } @@ -2512,7 +2525,7 @@ export class Canvas { const width = shape[2]; const channels = shape[3]; - console.log("Converting tensor:", { + log.debug("Converting tensor:", { shape: shape, dataRange: { min: tensor.min_val, @@ -2543,7 +2556,7 @@ export class Canvas { imageData.data.set(data); return imageData; } catch (error) { - console.error("Error converting tensor:", error); + log.error("Error converting tensor:", error); return null; } } @@ -2569,20 +2582,20 @@ export class Canvas { await this.initNodeData(); return; } catch (error) { - console.warn(`Retry ${i + 1}/${maxRetries} failed:`, error); + log.warn(`Retry ${i + 1}/${maxRetries} failed:`, error); if (i < maxRetries - 1) { await new Promise(resolve => setTimeout(resolve, delay)); } } } - console.error("Failed to load data after", maxRetries, "retries"); + log.error("Failed to load data after", maxRetries, "retries"); } async processMaskData(maskData) { try { if (!maskData) return; - console.log("Processing mask data:", maskData); + log.debug("Processing mask data:", maskData); if (Array.isArray(maskData)) { maskData = maskData[0]; @@ -2596,10 +2609,10 @@ export class Canvas { const maskTensor = await this.convertTensorToMask(maskData); this.selectedLayer.mask = maskTensor; this.render(); - console.log("Mask applied to selected layer"); + log.info("Mask applied to selected layer"); } } catch (error) { - console.error("Error processing mask data:", error); + log.error("Error processing mask data:", error); } } @@ -2614,7 +2627,7 @@ export class Canvas { async importImage(cacheData) { try { - console.log("Starting image import with cache data"); + log.info("Starting image import with cache data"); const img = await this.loadImageFromCache(cacheData.image); const mask = cacheData.mask ? await this.loadImageFromCache(cacheData.mask) : null; @@ -2667,18 +2680,18 @@ export class Canvas { this.render(); } catch (error) { - console.error('Error importing image:', error); + log.error('Error importing image:', error); } } async importLatestImage() { try { - console.log("Fetching latest image from server..."); + log.info("Fetching latest image from server..."); const response = await fetch('/ycnode/get_latest_image'); const result = await response.json(); if (result.success && result.image_data) { - console.log("Latest image received, adding to canvas."); + log.info("Latest image received, adding to canvas."); const img = new Image(); await new Promise((resolve, reject) => { img.onload = resolve; @@ -2692,13 +2705,13 @@ export class Canvas { width: this.width, height: this.height, }); - console.log("Latest image imported and placed on canvas successfully."); + log.info("Latest image imported and placed on canvas successfully."); return true; } else { throw new Error(result.error || "Failed to fetch the latest image."); } } catch (error) { - console.error("Error importing latest image:", error); + log.error("Error importing latest image:", error); alert(`Failed to import latest image: ${error.message}`); return false; } diff --git a/js/Canvas_view.js b/js/Canvas_view.js index b29ef34..2c241c2 100644 --- a/js/Canvas_view.js +++ b/js/Canvas_view.js @@ -5,7 +5,19 @@ import {$el} from "../../scripts/ui.js"; import {Canvas} from "./Canvas.js"; import {clearAllCanvasStates} from "./db.js"; import {ImageCache} from "./ImageCache.js"; -import { validateImageData, convertImageData, applyMaskToImageData, prepareImageForCanvas } from "./ImageUtils.js"; +import {validateImageData, convertImageData, applyMaskToImageData, prepareImageForCanvas} from "./ImageUtils.js"; +import {logger, LogLevel} from "./logger.js"; + +// Inicjalizacja loggera dla modułu Canvas_view +const log = { + debug: (...args) => logger.debug('Canvas_view', ...args), + info: (...args) => logger.info('Canvas_view', ...args), + warn: (...args) => logger.warn('Canvas_view', ...args), + error: (...args) => logger.error('Canvas_view', ...args) +}; + +// Konfiguracja loggera dla modułu Canvas_view +logger.setModuleLevel('Canvas_view', LogLevel.INFO); // Domyślnie INFO, można zmienić na DEBUG dla szczegółowych logów async function createCanvasWidget(node, widget, app) { const canvas = new Canvas(node, widget); @@ -580,7 +592,7 @@ async function createCanvasWidget(node, widget, app) { await canvas.saveToServer(widget.value); app.graph.runStep(); } catch (error) { - console.error("Matting error:", error); + log.error("Matting error:", error); alert(`Error during matting process: ${error.message}`); } finally { button.classList.remove('loading'); @@ -683,7 +695,7 @@ async function createCanvasWidget(node, widget, app) { await clearAllCanvasStates(); alert("Canvas cache cleared successfully!"); } catch (e) { - console.error("Failed to clear canvas cache:", e); + log.error("Failed to clear canvas cache:", e); alert("Error clearing canvas cache. Check the console for details."); } } @@ -788,18 +800,18 @@ async function createCanvasWidget(node, widget, app) { } }, [controlPanel, canvasContainer]); const handleFileLoad = async (file) => { - console.log("File dropped:", file.name); + log.info("File dropped:", file.name); if (!file.type.startsWith('image/')) { - console.log("Dropped file is not an image."); + log.info("Dropped file is not an image."); return; } const reader = new FileReader(); reader.onload = async (event) => { - console.log("FileReader finished loading dropped file as data:URL."); + log.debug("FileReader finished loading dropped file as data:URL."); const img = new Image(); img.onload = async () => { - console.log("Image object loaded from dropped data:URL."); + log.debug("Image object loaded from dropped data:URL."); const scale = Math.min( canvas.width / img.width, canvas.height / img.height @@ -821,7 +833,7 @@ async function createCanvasWidget(node, widget, app) { canvas.updateSelection([layer]); canvas.render(); canvas.saveState(); - console.log("Dropped layer added and state saved."); + log.info("Dropped layer added and state saved."); await updateOutput(); }; img.src = event.target.result; @@ -885,7 +897,7 @@ async function createCanvasWidget(node, widget, app) { originalParent = mainContainer.parentNode; if (!originalParent) { - console.error("Could not find original parent of the canvas container!"); + log.error("Could not find original parent of the canvas container!"); return; } @@ -910,13 +922,13 @@ async function createCanvasWidget(node, widget, app) { let executionInProgress = false; api.addEventListener("execution_start", async () => { - console.log(`[CANVAS_VIEW_LOG] Execution start event for node ${node.id}`); - console.log(`[CANVAS_VIEW_LOG] Widget value: ${widget.value}`); - console.log(`[CANVAS_VIEW_LOG] Node inputs: ${node.inputs?.length || 0}`); + log.info(`Execution start event for node ${node.id}`); + log.debug(`Widget value: ${widget.value}`); + log.debug(`Node inputs: ${node.inputs?.length || 0}`); // Sprawdź czy już trwa wykonanie if (executionInProgress) { - console.log(`[CANVAS_VIEW_LOG] Execution already in progress, skipping...`); + log.warn(`Execution already in progress, skipping...`); return; } @@ -925,34 +937,34 @@ async function createCanvasWidget(node, widget, app) { try { await canvas.saveToServer(widget.value); - console.log(`[CANVAS_VIEW_LOG] Canvas saved to server`); + log.info(`Canvas saved to server`); if (node.inputs[0]?.link) { const linkId = node.inputs[0].link; const inputData = app.nodeOutputs[linkId]; - console.log(`[CANVAS_VIEW_LOG] Input link ${linkId} has data: ${!!inputData}`); + log.debug(`Input link ${linkId} has data: ${!!inputData}`); if (inputData) { imageCache.set(linkId, inputData); - console.log(`[CANVAS_VIEW_LOG] Input data cached for link ${linkId}`); + log.debug(`Input data cached for link ${linkId}`); } } else { - console.log(`[CANVAS_VIEW_LOG] No input link found`); + log.debug(`No input link found`); } } catch (error) { - console.error(`[CANVAS_VIEW_LOG] Error during execution:`, error); + log.error(`Error during execution:`, error); } finally { // Zwolnij flagę wykonania executionInProgress = false; - console.log(`[CANVAS_VIEW_LOG] Execution completed, flag released`); + log.debug(`Execution completed, flag released`); } }); const originalSaveToServer = canvas.saveToServer; canvas.saveToServer = async function (fileName) { - console.log(`[CANVAS_VIEW_LOG] saveToServer called with fileName: ${fileName}`); - console.log(`[CANVAS_VIEW_LOG] Current execution context - node ID: ${node.id}`); + log.debug(`saveToServer called with fileName: ${fileName}`); + log.debug(`Current execution context - node ID: ${node.id}`); const result = await originalSaveToServer.call(this, fileName); - console.log(`[CANVAS_VIEW_LOG] saveToServer completed, result: ${result}`); + log.debug(`saveToServer completed, result: ${result}`); return result; }; @@ -975,11 +987,11 @@ app.registerExtension({ if (nodeType.comfyClass === "CanvasNode") { const onNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = async function () { - console.log("CanvasNode created, ID:", this.id); + log.info("CanvasNode created, ID:", this.id); const r = onNodeCreated?.apply(this, arguments); const widget = this.widgets.find(w => w.name === "canvas_image"); - console.log("Found canvas_image widget:", widget); + log.debug("Found canvas_image widget:", widget); await createCanvasWidget(this, widget, app); return r; @@ -1017,7 +1029,7 @@ app.registerExtension({ window.open(url, '_blank'); setTimeout(() => URL.revokeObjectURL(url), 1000); } catch (e) { - console.error("Error opening image:", e); + log.error("Error opening image:", e); } }, }, @@ -1028,9 +1040,9 @@ app.registerExtension({ const blob = await self.canvasWidget.getFlattenedCanvasAsBlob(); const item = new ClipboardItem({'image/png': blob}); await navigator.clipboard.write([item]); - console.log("Image copied to clipboard."); + log.info("Image copied to clipboard."); } catch (e) { - console.error("Error copying image:", e); + log.error("Error copying image:", e); alert("Failed to copy image to clipboard."); } }, @@ -1049,7 +1061,7 @@ app.registerExtension({ document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 1000); } catch (e) { - console.error("Error saving image:", e); + log.error("Error saving image:", e); } }, }, diff --git a/js/ImageCache.js b/js/ImageCache.js index c7c6cf8..0654df3 100644 --- a/js/ImageCache.js +++ b/js/ImageCache.js @@ -1,16 +1,29 @@ +import {logger, LogLevel} from "./logger.js"; + +// Inicjalizacja loggera dla modułu ImageCache +const log = { + debug: (...args) => logger.debug('ImageCache', ...args), + info: (...args) => logger.info('ImageCache', ...args), + warn: (...args) => logger.warn('ImageCache', ...args), + error: (...args) => logger.error('ImageCache', ...args) +}; + +// Konfiguracja loggera dla modułu ImageCache +logger.setModuleLevel('ImageCache', LogLevel.INFO); + export class ImageCache { constructor() { this.cache = new Map(); } set(key, imageData) { - console.log("Caching image data for key:", key); + log.info("Caching image data for key:", key); this.cache.set(key, imageData); } get(key) { const data = this.cache.get(key); - console.log("Retrieved cached data for key:", key, !!data); + log.debug("Retrieved cached data for key:", key, !!data); return data; } @@ -19,7 +32,7 @@ export class ImageCache { } clear() { - console.log("Clearing image cache"); + log.info("Clearing image cache"); this.cache.clear(); } } \ No newline at end of file diff --git a/js/ImageUtils.js b/js/ImageUtils.js index cf1d441..e76fcf9 100644 --- a/js/ImageUtils.js +++ b/js/ImageUtils.js @@ -1,5 +1,18 @@ +import {logger, LogLevel} from "./logger.js"; + +// Inicjalizacja loggera dla modułu ImageUtils +const log = { + debug: (...args) => logger.debug('ImageUtils', ...args), + info: (...args) => logger.info('ImageUtils', ...args), + warn: (...args) => logger.warn('ImageUtils', ...args), + error: (...args) => logger.error('ImageUtils', ...args) +}; + +// Konfiguracja loggera dla modułu ImageUtils +logger.setModuleLevel('ImageUtils', LogLevel.INFO); + export function validateImageData(data) { - console.log("Validating data structure:", { + log.debug("Validating data structure:", { hasData: !!data, type: typeof data, isArray: Array.isArray(data), @@ -10,22 +23,22 @@ export function validateImageData(data) { }); if (!data) { - console.log("Data is null or undefined"); + log.info("Data is null or undefined"); return false; } if (Array.isArray(data)) { - console.log("Data is array, getting first element"); + log.debug("Data is array, getting first element"); data = data[0]; } if (!data || typeof data !== 'object') { - console.log("Invalid data type"); + log.info("Invalid data type"); return false; } if (!data.data) { - console.log("Missing data property"); + log.info("Missing data property"); return false; } @@ -33,7 +46,7 @@ export function validateImageData(data) { try { data.data = new Float32Array(data.data); } catch (e) { - console.log("Failed to convert data to Float32Array:", e); + log.error("Failed to convert data to Float32Array:", e); return false; } } @@ -42,7 +55,7 @@ export function validateImageData(data) { } export function convertImageData(data) { - console.log("Converting image data:", data); + log.info("Converting image data:", data); if (Array.isArray(data)) { data = data[0]; @@ -54,7 +67,7 @@ export function convertImageData(data) { const channels = shape[3]; const floatData = new Float32Array(data.data); - console.log("Processing dimensions:", {height, width, channels}); + log.debug("Processing dimensions:", {height, width, channels}); const rgbaData = new Uint8ClampedArray(width * height * 4); @@ -80,7 +93,7 @@ export function convertImageData(data) { } export function applyMaskToImageData(imageData, maskData) { - console.log("Applying mask to image data"); + log.info("Applying mask to image data"); const rgbaData = new Uint8ClampedArray(imageData.data); const width = imageData.width; @@ -89,7 +102,7 @@ export function applyMaskToImageData(imageData, maskData) { const maskShape = maskData.shape; const maskFloatData = new Float32Array(maskData.data); - console.log(`Applying mask of shape: ${maskShape}`); + log.debug(`Applying mask of shape: ${maskShape}`); for (let h = 0; h < height; h++) { for (let w = 0; w < width; w++) { @@ -101,7 +114,7 @@ export function applyMaskToImageData(imageData, maskData) { } } - console.log("Mask application completed"); + log.info("Mask application completed"); return { data: rgbaData, @@ -111,7 +124,7 @@ export function applyMaskToImageData(imageData, maskData) { } export function prepareImageForCanvas(inputImage) { - console.log("Preparing image for canvas:", inputImage); + log.info("Preparing image for canvas:", inputImage); try { if (Array.isArray(inputImage)) { @@ -128,7 +141,7 @@ export function prepareImageForCanvas(inputImage) { const channels = shape[3]; const floatData = new Float32Array(inputImage.data); - console.log("Image dimensions:", {height, width, channels}); + log.debug("Image dimensions:", {height, width, channels}); const rgbaData = new Uint8ClampedArray(width * height * 4); @@ -152,7 +165,7 @@ export function prepareImageForCanvas(inputImage) { height: height }; } catch (error) { - console.error("Error preparing image:", error); + log.error("Error preparing image:", error); throw new Error(`Failed to prepare image: ${error.message}`); } } \ No newline at end of file diff --git a/js/Mask_tool.js b/js/Mask_tool.js index bb4ee1d..401b396 100644 --- a/js/Mask_tool.js +++ b/js/Mask_tool.js @@ -1,3 +1,16 @@ +import {logger, LogLevel} from "./logger.js"; + +// Inicjalizacja loggera dla modułu Mask_tool +const log = { + debug: (...args) => logger.debug('Mask_tool', ...args), + info: (...args) => logger.info('Mask_tool', ...args), + warn: (...args) => logger.warn('Mask_tool', ...args), + error: (...args) => logger.error('Mask_tool', ...args) +}; + +// Konfiguracja loggera dla modułu Mask_tool +logger.setModuleLevel('Mask_tool', LogLevel.INFO); + export class MaskTool { constructor(canvasInstance) { this.canvasInstance = canvasInstance; @@ -28,13 +41,13 @@ export class MaskTool { activate() { this.isActive = true; this.canvasInstance.interaction.mode = 'drawingMask'; - console.log("Mask tool activated"); + log.info("Mask tool activated"); } deactivate() { this.isActive = false; this.canvasInstance.interaction.mode = 'none'; - console.log("Mask tool deactivated"); + log.info("Mask tool deactivated"); } setBrushSize(size) { diff --git a/js/db.js b/js/db.js index 61d99e7..6593d82 100644 --- a/js/db.js +++ b/js/db.js @@ -1,3 +1,16 @@ +import {logger, LogLevel} from "./logger.js"; + +// Inicjalizacja loggera dla modułu db +const log = { + debug: (...args) => logger.debug('db', ...args), + info: (...args) => logger.info('db', ...args), + warn: (...args) => logger.warn('db', ...args), + error: (...args) => logger.error('db', ...args) +}; + +// Konfiguracja loggera dla modułu db +logger.setModuleLevel('db', LogLevel.INFO); + const DB_NAME = 'CanvasNodeDB'; const STATE_STORE_NAME = 'CanvasState'; const IMAGE_STORE_NAME = 'CanvasImages'; @@ -12,37 +25,37 @@ function openDB() { return; } - console.log("Opening IndexedDB..."); + log.info("Opening IndexedDB..."); const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = (event) => { - console.error("IndexedDB error:", event.target.error); + log.error("IndexedDB error:", event.target.error); reject("Error opening IndexedDB."); }; request.onsuccess = (event) => { db = event.target.result; - console.log("IndexedDB opened successfully."); + log.info("IndexedDB opened successfully."); resolve(db); }; request.onupgradeneeded = (event) => { - console.log("Upgrading IndexedDB..."); + log.info("Upgrading IndexedDB..."); const db = event.target.result; if (!db.objectStoreNames.contains(STATE_STORE_NAME)) { db.createObjectStore(STATE_STORE_NAME, {keyPath: 'id'}); - console.log("Object store created:", STATE_STORE_NAME); + log.info("Object store created:", STATE_STORE_NAME); } if (!db.objectStoreNames.contains(IMAGE_STORE_NAME)) { db.createObjectStore(IMAGE_STORE_NAME, {keyPath: 'imageId'}); - console.log("Object store created:", IMAGE_STORE_NAME); + log.info("Object store created:", IMAGE_STORE_NAME); } }; }); } export async function getCanvasState(id) { - console.log(`DB: Getting state for id: ${id}`); + log.info(`Getting state for id: ${id}`); const db = await openDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([STATE_STORE_NAME], 'readonly'); @@ -50,19 +63,19 @@ export async function getCanvasState(id) { const request = store.get(id); request.onerror = (event) => { - console.error("DB: Error getting canvas state:", event.target.error); + log.error("Error getting canvas state:", event.target.error); reject("Error getting state."); }; request.onsuccess = (event) => { - console.log(`DB: Get success for id: ${id}`, event.target.result ? 'found' : 'not found'); + log.debug(`Get success for id: ${id}`, event.target.result ? 'found' : 'not found'); resolve(event.target.result ? event.target.result.state : null); }; }); } export async function setCanvasState(id, state) { - console.log(`DB: Setting state for id: ${id}`); + log.info(`Setting state for id: ${id}`); const db = await openDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); @@ -70,19 +83,19 @@ export async function setCanvasState(id, state) { const request = store.put({id, state}); request.onerror = (event) => { - console.error("DB: Error setting canvas state:", event.target.error); + log.error("Error setting canvas state:", event.target.error); reject("Error setting state."); }; request.onsuccess = () => { - console.log(`DB: Set success for id: ${id}`); + log.debug(`Set success for id: ${id}`); resolve(); }; }); } export async function removeCanvasState(id) { - console.log(`DB: Removing state for id: ${id}`); + log.info(`Removing state for id: ${id}`); const db = await openDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); @@ -90,19 +103,19 @@ export async function removeCanvasState(id) { const request = store.delete(id); request.onerror = (event) => { - console.error("DB: Error removing canvas state:", event.target.error); + log.error("Error removing canvas state:", event.target.error); reject("Error removing state."); }; request.onsuccess = () => { - console.log(`DB: Remove success for id: ${id}`); + log.debug(`Remove success for id: ${id}`); resolve(); }; }); } export async function saveImage(imageId, imageSrc) { - console.log(`DB: Saving image with id: ${imageId}`); + log.info(`Saving image with id: ${imageId}`); const db = await openDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite'); @@ -110,19 +123,19 @@ export async function saveImage(imageId, imageSrc) { const request = store.put({imageId, imageSrc}); request.onerror = (event) => { - console.error("DB: Error saving image:", event.target.error); + log.error("Error saving image:", event.target.error); reject("Error saving image."); }; request.onsuccess = () => { - console.log(`DB: Image saved successfully for id: ${imageId}`); + log.debug(`Image saved successfully for id: ${imageId}`); resolve(); }; }); } export async function getImage(imageId) { - console.log(`DB: Getting image with id: ${imageId}`); + log.info(`Getting image with id: ${imageId}`); const db = await openDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([IMAGE_STORE_NAME], 'readonly'); @@ -130,19 +143,19 @@ export async function getImage(imageId) { const request = store.get(imageId); request.onerror = (event) => { - console.error("DB: Error getting image:", event.target.error); + log.error("Error getting image:", event.target.error); reject("Error getting image."); }; request.onsuccess = (event) => { - console.log(`DB: Get image success for id: ${imageId}`, event.target.result ? 'found' : 'not found'); + log.debug(`Get image success for id: ${imageId}`, event.target.result ? 'found' : 'not found'); resolve(event.target.result ? event.target.result.imageSrc : null); }; }); } export async function removeImage(imageId) { - console.log(`DB: Removing image with id: ${imageId}`); + log.info(`Removing image with id: ${imageId}`); const db = await openDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite'); @@ -150,19 +163,19 @@ export async function removeImage(imageId) { const request = store.delete(imageId); request.onerror = (event) => { - console.error("DB: Error removing image:", event.target.error); + log.error("Error removing image:", event.target.error); reject("Error removing image."); }; request.onsuccess = () => { - console.log(`DB: Remove image success for id: ${imageId}`); + log.debug(`Remove image success for id: ${imageId}`); resolve(); }; }); } export async function clearAllCanvasStates() { - console.log("DB: Clearing all canvas states..."); + log.info("Clearing all canvas states..."); const db = await openDB(); return new Promise((resolve, reject) => { const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); @@ -170,12 +183,12 @@ export async function clearAllCanvasStates() { const request = store.clear(); request.onerror = (event) => { - console.error("DB: Error clearing canvas states:", event.target.error); + log.error("Error clearing canvas states:", event.target.error); reject("Error clearing states."); }; request.onsuccess = () => { - console.log("DB: All canvas states cleared successfully."); + log.info("All canvas states cleared successfully."); resolve(); }; }); diff --git a/js/logger.js b/js/logger.js new file mode 100644 index 0000000..074f415 --- /dev/null +++ b/js/logger.js @@ -0,0 +1,371 @@ +/** + * Logger - Centralny system logowania dla ComfyUI-LayerForge + * + * Funkcje: + * - Różne poziomy logowania (DEBUG, INFO, WARN, ERROR) + * - Możliwość włączania/wyłączania logów globalnie lub per moduł + * - Kolorowe logi w konsoli + * - Możliwość zapisywania logów do localStorage + * - Możliwość eksportu logów + */ + +// Poziomy logowania +export const LogLevel = { + DEBUG: 0, + INFO: 1, + WARN: 2, + ERROR: 3, + NONE: 4 +}; + +// Konfiguracja domyślna +const DEFAULT_CONFIG = { + globalLevel: LogLevel.INFO, // Domyślny poziom logowania + moduleSettings: {}, // Ustawienia per moduł + useColors: true, // Kolorowe logi w konsoli + saveToStorage: false, // Zapisywanie logów do localStorage + maxStoredLogs: 1000, // Maksymalna liczba przechowywanych logów + timestampFormat: 'HH:mm:ss', // Format znacznika czasu + storageKey: 'layerforge_logs' // Klucz localStorage +}; + +// Kolory dla różnych poziomów logowania +const COLORS = { + [LogLevel.DEBUG]: '#9e9e9e', // Szary + [LogLevel.INFO]: '#2196f3', // Niebieski + [LogLevel.WARN]: '#ff9800', // Pomarańczowy + [LogLevel.ERROR]: '#f44336', // Czerwony +}; + +// Nazwy poziomów logowania +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; + + // Załaduj konfigurację z localStorage, jeśli istnieje + this.loadConfig(); + } + + /** + * Konfiguracja loggera + * @param {Object} config - Obiekt konfiguracyjny + */ + configure(config) { + this.config = { ...this.config, ...config }; + this.saveConfig(); + return this; + } + + /** + * Włącz/wyłącz logger globalnie + * @param {boolean} enabled - Czy logger ma być włączony + */ + setEnabled(enabled) { + this.enabled = enabled; + return this; + } + + /** + * Ustaw globalny poziom logowania + * @param {LogLevel} level - Poziom logowania + */ + setGlobalLevel(level) { + this.config.globalLevel = level; + this.saveConfig(); + return this; + } + + /** + * Ustaw poziom logowania dla konkretnego modułu + * @param {string} module - Nazwa modułu + * @param {LogLevel} level - Poziom logowania + */ + setModuleLevel(module, level) { + this.config.moduleSettings[module] = level; + this.saveConfig(); + return this; + } + + /** + * Sprawdź, czy dany poziom logowania jest aktywny dla modułu + * @param {string} module - Nazwa modułu + * @param {LogLevel} level - Poziom logowania do sprawdzenia + * @returns {boolean} - Czy poziom jest aktywny + */ + isLevelEnabled(module, level) { + if (!this.enabled) return false; + + // Sprawdź ustawienia modułu, jeśli istnieją + if (this.config.moduleSettings[module] !== undefined) { + return level >= this.config.moduleSettings[module]; + } + + // W przeciwnym razie użyj globalnego poziomu + return level >= this.config.globalLevel; + } + + /** + * Formatuj znacznik czasu + * @returns {string} - Sformatowany znacznik czasu + */ + formatTimestamp() { + const now = new Date(); + const format = this.config.timestampFormat; + + // Prosty formatter - można rozszerzyć o więcej opcji + return format + .replace('HH', String(now.getHours()).padStart(2, '0')) + .replace('mm', String(now.getMinutes()).padStart(2, '0')) + .replace('ss', String(now.getSeconds()).padStart(2, '0')) + .replace('SSS', String(now.getMilliseconds()).padStart(3, '0')); + } + + /** + * Zapisz log + * @param {string} module - Nazwa modułu + * @param {LogLevel} level - Poziom logowania + * @param {Array} args - Argumenty do zalogowania + */ + log(module, level, ...args) { + if (!this.isLevelEnabled(module, level)) return; + + const timestamp = this.formatTimestamp(); + const levelName = LEVEL_NAMES[level]; + + // Przygotuj dane logu + const logData = { + timestamp, + module, + level, + levelName, + args, + time: new Date() + }; + + // Dodaj do pamięci, jeśli zapisywanie jest włączone + if (this.config.saveToStorage) { + this.logs.push(logData); + + // Ogranicz liczbę przechowywanych logów + if (this.logs.length > this.config.maxStoredLogs) { + this.logs.shift(); + } + + // Zapisz do localStorage + this.saveLogs(); + } + + // Wyświetl w konsoli + this.printToConsole(logData); + } + + /** + * Wyświetl log w konsoli + * @param {Object} logData - Dane logu + */ + printToConsole(logData) { + const { timestamp, module, level, levelName, args } = logData; + + // Przygotuj prefix logu + const prefix = `[${timestamp}] [${module}] [${levelName}]`; + + // Użyj kolorów, jeśli są włączone + if (this.config.useColors && typeof console.log === 'function') { + const color = COLORS[level] || '#000000'; + console.log(`%c${prefix}`, `color: ${color}; font-weight: bold;`, ...args); + return; + } + + // Fallback bez kolorów + console.log(prefix, ...args); + } + + /** + * Zapisz logi do localStorage + */ + saveLogs() { + if (typeof localStorage !== 'undefined' && this.config.saveToStorage) { + try { + // Zapisz tylko niezbędne informacje + const simplifiedLogs = this.logs.map(log => ({ + t: log.timestamp, + m: log.module, + l: log.level, + a: log.args.map(arg => { + // Konwertuj obiekty na stringi + 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); + } + } + } + + /** + * Załaduj logi z 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); + } + } + } + + /** + * Zapisz konfigurację do localStorage + */ + saveConfig() { + if (typeof localStorage !== 'undefined') { + try { + localStorage.setItem('layerforge_logger_config', JSON.stringify(this.config)); + } catch (e) { + console.error('Failed to save logger config to localStorage:', e); + } + } + } + + /** + * Załaduj konfigurację z localStorage + */ + loadConfig() { + if (typeof localStorage !== 'undefined') { + try { + const storedConfig = localStorage.getItem('layerforge_logger_config'); + if (storedConfig) { + this.config = { ...this.config, ...JSON.parse(storedConfig) }; + } + } catch (e) { + console.error('Failed to load logger config from localStorage:', e); + } + } + } + + /** + * Wyczyść wszystkie logi + */ + clearLogs() { + this.logs = []; + if (typeof localStorage !== 'undefined') { + localStorage.removeItem(this.config.storageKey); + } + return this; + } + + /** + * Eksportuj logi do pliku + * @param {string} format - Format eksportu ('json' lub 'txt') + */ + 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 { + // Format tekstowy + content = this.logs.map(log => + `[${log.timestamp}] [${log.module}] [${log.levelName}] ${log.args.join(' ')}` + ).join('\n'); + mimeType = 'text/plain'; + extension = 'txt'; + } + + // Utwórz link do pobrania + const blob = new Blob([content], { type: mimeType }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `layerforge_logs_${new Date().toISOString().replace(/[:.]/g, '-')}.${extension}`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + // Metody pomocnicze dla różnych poziomów logowania + + /** + * Log na poziomie DEBUG + * @param {string} module - Nazwa modułu + * @param {...any} args - Argumenty do zalogowania + */ + debug(module, ...args) { + this.log(module, LogLevel.DEBUG, ...args); + } + + /** + * Log na poziomie INFO + * @param {string} module - Nazwa modułu + * @param {...any} args - Argumenty do zalogowania + */ + info(module, ...args) { + this.log(module, LogLevel.INFO, ...args); + } + + /** + * Log na poziomie WARN + * @param {string} module - Nazwa modułu + * @param {...any} args - Argumenty do zalogowania + */ + warn(module, ...args) { + this.log(module, LogLevel.WARN, ...args); + } + + /** + * Log na poziomie ERROR + * @param {string} module - Nazwa modułu + * @param {...any} args - Argumenty do zalogowania + */ + error(module, ...args) { + this.log(module, LogLevel.ERROR, ...args); + } +} + +// Eksportuj singleton +export const logger = new Logger(); + +// Eksportuj funkcje pomocnicze +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); + +// Dodaj do window dla łatwego dostępu z konsoli +if (typeof window !== 'undefined') { + window.LayerForgeLogger = logger; +} + +export default logger; \ No newline at end of file diff --git a/python/logger.py b/python/logger.py new file mode 100644 index 0000000..872202f --- /dev/null +++ b/python/logger.py @@ -0,0 +1,299 @@ +""" +Logger - Centralny system logowania dla ComfyUI-LayerForge (Python) + +Funkcje: +- Różne poziomy logowania (DEBUG, INFO, WARN, ERROR) +- Możliwość włączania/wyłączania logów globalnie lub per moduł +- Kolorowe logi w konsoli +- Rotacja plików logów +- Konfiguracja przez zmienne środowiskowe +""" + +import os +import sys +import json +import logging +import datetime +from enum import IntEnum +from logging.handlers import RotatingFileHandler +import traceback + +# Poziomy logowania +class LogLevel(IntEnum): + DEBUG = 10 + INFO = 20 + WARN = 30 + ERROR = 40 + NONE = 100 + +# Mapowanie poziomów logowania +LEVEL_MAP = { + LogLevel.DEBUG: logging.DEBUG, + LogLevel.INFO: logging.INFO, + LogLevel.WARN: logging.WARNING, + LogLevel.ERROR: logging.ERROR, + LogLevel.NONE: logging.CRITICAL + 1 +} + +# Kolory ANSI dla różnych poziomów logowania +COLORS = { + LogLevel.DEBUG: '\033[90m', # Szary + LogLevel.INFO: '\033[94m', # Niebieski + LogLevel.WARN: '\033[93m', # Żółty + LogLevel.ERROR: '\033[91m', # Czerwony + 'RESET': '\033[0m' # Reset +} + +# Konfiguracja domyślna +DEFAULT_CONFIG = { + 'global_level': LogLevel.INFO, + 'module_settings': {}, + 'use_colors': True, + 'log_to_file': False, + 'log_dir': 'logs', + 'max_file_size_mb': 10, + 'backup_count': 5, + 'timestamp_format': '%H:%M:%S', +} + +class ColoredFormatter(logging.Formatter): + """Formatter dodający kolory do logów w konsoli""" + + def __init__(self, fmt=None, datefmt=None, use_colors=True): + super().__init__(fmt, datefmt) + self.use_colors = use_colors + + def format(self, record): + levelname = record.levelname + message = super().format(record) + + if self.use_colors and hasattr(LogLevel, levelname): + level = getattr(LogLevel, levelname) + color = COLORS.get(level, '') + reset = COLORS['RESET'] + return f"{color}{message}{reset}" + + return message + +class LayerForgeLogger: + """Główna klasa loggera dla LayerForge""" + + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(LayerForgeLogger, cls).__new__(cls) + cls._instance._initialized = False + return cls._instance + + def __init__(self): + if self._initialized: + return + + self.config = DEFAULT_CONFIG.copy() + self.enabled = True + self.loggers = {} + + # Załaduj konfigurację ze zmiennych środowiskowych + self._load_config_from_env() + + # Utwórz katalog logów, jeśli nie istnieje + if self.config['log_to_file']: + os.makedirs(self.config['log_dir'], exist_ok=True) + + self._initialized = True + + def _load_config_from_env(self): + """Załaduj konfigurację ze zmiennych środowiskowych""" + + # Poziom globalny + if 'LAYERFORGE_LOG_LEVEL' in os.environ: + level_name = os.environ['LAYERFORGE_LOG_LEVEL'].upper() + if hasattr(LogLevel, level_name): + self.config['global_level'] = getattr(LogLevel, level_name) + + # Ustawienia modułów + if 'LAYERFORGE_MODULE_LEVELS' in os.environ: + try: + module_settings = json.loads(os.environ['LAYERFORGE_MODULE_LEVELS']) + for module, level_name in module_settings.items(): + if hasattr(LogLevel, level_name.upper()): + self.config['module_settings'][module] = getattr(LogLevel, level_name.upper()) + except json.JSONDecodeError: + pass + + # Inne ustawienia + if 'LAYERFORGE_USE_COLORS' in os.environ: + self.config['use_colors'] = os.environ['LAYERFORGE_USE_COLORS'].lower() == 'true' + + if 'LAYERFORGE_LOG_TO_FILE' in os.environ: + self.config['log_to_file'] = os.environ['LAYERFORGE_LOG_TO_FILE'].lower() == 'true' + + if 'LAYERFORGE_LOG_DIR' in os.environ: + self.config['log_dir'] = os.environ['LAYERFORGE_LOG_DIR'] + + if 'LAYERFORGE_MAX_FILE_SIZE_MB' in os.environ: + try: + self.config['max_file_size_mb'] = int(os.environ['LAYERFORGE_MAX_FILE_SIZE_MB']) + except ValueError: + pass + + if 'LAYERFORGE_BACKUP_COUNT' in os.environ: + try: + self.config['backup_count'] = int(os.environ['LAYERFORGE_BACKUP_COUNT']) + except ValueError: + pass + + def configure(self, config): + """Konfiguracja loggera""" + self.config.update(config) + return self + + def set_enabled(self, enabled): + """Włącz/wyłącz logger globalnie""" + self.enabled = enabled + return self + + def set_global_level(self, level): + """Ustaw globalny poziom logowania""" + self.config['global_level'] = level + return self + + def set_module_level(self, module, level): + """Ustaw poziom logowania dla konkretnego modułu""" + self.config['module_settings'][module] = level + return self + + def is_level_enabled(self, module, level): + """Sprawdź, czy dany poziom logowania jest aktywny dla modułu""" + if not self.enabled: + return False + + # Sprawdź ustawienia modułu, jeśli istnieją + if module in self.config['module_settings']: + return level >= self.config['module_settings'][module] + + # W przeciwnym razie użyj globalnego poziomu + return level >= self.config['global_level'] + + def _get_logger(self, module): + """Pobierz lub utwórz logger dla modułu""" + if module in self.loggers: + return self.loggers[module] + + # Utwórz nowy logger + logger = logging.getLogger(f"layerforge.{module}") + logger.setLevel(logging.DEBUG) # Ustaw najniższy poziom, filtrowanie będzie później + logger.propagate = False + + # Dodaj handler dla konsoli + console_handler = logging.StreamHandler(sys.stdout) + console_formatter = ColoredFormatter( + fmt='[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s', + datefmt=self.config['timestamp_format'], + use_colors=self.config['use_colors'] + ) + console_handler.setFormatter(console_formatter) + logger.addHandler(console_handler) + + # Dodaj handler dla pliku, jeśli włączono logowanie do pliku + if self.config['log_to_file']: + log_file = os.path.join(self.config['log_dir'], f"layerforge_{module}.log") + file_handler = RotatingFileHandler( + log_file, + maxBytes=self.config['max_file_size_mb'] * 1024 * 1024, + backupCount=self.config['backup_count'] + ) + 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) + + self.loggers[module] = logger + return logger + + def log(self, module, level, *args, **kwargs): + """Zapisz log""" + if not self.is_level_enabled(module, level): + return + + logger = self._get_logger(module) + + # Konwertuj argumenty na string + message = " ".join(str(arg) for arg in args) + + # Dodaj informacje o wyjątku, jeśli podano + exc_info = kwargs.get('exc_info', None) + + # Mapuj poziom LogLevel na poziom logging + log_level = LEVEL_MAP.get(level, logging.INFO) + + # Zapisz log + logger.log(log_level, message, exc_info=exc_info) + + def debug(self, module, *args, **kwargs): + """Log na poziomie DEBUG""" + self.log(module, LogLevel.DEBUG, *args, **kwargs) + + def info(self, module, *args, **kwargs): + """Log na poziomie INFO""" + self.log(module, LogLevel.INFO, *args, **kwargs) + + def warn(self, module, *args, **kwargs): + """Log na poziomie WARN""" + self.log(module, LogLevel.WARN, *args, **kwargs) + + def error(self, module, *args, **kwargs): + """Log na poziomie ERROR""" + self.log(module, LogLevel.ERROR, *args, **kwargs) + + def exception(self, module, *args): + """Log wyjątku na poziomie ERROR""" + self.log(module, LogLevel.ERROR, *args, exc_info=True) + +# Singleton +logger = LayerForgeLogger() + +# Funkcje pomocnicze +def debug(module, *args, **kwargs): + """Log na poziomie DEBUG""" + logger.debug(module, *args, **kwargs) + +def info(module, *args, **kwargs): + """Log na poziomie INFO""" + logger.info(module, *args, **kwargs) + +def warn(module, *args, **kwargs): + """Log na poziomie WARN""" + logger.warn(module, *args, **kwargs) + +def error(module, *args, **kwargs): + """Log na poziomie ERROR""" + logger.error(module, *args, **kwargs) + +def exception(module, *args): + """Log wyjątku na poziomie ERROR""" + logger.exception(module, *args) + +# Funkcja do szybkiego włączania/wyłączania debugowania +def set_debug(enabled=True): + """Włącz/wyłącz debugowanie globalnie""" + if enabled: + logger.set_global_level(LogLevel.DEBUG) + else: + logger.set_global_level(LogLevel.INFO) + return logger + +# Funkcja do włączania/wyłączania logowania do pliku +def set_file_logging(enabled=True, log_dir=None): + """Włącz/wyłącz logowanie do pliku""" + logger.config['log_to_file'] = enabled + if log_dir: + logger.config['log_dir'] = log_dir + os.makedirs(log_dir, exist_ok=True) + + # Zresetuj loggery, aby zastosować nowe ustawienia + logger.loggers = {} + return logger \ No newline at end of file