UNPKG

tyo-mq

Version:

Distributed Message Pub/Sub Service with socket.io

175 lines (146 loc) 5.98 kB
/** * @file logger.js * * Levelled, coloured logger for tyo-mq. * Adapted from tyostocks/algo/utils/logger.js — standalone, no external deps. * * Levels (lower number = higher priority): * CRITICAL 0 bright-red — fatal misconfigurations, must never be ignored * ERROR 1 red — unexpected failures, always shown * OUTPUT 2 white — command results * WARN 3 yellow — recoverable problems / bad input * LOG 4 cyan — normal operational events (default level) * INFO 8 green — extra informational detail * DEBUG 16 blue — internal mechanics (message routing, etc.) * TRACE 32 magenta — very verbose, usually disabled */ 'use strict'; const fs = require('fs'); const pkg = require('../package.json'); // ANSI colour codes (foreground) const theme_default = { output: 97, // bright white log: 36, // cyan info: 32, // green warn: 33, // yellow error: 31, // red debug: 34, // blue trace: 35, // magenta critical: 91, // bright red }; function pad2(n) { return String(n).padStart(2, '0'); } function pad3(n) { return String(n).padStart(3, '0'); } function formatDate(d) { return d.getFullYear().toString().substr(2) + pad2(d.getMonth() + 1) + pad2(d.getDate()) + '-' + pad2(d.getHours()) + ':' + pad2(d.getMinutes()) + ':' + pad2(d.getSeconds()) + '.' + pad3(d.getMilliseconds()); } function Logger(name, options) { options = options || {}; this.name = name || 'tyo-mq'; this.theme = options.theme || theme_default; this.out = options.out || process.stdout; this.suffix = options.suffix || '\n'; this.level = options.level !== undefined ? options.level : Logger.LOG; this.logger_file = options.logger_file || null; this.errors_file = options.errors_file || null; // keep refs to originals so internal helpers can always write this._stdout_write = process.stdout.write.bind(process.stdout); } Logger.CRITICAL = 0; Logger.ERROR = 1; Logger.OUTPUT = 2; Logger.WARN = 3; Logger.LOG = 4; Logger.INFO = 8; Logger.DEBUG = 16; Logger.TRACE = 32; Logger.prototype.set_level = function (level) { const map = { CRITICAL:0, ERROR:1, OUTPUT:2, WARN:3, LOG:4, INFO:8, DEBUG:16, TRACE:32 }; if (typeof level === 'string') { const v = map[level.toUpperCase()]; if (v === undefined) throw new Error('Invalid log level: ' + level); this.level = v; } else if (typeof level === 'number') { this.level = level; } else { throw new Error('level must be a string or number'); } }; Logger.prototype._print_one = function (what, color, step, date) { date = date || new Date(); step = step || 0; const prefix = '[' + this.name + '@' + pkg.version + '|' + formatDate(date) + ']'; const indent = ' '.repeat(step); const ansiOn = '\x1b[' + (color || 35) + 'm'; const ansiOff = '\x1b[0m'; this._stdout_write(prefix + indent + ansiOn + String(what) + ansiOff + this.suffix); }; Logger.prototype._print = function (what, color, step, date) { step = step || 0; if (Array.isArray(what)) { for (const item of what) this._print(item, color, step + 1, date); } else if (what !== null && typeof what === 'object' && !(what instanceof Error)) { for (const key of Object.keys(what)) { const val = what[key]; if (typeof val === 'object' || Array.isArray(val)) this._print(val, color, step + 1, date); else this._print_one(key + ': ' + val, color, step + 1, date); } } else { this._print_one(what instanceof Error ? what.stack : what, color, step, date); } }; Logger.prototype._file_write = function (logger_file, level_string, what) { if (!logger_file) return; const date = new Date(); const prefix = '[' + this.name + '|' + formatDate(date) + '] '; const tag = level_string ? '(' + level_string + ') ' : ''; const lines = (Array.isArray(what) ? what : [what]).map(item => { if (!item && item !== 0) return null; return prefix + tag + (item instanceof Error ? item.stack : String(item)); }).filter(Boolean); if (lines.length) fs.appendFileSync(logger_file, lines.join('\n') + '\n'); }; // ── public methods ──────────────────────────────────────────────────────────── Logger.prototype.critical = function (...args) { // always shown regardless of level this._print(args, this.theme.critical); this._file_write(this.errors_file, 'CRITICAL', args); }; Logger.prototype.error = function (...args) { // always shown regardless of level this._print(args, this.theme.error); this._file_write(this.errors_file, 'ERROR', args); }; Logger.prototype.warn = function (...args) { if (this.level >= Logger.WARN) this._print(args, this.theme.warn); this._file_write(this.logger_file, 'WARN', args); }; Logger.prototype.output = function (...args) { if (this.level >= Logger.OUTPUT) this._print(args, this.theme.output); }; Logger.prototype.log = function (...args) { if (this.level >= Logger.LOG) this._print(args, this.theme.log); this._file_write(this.logger_file, 'LOG', args); }; Logger.prototype.info = function (...args) { if (this.level >= Logger.INFO) this._print(args, this.theme.info); this._file_write(this.logger_file, 'INFO', args); }; Logger.prototype.debug = function (...args) { if (this.level >= Logger.DEBUG) this._print(args, this.theme.debug); }; Logger.prototype.trace = function (...args) { if (this.level >= Logger.TRACE) this._print(args, this.theme.trace); }; module.exports = Logger;