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