UNPKG

@4players/odin-common

Version:

A collection of commonly used type definitions and utility functions across ODIN web projects

211 lines (210 loc) 6.89 kB
import { failure, success, unwrap } from './result'; import { isNumber } from './validation'; export var LogVerbosity; (function (LogVerbosity) { LogVerbosity[LogVerbosity["NONE"] = 0] = "NONE"; LogVerbosity[LogVerbosity["ERROR"] = 1] = "ERROR"; LogVerbosity[LogVerbosity["WARN"] = 2] = "WARN"; LogVerbosity[LogVerbosity["INFO"] = 3] = "INFO"; LogVerbosity[LogVerbosity["DEBUG"] = 4] = "DEBUG"; })(LogVerbosity || (LogVerbosity = {})); export const LogLevels = Object.values(LogVerbosity).filter((key) => !isNaN(Number(key))); export const LogLevelNames = Object.keys(LogVerbosity).filter((key) => isNaN(Number(key))); export const LogSymbols = { [LogVerbosity.NONE]: '⚫', [LogVerbosity.ERROR]: '🔴', [LogVerbosity.WARN]: '🟡', [LogVerbosity.INFO]: '🔵', [LogVerbosity.DEBUG]: '🟣', }; export const LogFunctions = { [LogVerbosity.NONE]: console.log, [LogVerbosity.ERROR]: console.error, [LogVerbosity.WARN]: console.warn, [LogVerbosity.INFO]: console.info, [LogVerbosity.DEBUG]: console.debug, }; export function getLevelByName(name) { const level = LogVerbosity[name]; if (level === undefined) { return failure(`invalid log level name '${name}'; valid names are: ${LogLevelNames.join(', ')}`); } return success(level); } export function getLevelName(level) { const name = LogVerbosity[level]; if (name === undefined) { return failure(`invalid log level ${level}; valid levels are: ${LogLevels[0]}-${LogLevels[LogLevels.length - 1]}`); } return success(name); } const LOG_DEFAULT_ID = 'default'; const LOG_COLLECTION = new Map(); export function getLogger(name = LOG_DEFAULT_ID) { let logger = LOG_COLLECTION.get(name); if (!logger) { logger = new Logger('WARN', name); } return logger; } export function error(msg, ...args) { if (msg instanceof Function) { return getLogger(LOG_DEFAULT_ID).error(msg, ...args); } return getLogger(LOG_DEFAULT_ID).error(msg, ...args); } export function warn(msg, ...args) { if (msg instanceof Function) { return getLogger(LOG_DEFAULT_ID).warn(msg, ...args); } return getLogger(LOG_DEFAULT_ID).warn(msg, ...args); } export function info(msg, ...args) { if (msg instanceof Function) { return getLogger(LOG_DEFAULT_ID).info(msg, ...args); } return getLogger(LOG_DEFAULT_ID).info(msg, ...args); } export function debug(msg, ...args) { if (msg instanceof Function) { return getLogger(LOG_DEFAULT_ID).debug(msg, ...args); } return getLogger(LOG_DEFAULT_ID).debug(msg, ...args); } export class Logger { constructor(level, name, options) { this.name = name ?? LOG_DEFAULT_ID; if (isNumber(level)) { this._Level = unwrap(getLevelByName(unwrap(getLevelName(level)))); } else { this._Level = unwrap(getLevelByName(level)); } this._Handlers = options?.handlers || [new LogHandler(level)]; LOG_COLLECTION.set(this.name, this); } get level() { return this._Level; } set level(value) { this.levelName = unwrap(getLevelName(value)); } get levelName() { return unwrap(getLevelName(this._Level)); } set levelName(value) { this._Level = unwrap(getLevelByName(value)); this._Handlers.forEach((handler) => (handler.level = this._Level)); } get handlers() { return this._Handlers; } addHandler(handler) { this._Handlers.push(handler); } removeHandler(handler) { const index = this._Handlers.indexOf(handler); if (index !== -1) this._Handlers.splice(index, 1); } log(level, msg, ...args) { if (this.level < level) { return msg instanceof Function ? undefined : msg; } let fnResult; let message; if (msg instanceof Function) { fnResult = msg(); message = this.asString(fnResult); } else { message = this.asString(msg); } this._Handlers.forEach((handler) => { handler.handle({ date: new Date(), level, levelName: unwrap(getLevelName(level)), logger: this.name, message, args, }); }); return msg instanceof Function ? fnResult : msg; } error(msg, ...args) { return this.log(LogVerbosity.ERROR, msg, ...args); } warn(msg, ...args) { return this.log(LogVerbosity.WARN, msg, ...args); } info(msg, ...args) { return this.log(LogVerbosity.INFO, msg, ...args); } debug(msg, ...args) { return this.log(LogVerbosity.DEBUG, msg, ...args); } asString(data, isProperty = false) { if (typeof data === 'string') { if (isProperty) return `"${data}"`; return data; } else if (data === null || typeof data === 'number' || typeof data === 'bigint' || typeof data === 'boolean' || typeof data === 'undefined' || typeof data === 'symbol') { return String(data); } else if (data instanceof Error) { return data.stack; } else if (typeof data === 'object') { return `{${Object.entries(data) .map(([k, v]) => `"${k}":${this.asString(v, true)}`) .join(',')}}`; } return 'undefined'; } } export const DEFAULT_FORMATTER = ({ message }) => message; export const PRETTY_FORMATTER = (record) => { const date = `[${record.date.toISOString()}]`; const level = `[${record.levelName}]`; const logger = record.logger !== LOG_DEFAULT_ID ? `[${record.logger}] ` : ''; const symbol = LogSymbols[record.level]; return `${date} ${symbol} ${level.padEnd(7, ' ')} ${logger}${record.message}`; }; export class LogHandler { constructor(level, formatter = DEFAULT_FORMATTER) { if (isNumber(level)) { this._Level = unwrap(getLevelByName(unwrap(getLevelName(level)))); } else { this._Level = unwrap(getLevelByName(level)); } this._FormatterFn = formatter; } get level() { return this._Level; } set level(value) { this.levelName = unwrap(getLevelName(value)); } get levelName() { return unwrap(getLevelName(this._Level)); } set levelName(value) { this._Level = unwrap(getLevelByName(value)); } handle(record) { if (this._Level < record.level) return; this.log(this._FormatterFn(record), record); } log(formattedString, record) { LogFunctions[record.level](formattedString, ...record.args); } }