@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
JavaScript
"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