xcraft-core-log
Version:
450 lines (373 loc) • 9.91 kB
JavaScript
'use strict';
const mainModuleName = 'xcraft';
const path = require('path');
const util = require('util');
const EventEmitter = require('events').EventEmitter;
const colors = require('picocolors').createColors(true);
let currentModulesNames = [];
let currentLevel = 0;
let currentUseColor = true;
let currentUseDatetime = false;
let currentDecorate = true;
const levelsText = ['Verb', 'Info', 'Warn', 'Err', 'Dbg'];
const levels = {
true: [
colors.cyanBright(colors.bold(levelsText[0])),
colors.greenBright(colors.bold(levelsText[1])),
colors.yellowBright(colors.bold(levelsText[2])),
colors.redBright(colors.bold(levelsText[3])),
colors.magentaBright(colors.bold(levelsText[4])),
],
false: levelsText,
};
let xBusLog = null;
let xJournal = null;
let modesInitialized = false;
// http://stackoverflow.com/a/29581862
function getCallerFile() {
const originalFunc = Error.prepareStackTrace;
let callerfile;
try {
const err = new Error();
let currentfile;
Error.prepareStackTrace = function (err, stack) {
return stack;
};
currentfile = err.stack.shift().getFileName();
while (err.stack.length) {
callerfile = err.stack.shift().getFileName();
if (currentfile !== callerfile) {
break;
}
}
} catch (ex) {
/* ignore exceptions */
}
Error.prepareStackTrace = originalFunc;
return callerfile;
}
function Log(mod, resp) {
EventEmitter.call(this);
/** @private */ this._moduleName = mod;
/** @private */ this._currentLevel = -1;
/** @private */ this._currentDecorate = null;
/** @private */ this._busLog = null;
/** @private */ this._journal = null;
/** @private */ this._resp = resp;
const table = (level, table) => {
const asTable = require('as-table');
this._log(level, '%s', asTable.configure({dash: '-'})(table));
};
this.__proto__.verb.table = function (data) {
table(0, data);
}.bind(this);
this.__proto__.info.table = function (data) {
table(1, data);
}.bind(this);
this.__proto__.warn.table = function (data) {
table(2, data);
}.bind(this);
this.__proto__.err.table = function (data) {
table(3, data);
}.bind(this);
this.__proto__.dbg.table = function (data) {
table(4, data);
}.bind(this);
}
util.inherits(Log, EventEmitter);
/** @private */
Log.prototype._testLevel = function (level) {
if (this._currentLevel >= 0) {
return level >= this._currentLevel;
}
return level >= currentLevel;
};
/** @private */
Log.prototype._loadBusLog = function () {
if (!xBusLog) {
initModes();
this._busLog = null;
return 0;
}
if (this._busLog) {
return xBusLog.getModes();
}
if (!this._resp) {
return 0;
}
if (xBusLog) {
this._busLog = xBusLog(this, this._resp);
return xBusLog.getModes();
}
return 0;
};
/** @private */
Log.prototype._loadJournal = function () {
if (xJournal === false) {
this._journal = null;
return false;
}
if (this._journal) {
return true;
}
journalize();
if (xJournal) {
this._journal = xJournal(this);
return true;
}
return false;
};
Log.prototype.getModule = function () {
let module = this._moduleName;
const callerFile = getCallerFile();
let caller = callerFile.replace(/.*xcraft-[a-z]+-([a-z0-9]+).*/, '$1');
if (caller === callerFile) {
caller = path.basename(callerFile).replace(/\.js$/, '');
}
if (this._moduleName && this._moduleName.search(caller) === -1) {
module += `/${caller}`;
}
return module;
};
/** @private */
Log.prototype._log = function (level, format, ...params) {
this._loadJournal();
const busModes = this._loadBusLog();
const testLevel = this._testLevel(level);
let mustContinue = false;
if (xBusLog) {
mustContinue = level === 3 && busModes & xBusLog.modes.overwatch;
if (!mustContinue) {
mustContinue = busModes & xBusLog.modes.event;
}
}
if (!mustContinue) {
mustContinue = testLevel;
}
if (!mustContinue) {
return;
}
if (
currentModulesNames.length &&
!currentModulesNames.includes(this._moduleName.replace(/[./].*/, ''))
) {
return;
}
const whiteBrightBold = (str) =>
currentUseColor ? colors.whiteBright(colors.bold(str)) : str;
const white = (str) => (currentUseColor ? colors.white(str) : str);
if (!format) {
format = '';
}
let overwatch = null;
if (typeof format === 'object') {
if (format._xcraftOverwatch) {
overwatch = format;
format = overwatch.err;
if (overwatch.goblin) {
format +=
`\n Goblin callstack` +
`\n id = ${overwatch.goblin.id}` +
`\n quest = ${overwatch.goblin.goblin}.${overwatch.goblin.quest}`;
if (overwatch.goblin.callerGoblin) {
format += `\n caller = ${overwatch.goblin.callerGoblin}.${overwatch.goblin.callerQuest}`;
}
}
} else {
format = util.inspect(format);
}
}
format = format.replace(/\n$/, '');
const mod = this.getModule();
const xcraft = mainModuleName;
const time = new Date().toISOString();
let args = [
white(xcraft + ' [%s]%s%s: ') + format,
whiteBrightBold(mod),
currentUseDatetime ? ' (' + time.toISOString() + ') ' : ' ',
levels[currentUseColor][level],
];
args = args.concat(params);
this.emit(this.getLevels()[level], {
module: mod,
moduleName: this._moduleName,
time: time,
message: util.format(format, ...params),
rawArgs: args,
decorate:
this._currentDecorate !== null ? this._currentDecorate : currentDecorate,
overwatch,
});
};
/**
* @type {((format: string, ...args: any) => void) & {table: (data: object) => void}}
*/
Log.prototype.verb = function (...args) {
this._log(0, ...args);
};
Log.prototype.isVerb = function () {
return this._testLevel(0);
};
/**
* @type {((format: string, ...args: any) => void) & {table: (data: object) => void}}
*/
Log.prototype.info = function (...args) {
this._log(1, ...args);
};
Log.prototype.isInfo = function () {
return this._testLevel(1);
};
/**
* @type {((format: string, ...args: any) => void) & {table: (data: object) => void}}
*/
Log.prototype.warn = function (...args) {
this._log(2, ...args);
};
Log.prototype.isWarn = function () {
return this._testLevel(2);
};
/**
* @type {((format: string, ...args: any) => void) & {table: (data: object) => void}}
*/
Log.prototype.err = function (...args) {
this._log(3, ...args);
};
Log.prototype.isErr = function () {
return this._testLevel(3);
};
/**
* @type {((format: string, ...args: any) => void) & {table: (data: object) => void}}
*/
Log.prototype.dbg = function (...args) {
this._log(4, ...args);
};
Log.prototype.progress = function (topic, position, length) {
if (!this._busLog) {
this._loadBusLog();
}
if (this._busLog) {
this._busLog.progress(topic, position, length);
}
};
Log.prototype.setDecorate = function (decorate, onlyLocal) {
if (onlyLocal) {
this._currentDecorate = !!decorate;
} else {
currentDecorate = !!decorate;
}
};
Log.prototype.setVerbosity = function (level, onlyLocal) {
if (level < 0 || level > 3) {
return;
}
if (onlyLocal) {
this._currentLevel = level;
} else {
currentLevel = level;
}
};
Log.prototype.setResponse = function (resp) {
this._resp = resp;
};
Log.prototype.color = function (useColor) {
currentUseColor = useColor;
};
Log.prototype.datetime = function (useDatetime) {
currentUseDatetime = useDatetime;
};
Log.prototype.getLevels = function () {
return levelsText.map(function (level) {
return level.toLowerCase();
});
};
Log.prototype.getModuleName = function () {
return this._moduleName;
};
module.exports = function (mod, resp) {
const logger = new Log(mod, resp);
logger.getLevels().forEach((level, index) => {
logger.on(level, (msg) => {
if (logger._testLevel(index)) {
const message = msg.decorate ? msg.rawArgs : [msg.message];
if (level === 3) {
console.error.apply(console.error, message);
} else {
console.log.apply(console.log, message);
}
}
});
});
return logger;
};
function initModes() {
if (modesInitialized === true) {
return;
}
const xEtc = require('xcraft-core-etc')();
if (!xEtc) {
return;
}
modesInitialized = true;
const logConfig = xEtc.load('xcraft-core-log');
if (logConfig && logConfig.modes && logConfig.modes.length) {
module.exports.setEnable(true, logConfig.modes);
}
}
function journalize() {
if (xJournal || xJournal === false) {
return;
}
const xEtc = require('xcraft-core-etc')();
if (!xEtc) {
return;
}
xJournal = false;
const logConfig = xEtc.load('xcraft-core-log');
if (logConfig && logConfig.journalize) {
try {
xJournal = require('xcraft-core-journal');
} catch (ex) {
if (ex.code !== 'MODULE_NOT_FOUND') {
throw ex;
}
}
}
}
module.exports.setEnable = function (en, modes) {
const changeModes = (modes) =>
modes
? modes.reduce((flags, mode) => (flags |= xBusLog.modes[mode]), 0)
: undefined;
if (en && xBusLog) {
xBusLog.addModes(changeModes(modes));
return true;
}
if (!en && !xBusLog) {
return false;
}
if (en) {
try {
xBusLog = require('xcraft-core-buslog');
xBusLog.addModes(changeModes(modes));
} catch (ex) {
if (ex.code !== 'MODULE_NOT_FOUND') {
throw ex;
}
}
} else {
xBusLog.delModes(changeModes(modes));
}
return en;
};
module.exports.setModuleNames = function (moduleNames) {
if (moduleNames && !Array.isArray(moduleNames)) {
moduleNames = [moduleNames];
}
currentModulesNames = moduleNames || [];
};
module.exports.setGlobalVerbosity = function (level) {
if (level < 0 || level > 3) {
return;
}
currentLevel = level;
};