Add logging system

This commit is contained in:
Dariusz L
2025-06-25 20:44:25 +02:00
parent 1e58747b76
commit e3040c3aed
10 changed files with 1346 additions and 247 deletions

View File

@@ -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;
}

View File

@@ -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);
}
},
},

View File

@@ -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();
}
}

View File

@@ -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}`);
}
}

View File

@@ -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) {

View File

@@ -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();
};
});

371
js/logger.js Normal file
View File

@@ -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;