UNPKG

@lit-protocol/logger

Version:

This package provides a centralized logging utility for the Lit Protocol SDK, offering structured logging capabilities across all packages. It enables consistent log formatting, level-based filtering, and standardized error reporting throughout the Lit Pr

408 lines 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LogManager = exports.Logger = exports.LogLevel = exports.LOG_LEVEL = void 0; const constants_1 = require("@lit-protocol/constants"); Object.defineProperty(exports, "LOG_LEVEL", { enumerable: true, get: function () { return constants_1.LOG_LEVEL; } }); const utils_1 = require("ethers/lib/utils"); var LogLevel; (function (LogLevel) { LogLevel[LogLevel["OFF"] = -1] = "OFF"; LogLevel[LogLevel["ERROR"] = 0] = "ERROR"; LogLevel[LogLevel["INFO"] = 1] = "INFO"; LogLevel[LogLevel["DEBUG"] = 2] = "DEBUG"; LogLevel[LogLevel["WARN"] = 3] = "WARN"; LogLevel[LogLevel["FATAL"] = 4] = "FATAL"; LogLevel[LogLevel["TIMING_START"] = 5] = "TIMING_START"; LogLevel[LogLevel["TIMING_END"] = 6] = "TIMING_END"; })(LogLevel || (exports.LogLevel = LogLevel = {})); const colours = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', underscore: '\x1b[4m', blink: '\x1b[5m', reverse: '\x1b[7m', hidden: '\x1b[8m', fg: { black: '\x1b[30m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', gray: '\x1b[90m', crimson: '\x1b[38m', // Scarlet }, bg: { black: '\x1b[40m', red: '\x1b[41m', green: '\x1b[42m', yellow: '\x1b[43m', blue: '\x1b[44m', magenta: '\x1b[45m', cyan: '\x1b[46m', white: '\x1b[47m', gray: '\x1b[100m', crimson: '\x1b[48m', }, }; function _convertLoggingLevel(level) { switch (level) { case constants_1.LOG_LEVEL.INFO: return `${colours.fg.green}[INFO]${colours.reset}`; case constants_1.LOG_LEVEL.DEBUG: return `${colours.fg.cyan}[DEBUG]${colours.reset}`; case constants_1.LOG_LEVEL.WARN: return `${colours.fg.yellow}[WARN]${colours.reset}`; case constants_1.LOG_LEVEL.ERROR: return `${colours.fg.red}[ERROR]${colours.reset}`; case constants_1.LOG_LEVEL.FATAL: return `${colours.fg.red}[FATAL]${colours.reset}`; case constants_1.LOG_LEVEL.TIMING_START: return `${colours.fg.green}[TIME_START]${colours.reset}`; case constants_1.LOG_LEVEL.TIMING_END: return `${colours.fg.green}[TIME_END]${colours.reset}`; } return '[UNKNOWN]'; } function _resolveLoggingHandler(level) { switch (level) { case constants_1.LOG_LEVEL.DEBUG: return console.debug; case constants_1.LOG_LEVEL.INFO: return console.info; case constants_1.LOG_LEVEL.ERROR: return console.error; case constants_1.LOG_LEVEL.WARN: return console.warn; case constants_1.LOG_LEVEL.FATAL: return console.error; case constants_1.LOG_LEVEL.TIMING_END: return console.timeLog; case constants_1.LOG_LEVEL.TIMING_START: return console.time; } } /** * Implementation of `JSON.stringify` which removes circular object references * @example * let circ = {foo: 'bar'}; * circ.circ = circ; // creates a circular reference * _safeStringify(circ) -> {foo: 'bar'} * @param obj object to check for circular references * @param indent number of indents to include (spaces) * @returns obj param without without circular references */ function _safeStringify(obj, indent = 2) { let cache = []; const retVal = JSON.stringify(obj, (_key, value) => typeof value === 'object' && value !== null ? cache?.includes(value) ? undefined // Duplicate reference found, discard key : cache?.push(value) && value // Store value in our collection : value, indent); cache = null; return retVal; } class Log { constructor(timestamp, message, args, id, category, level) { this.timestamp = timestamp; this.message = message; this.args = args; this.id = id; this.category = category; this.level = level; } toString() { let fmtStr = `[Lit-JS-SDK v${constants_1.version}]${_convertLoggingLevel(this.level)} [${this.category}] [id: ${this.id}] ${this.message}`; for (let i = 0; i < this.args.length; i++) { if (typeof this.args[i] === 'object') { fmtStr = `${fmtStr} ${_safeStringify(this.args[i])}`; } else { fmtStr = `${fmtStr} ${this.args[i]}`; } } return fmtStr; } toArray() { const args = []; args.push(`[Lit-JS-SDK v${constants_1.version}]`); args.push(`[${this.timestamp}]`); args.push(_convertLoggingLevel(this.level)); args.push(`[${this.category}]`); this.id && args.push(`${colours.fg.cyan}[id: ${this.id}]${colours.reset}`); this.message && args.push(this.message); for (let i = 0; i < this.args.length; i++) { args.push(this.args[i]); } return args; } toJSON() { return { timestamp: this.timestamp, message: this.message, args: this.args, id: this.id, category: this.category, level: this.level, }; } } class Logger { static createLogger(category, level, id, isParent, config) { return new Logger(category, level, id, isParent, config); } constructor(category, level, id, isParent, config) { this._logs = []; this._logHashes = new Map(); this._category = category; this._level = level; this._id = id; this._consoleHandler = _resolveLoggingHandler(this._level); this._config = config; this._children = new Map(); this._isParent = isParent; this._timestamp = Date.now(); } get id() { return this._id; } get category() { return this._category; } get timestamp() { return this._timestamp; } get Logs() { return this._logs; } set Config(value) { this._config = value; } get Config() { return this._config; } get Children() { return this._children; } setLevel(level) { this._level = level; } setHandler(handler) { this._handler = handler; } info(message = '', ...args) { this._log(constants_1.LOG_LEVEL.INFO, message, ...args); } debug(message = '', ...args) { this._log(constants_1.LOG_LEVEL.DEBUG, message, ...args); } warn(message = '', ...args) { this._log(constants_1.LOG_LEVEL.WARN, message, args); } error(message = '', ...args) { this._log(constants_1.LOG_LEVEL.ERROR, message, ...args); } fatal(message = '', ...args) { this._log(constants_1.LOG_LEVEL.FATAL, message, ...args); } trace(message = '', ...args) { this._log(constants_1.LOG_LEVEL.FATAL, message, ...args); } timeStart(message = '', ...args) { this._log(constants_1.LOG_LEVEL.TIMING_START, message, ...args); } timeEnd(message = '', ...args) { this._level < constants_1.LOG_LEVEL.OFF && this._log(constants_1.LOG_LEVEL.TIMING_END, message, ...args); } _log(level, message = '', ...args) { const log = new Log(new Date().toISOString(), message, args, this._id, this._category, level); const arrayLog = log.toArray(); if (this._config?.['condenseLogs'] && !this._checkHash(log)) { (this._level >= level || level === LogLevel.ERROR) && this._consoleHandler && this._consoleHandler(...arrayLog); (this._level >= level || level === constants_1.LOG_LEVEL.ERROR) && this._handler && this._handler(log); (this._level >= level || level === LogLevel.ERROR) && this._addLog(log); } else if (!this._config?.['condenseLogs']) { (this._level >= level || level === LogLevel.ERROR) && this._consoleHandler && this._consoleHandler(...arrayLog); (this._level >= level || level === constants_1.LOG_LEVEL.ERROR) && this._handler && this._handler(log); (this._level >= level || level === constants_1.LOG_LEVEL.ERROR) && this._addLog(log); } } _checkHash(log) { const strippedMessage = this._cleanString(log.message); const digest = (0, utils_1.hashMessage)(strippedMessage); const hash = digest.toString(); const item = this._logHashes.get(hash); if (item) { return true; } else { this._logHashes.set(hash, true); return false; } } _addLog(log) { this._logs.push(log); // TODO: currently we are not deleting old request id's which over time will fill local storage as the maximum storage size is 10mb // we should be deleting keys from the front of the collection of `Object.keys(category)` such that the first keys entered are deleted when we reach a pre defined key threshold // this implementation assumes that serialization / deserialization from `localStorage` keeps the same key ordering in each `category` object as we will asssume the array produced from `Object.keys` will always be the same ordering. // which then allows us to start at the front of the array and do `delete` operation on each key we wish to delete from the object. //log.id && this._addToLocalStorage(log); } _addToLocalStorage(log) { if (globalThis.localStorage) { let bucket = globalThis.localStorage.getItem(log.category); if (bucket) { bucket = JSON.parse(bucket); if (!bucket[log.id]) { bucket[log.id] = []; } bucket[log.id].push(log.toString()); globalThis.localStorage.setItem(log.category, _safeStringify(bucket)); } else { const bucket = {}; bucket[log.id] = [log.toString()]; globalThis.localStorage.setItem(log.category, _safeStringify(bucket)); } } } /** * * @param input string which will be cleaned of non utf-8 characters * @returns {string} input cleaned of non utf-8 characters */ _cleanString(input) { let output = ''; for (let i = 0; i < input.length; i++) { if (input.charCodeAt(i) <= 127) { output += input.charAt(i); } } return output; } } exports.Logger = Logger; class LogManager { static get Instance() { if (!LogManager._instance) { LogManager._instance = new LogManager(); } return LogManager._instance; } static clearInstance() { LogManager._instance = undefined; } constructor() { this._level = constants_1.LOG_LEVEL.DEBUG; this._loggers = new Map(); } withConfig(config) { this._config = config; for (const logger of this._loggers) { logger[1].Config = config; } } setLevel(level) { this._level = level; for (const logger of this._loggers) { logger[1].setLevel(level); } } setHandler(handler) { for (const logger of this._loggers) { logger[1].setHandler(handler); } } get LoggerIds() { const keys = []; for (const category of this._loggers.entries()) { for (const child of category[1].Children) { keys.push([child[0], child[1].timestamp]); } } return keys .sort((a, b) => { return a[1] - b[1]; }) .map((value) => { return value[0]; }); } // if a logger is given an id it will persist logs under its logger instance get(category, id) { let instance = this._loggers.get(category); if (!instance && !id) { this._loggers.set(category, Logger.createLogger(category, this._level ?? constants_1.LOG_LEVEL.INFO, '', true)); instance = this._loggers.get(category); instance.Config = this._config; return instance; } if (id) { if (!instance) { this._loggers.set(category, Logger.createLogger(category, this._level ?? constants_1.LOG_LEVEL.INFO, '', true)); instance = this._loggers.get(category); instance.Config = this._config; } const children = instance?.Children; let child = children?.get(id); if (child) { return child; } children?.set(id, Logger.createLogger(category, this._level ?? constants_1.LOG_LEVEL.INFO, id ?? '', true)); child = children?.get(id); child.Config = this._config; return children?.get(id); // fall through condition for if there is no id for the logger and the category is not yet created. // ex: LogManager.Instance.get('foo'); } else if (!instance) { this._loggers.set(category, Logger.createLogger(category, this._level ?? constants_1.LOG_LEVEL.INFO, '', true)); instance = this._loggers.get(category); instance.Config = this._config; } return instance; } getById(id) { let logStrs = []; for (const category of this._loggers.entries()) { const logger = category[1].Children.get(id); if (logger) { const logStr = []; for (const log of logger.Logs) { logStr.push(log.toString()); } logStrs = logStrs.concat(logStr); } } return logStrs; } getLogsForId(id) { let logsForRequest = this.getById(id); if (logsForRequest.length < 1 && globalThis.localStorage) { for (const category of this._loggers.keys()) { const bucketStr = globalThis.localStorage.getItem(category); const bucket = JSON.parse(bucketStr); if (bucket && bucket[id]) { const logsForId = bucket[id].filter((log) => log.includes(id)); logsForRequest = logsForId.concat(logsForRequest); } } } return logsForRequest; } } exports.LogManager = LogManager; //# sourceMappingURL=logger.js.map