(function ($) { "use strict"; var CONFIG = null; const log = function () { this.webroot = '/' + window.location.pathname.split('/')[1] + '/'; this.storeWorker = new Worker(this.webroot + 'plugin/Logf/js/indexeddb.worker.js'); this.storeWorker.onmessage = function (event) { console.log("Log worker:", event.data); const { type, payload } = event.data; if (type === "config") { CONFIG = payload; } }; this.storeWorker.onerror = function (event) { console.error("Log worker:", event); }; this.storeWorker.postMessage({type: "getConfig"}); }; var p = log.prototype; p.info = function(data, message, domain) { if (!data) data = {}; if (!data.level) data.level = "info"; if (domain) data.domain = domain; if (message) data.message = message; data.url = window.location.href; this.storeWorker.postMessage({ type: "log", payload: p.sanitize(data) }); } p.error = function(data, domain) { if (!data) data = {}; if (!data.level) data.level = "error"; if (domain) data.domain = domain; this.storeWorker.postMessage({ type: "log", payload: p.sanitize(data) }); } // Принудительная отправка p.flushLogs = function() { this.storeWorker.postMessage({ type: "flush" }); } p.sanitize = function(obj) { var processed = []; // Функция для обрезки строк function truncateString(str) { if (typeof str === 'string' && str.length > 33) { return str.substring(0, 30) + '...'; } return str; } function transform(value) { // Примитивные типы if (value === null || typeof value !== 'object') { if (typeof value === 'function') return truncateString(String(value)); if (typeof value === 'symbol') return truncateString(String(value)); if (typeof value === 'undefined') return 'undefined'; return value; } // Проверка циклических ссылок for (var i = 0; i < processed.length; i++) { if (processed[i] === value) { return '[Circular]'; } } processed.push(value); // Определяем тип объекта var objectType = Object.prototype.toString.call(value); // Специальные объекты - преобразуем в строку и обрезаем if (objectType === '[object Date]') { return truncateString(value.toISOString()); } if (objectType === '[object RegExp]') { return truncateString(value.toString()); } // Обрабатываем Map (если поддерживается) if (typeof Map !== 'undefined' && value instanceof Map) { var mapArray = []; value.forEach(function(val, key) { mapArray.push([key, transform(val)]); }); return truncateString('Map: ' + JSON.stringify(mapArray)); } // Обрабатываем Set (если поддерживается) if (typeof Set !== 'undefined' && value instanceof Set) { var setArray = []; value.forEach(function(val) { setArray.push(transform(val)); }); return truncateString('Set: ' + JSON.stringify(setArray)); } // Обрабатываем DOM элементы if (typeof HTMLElement !== 'undefined' && value instanceof HTMLElement) { return truncateString('HTMLElement: ' + value.tagName + (value.id ? '#' + value.id : '')); } // Обрабатываем Error - обрезаем все строковые поля if (value instanceof Error) { var errorObj = { name: truncateString(value.name), message: truncateString(value.message) }; // stack может быть очень длинным, поэтому обрезаем if (value.stack) { errorObj.stack = truncateString(value.stack); } return errorObj; } // Обрабатываем Promise (если поддерживается) if (typeof Promise !== 'undefined' && value instanceof Promise) { return truncateString('Promise: [object Promise]'); } // Обрабатываем ArrayBuffer и TypedArray if (objectType === '[object ArrayBuffer]' || objectType === '[object Int8Array]' || objectType === '[object Uint8Array]' || objectType === '[object Uint8ClampedArray]' || objectType === '[object Int16Array]' || objectType === '[object Uint16Array]' || objectType === '[object Int32Array]' || objectType === '[object Uint32Array]' || objectType === '[object Float32Array]' || objectType === '[object Float64Array]') { return truncateString(objectType + ' (length: ' + value.length + ')'); } // Массивы if (Array.isArray ? Array.isArray(value) : objectType === '[object Array]') { var arrResult = []; for (var i = 0; i < value.length; i++) { arrResult[i] = transform(value[i]); } return arrResult; } // Обычные объекты var objResult = {}; for (var key in value) { if (Object.prototype.hasOwnProperty.call(value, key)) { objResult[key] = transform(value[key]); } } return objResult; } var result = transform(obj); processed = null; return result; } const logger = new log(); //создаём вызываемую функцию-обёртку function base(...args) { return logger.info(...args); } //копируем методы экземпляра Object.setPrototypeOf(base, logger); //оборачиваем в Proxy const Log = new Proxy(base, { get(target, prop) { return logger[prop]; }, set(target, prop, value) { logger[prop] = value; return true; } }); window.Log = Log; window.addEventListener("error", function(event) { logger.error({ message: event.message, url: event.filename, line: event.lineno, col: event.colno, stack: event.error?.stack }); }); window.addEventListener("unhandledrejection", function(event) { logger.error({ message: event.reason?.message || event.reason, stack: event.reason?.stack }); }); //перед закрытием окна window.addEventListener("beforeunload", function(e) { logger.storeWorker.postMessage({ type: "flush" });//слив через worker //дополнительный слив если worker не отработал const req = indexedDB.open(CONFIG.DB_NAME, 1); req.onupgradeneeded = function(event) { const db = event.target.result; if (!db.objectStoreNames.contains(CONFIG.STORE_NAME)) { db.createObjectStore(CONFIG.STORE_NAME, {keyPath: "id", autoIncrement: true}); } }; req.onsuccess = function(event) { const db = event.target.result; const tx = db.transaction(CONFIG.STORE_NAME, "readonly"); const logsReq = tx.objectStore(CONFIG.STORE_NAME).getAll(); logsReq.onsuccess = function(logs) { if (!logs.length) return; navigator.sendBeacon( logger.webroot + 'Logf/Logf/save', JSON.stringify(logs) ); tx = db.transaction(CONFIG.STORE_NAME, "readwrite"); tx.objectStore(CONFIG.STORE_NAME).clear(); }; }; req.onerror = function(event) { console.log('error opening database ' + event.target.errorCode); }; }); }(jQuery));