import config from "config"; import {v4 as uuid} from "uuid"; import Log from "./models/Log"; import {bufferToUuid} from "./Utils"; import ModelFactory from "./db/ModelFactory"; export enum LogLevel { ERROR, WARN, INFO, DEBUG, DEV, } export type LogLevelKeys = keyof typeof LogLevel; /** * TODO: make logger not static */ export default class Logger { private static logLevel: LogLevel = LogLevel[config.get('log.level')]; private static dbLogLevel: LogLevel = LogLevel[config.get('log.db_level')]; private static verboseMode: boolean = config.get('log.verbose'); public static verbose(): void { this.verboseMode = true; if (LogLevel[this.logLevel + 1]) this.logLevel++; if (LogLevel[this.dbLogLevel + 1]) this.dbLogLevel++; Logger.info('Verbose mode'); } public static isVerboseMode(): boolean { return this.verboseMode; } public static silentError(error: Error, ...message: unknown[]): string { return this.log(LogLevel.ERROR, message, error, true) || ''; } public static error(error: Error, ...message: unknown[]): string { return this.log(LogLevel.ERROR, message, error) || ''; } public static warn(...message: unknown[]): void { this.log(LogLevel.WARN, message); } public static info(...message: unknown[]): void { this.log(LogLevel.INFO, message); } public static debug(...message: unknown[]): void { this.log(LogLevel.DEBUG, message); } public static dev(...message: unknown[]): void { this.log(LogLevel.DEV, message); } private static log(level: LogLevel, message: unknown[], error?: Error, silent: boolean = false): string | null { if (level <= this.logLevel) { if (error) { if (level > LogLevel.ERROR) this.warn(`Wrong log level ${level} with attached error.`); } else { if (level <= LogLevel.ERROR) this.warn(`No error attached with log level ${level}.`); } const computedMsg = message.map(v => { if (typeof v === 'string') { return v; } else { return JSON.stringify(v, (key: string, value: string | unknown[] | Record) => { if (!Array.isArray(value) && value instanceof Object) { if (value.type === 'Buffer' && typeof value.data === 'string') { return `Buffer<${Buffer.from(value.data).toString('hex')}>`; } else if (value !== v) { return `[object Object]`; } } if (typeof value === 'string' && value.length > 96) { return value.substr(0, 96) + '...'; } return value; }, 4); } }).join(' '); const shouldSaveToDB = level <= this.dbLogLevel; let output = `[${LogLevel[level]}] `; const pad = output.length; const logId = Buffer.alloc(16); uuid({}, logId); const strLogId = bufferToUuid(logId); if (shouldSaveToDB) output += `${strLogId} - `; output += computedMsg.replace(/\n/g, '\n' + ' '.repeat(pad)); switch (level) { case LogLevel.ERROR: if (silent || !error) { console.error(output); } else { console.error(output, error); } break; case LogLevel.WARN: console.warn(output); break; case LogLevel.INFO: console.info(output); break; case LogLevel.DEBUG: case LogLevel.DEV: console.debug(output); break; } if (shouldSaveToDB && ModelFactory.has(Log)) { const log = Log.create({}); log.setLevel(level); log.message = computedMsg; log.setError(error); log.setLogId(logId); log.save().catch(err => { if (!silent && err.message.indexOf('ECONNREFUSED') < 0) { console.error({save_err: err, error}); } }); } return strLogId; } return null; } private constructor() { // disable constructor } }