pot-logger
Version:
A powerful log system for node.js, with zero configuration
570 lines (485 loc) • 14.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.logger = exports.setLoggers = exports.defaultAppenders = undefined;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
exports.setConfig = setConfig;
exports.overrideConsole = overrideConsole;
exports.resetConsole = resetConsole;
exports.overrideConsoleInRuntime = overrideConsoleInRuntime;
exports.getLogger = getLogger;
exports.hasLogger = hasLogger;
exports.createLogger = createLogger;
exports.ensureLogger = ensureLogger;
exports.flush = flush;
var _log4js = require('log4js');
var _log4js2 = _interopRequireDefault(_log4js);
var _path = require('path');
var _chalk = require('chalk');
var _chalk2 = _interopRequireDefault(_chalk);
var _fsExtra = require('fs-extra');
var _globby = require('globby');
var _globby2 = _interopRequireDefault(_globby);
var _deasync = require('deasync');
var _deasync2 = _interopRequireDefault(_deasync);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
const isFunction = src => typeof src === 'function';
const isObject = src => typeof src === 'object';
const isString = src => typeof src === 'string';
const noop = () => {};
const shutdown = (0, _deasync2.default)(_log4js2.default.shutdown);
_log4js2.default.configure({
appenders: { out: { type: 'stdout' } },
categories: { default: { appenders: ['out'], level: 'info' } }
});
const defaultCategory = 'out';
const defaultLogLevel = 'INFO';
const defaultFileAppender = {
type: 'file',
filename: defaultCategory,
maxLogSize: 10485760, // 10MB
backups: 5,
compress: true
};
const config = {
enable: true,
logLevel: defaultLogLevel,
logsDir: (0, _path.resolve)('.logs'),
daemon: false,
overrideConsole: false
};
const colored = function () {
let color = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'dim';
const pattern = '[%c]';
const validatColor = color => {
if (!isFunction(_chalk2.default[color])) {
throw new Error(`Category with style "${color}" is NOT support.`);
}
};
const getColor = color.split('.').reduce((chalkChaining, color) => {
validatColor(color);
return chalkChaining[color];
}, _chalk2.default);
return getColor(pattern);
};
const createAppender = function createAppender(category, description, daemon) {
const type = description.type,
color = description.color,
file = description.file,
other = _objectWithoutProperties(description, ['type', 'color', 'file']);
if (type) {
return description;
}
if (daemon) {
const categoryPattern = file ? ' ' : ' %c - ';
return _extends({}, defaultFileAppender, {
layout: {
type: 'pattern',
pattern: `[%d] [%p]${categoryPattern}%m`
}
}, other, {
filename: file ? category : defaultCategory
});
} else {
return _extends({}, other, {
type: 'console',
layout: {
type: 'pattern',
pattern: `%[%p%] ${colored(color)} %m`
}
});
}
};
const nativeConsole = {};
const SymbolEnsureLatest = Symbol('EnsureLatest');
const getLevel = (key, level, defaultLevel) => (isObject(level) ? level[key] : level) || defaultLevel;
const logSystem = function () {
let shouldReload = false;
let shouldUpdateDaemon = false;
let shouldUpdateAppenders = false;
let shouldUpdateCategories = false;
let shouldReloadConfigure = false;
let shouldReloadEnableStatus = false;
let enable = config.enable;
let appenders = {};
let categories = {};
const loggers = new Map();
const getCategories = () => {
const appenderKeys = Object.keys(appenders);
const builtInLevelAppenders = [];
if (appenders['$_all']) {
builtInLevelAppenders.push('$_all');
}
if (appenders['$_err']) {
builtInLevelAppenders.push('$_err');
}
return appenderKeys.reduce((categories, key) => {
const isCustomKey = !/^[$_]/.test(key);
const $key = '$' + key;
if (isCustomKey && appenders[$key]) {
categories[key] = {
appenders: [$key].concat(builtInLevelAppenders),
level: 'ALL'
};
}
return categories;
}, {
default: {
appenders: [`$${defaultCategory}`].concat(builtInLevelAppenders),
level: 'ALL'
}
});
};
const performUpdateAppenders = () => {
if (!shouldUpdateAppenders) {
return;
}
const logsDir = config.logsDir,
logLevel = config.logLevel;
const appenderKeys = Object.keys(appenders);
const ensureFilename = appender => {
let name = appender.filename;
if (!name) {
return appender;
}
if ((0, _path.extname)(name) !== '.log') {
name += '.log';
}
appender.filename = (0, _path.isAbsolute)(name) ? name : (0, _path.join)(logsDir, name);
};
const ensureLevelAppender = (key, appender) => {
if (key.charAt(0) === '$') return;
const $key = '$' + key;
if (~appenderKeys.indexOf($key)) return;
appenders[$key] = {
type: 'logLevelFilter',
appender: key,
level: function () {
if (key === '_err') return 'ERROR';
if (key === '_all') return 'ALL';
if (appender.level) return appender.level;
return getLevel(key, logLevel, defaultLogLevel);
}(),
maxLevel: appender.maxLevel || 'MARK'
};
};
appenderKeys.forEach(key => {
let appender = appenders[key];
// lazy appender
if (isFunction(appender)) {
appenders[key] = appender = appender();
}
ensureFilename(appender);
ensureLevelAppender(key, appender);
});
shouldUpdateAppenders = false;
};
const performUpdateCategories = () => {
if (!shouldUpdateCategories) return;
categories = getCategories();
shouldUpdateCategories = false;
};
const performUpdateDaemon = () => {
if (!shouldUpdateDaemon) {
return;
}
if (enable) {
if (config.daemon) {
Object.assign(appenders, {
_all: defaultAppenders.all,
_err: defaultAppenders.err,
[defaultCategory]: defaultAppenders.out
});
} else {
delete appenders._all;
delete appenders._err;
delete appenders.$_all;
delete appenders.$_err;
appenders[defaultCategory] = defaultAppenders.con;
}
}
shouldUpdateDaemon = false;
};
const performReloadConfigure = () => {
if (shouldReloadConfigure) {
shutdown();
_log4js2.default.configure({ appenders, categories });
}
};
const performReloadEnableStatus = () => {
if (!shouldReloadEnableStatus) {
return;
}
enable = config.enable;
shouldReloadEnableStatus = false;
};
return {
get shouldReload() {
return shouldReload;
},
get appenders() {
return appenders;
},
requestUpdateDaemon() {
shouldUpdateDaemon = true;
shouldUpdateAppenders = true;
shouldUpdateCategories = true;
shouldReloadConfigure = true;
shouldReload = true;
},
requestUpdateAppenders() {
shouldUpdateAppenders = true;
shouldReloadConfigure = true;
shouldReload = true;
},
requestUpdateCategories() {
shouldUpdateCategories = true;
shouldReloadConfigure = true;
shouldReload = true;
},
requestReloadConfigure() {
shouldReloadConfigure = true;
shouldReload = true;
},
requestReloadEnableStatus() {
shouldUpdateDaemon = true;
shouldUpdateAppenders = true;
shouldUpdateCategories = true;
shouldReloadConfigure = true;
shouldReloadEnableStatus = true;
shouldReload = true;
},
reload() {
performReloadEnableStatus();
performUpdateDaemon();
performUpdateAppenders();
performUpdateCategories();
performReloadConfigure();
shouldReload = false;
},
hasLogger(category) {
return loggers.has(category);
},
getLogger() {
let category = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultCategory;
if (loggers.has(category)) {
return loggers.get(category);
}
let origin = null;
let cache = {};
const ensureLatest = () => {
if (this.shouldReload) {
this.reload();
cache = {};
}
if (!origin) origin = _log4js2.default.getLogger(category);
return origin;
};
const reflect = name => {
ensureLatest();
if (cache[name]) return cache[name];
return cache[name] = enable ? origin[name].bind(origin) : noop;
};
// prettier-ignore
const logger = {
[SymbolEnsureLatest]: ensureLatest,
get all() {
return reflect('all');
},
get trace() {
return reflect('trace');
},
get debug() {
return reflect('debug');
},
get info() {
return reflect('info');
},
get log() {
return reflect('info');
},
get warn() {
return reflect('warn');
},
get error() {
return reflect('error');
},
get fatal() {
return reflect('fatal');
},
get mark() {
return reflect('mark');
}
};
loggers.set(category, logger);
return logger;
}
};
}();
const defaultAppenders = exports.defaultAppenders = {
out: _extends({}, defaultFileAppender),
err: _extends({}, defaultFileAppender, {
filename: 'err'
}),
all: _extends({}, defaultFileAppender, {
filename: 'all'
}),
con: {
type: 'console',
layout: {
type: 'pattern',
pattern: '%[%p%] %m'
}
}
};
function setConfig(maybeKey, maybeVal) {
const prev = _extends({}, config);
Object.assign(config, isObject(maybeKey) ? maybeKey : { [maybeKey]: maybeVal });
if (prev.enable !== config.enable) {
logSystem.requestReloadEnableStatus();
}
if (prev.daemon !== config.daemon) {
logSystem.requestUpdateDaemon();
}
if (prev.logLevel !== config.logLevel) {
Object.keys(logSystem.appenders).forEach(key => {
const $key = '$' + key;
const levelAppender = logSystem.appenders[$key];
if (levelAppender) {
const newLevel = getLevel(key, config.logLevel);
if (newLevel) levelAppender.level = newLevel;
}
});
logSystem.requestReloadConfigure();
}
if (prev.logsDir !== config.logsDir) {
logSystem.requestUpdateAppenders();
}
if (prev.overrideConsole && !config.overrideConsole) {
resetConsole();
} else if (!prev.overrideConsole && config.overrideConsole) {
overrideConsole();
}
}
function overrideConsole() {
let logger = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : logSystem.getLogger();
let filter = arguments[1];
logger = logger[SymbolEnsureLatest] ? logger[SymbolEnsureLatest]() : logger;
const createReflection = method => {
return function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
if (isFunction(filter)) {
args = filter(args, method, logger);
}
if (args.length) {
logger[method].apply(logger, args);
}
};
};
nativeConsole.trace = console.trace;
nativeConsole.debug = console.debug;
nativeConsole.dir = console.dir;
nativeConsole.info = console.info;
nativeConsole.log = console.log;
nativeConsole.warn = console.warn;
nativeConsole.error = console.error;
console.trace = createReflection('trace');
console.debug = createReflection('debug');
console.dir = createReflection('info');
console.log = createReflection('info');
console.warn = createReflection('warn');
console.error = createReflection('error');
}
function resetConsole() {
console.trace = nativeConsole.trace;
console.debug = nativeConsole.debug;
console.dir = nativeConsole.dir;
console.log = nativeConsole.log;
console.warn = nativeConsole.warn;
console.error = nativeConsole.error;
}
function overrideConsoleInRuntime(start, logger, filter) {
overrideConsole(logger, filter);
return Promise.resolve(start()).then(() => {
resetConsole();
config.overrideConsole && overrideConsole();
});
}
function getLogger(category) {
return logSystem.getLogger(category);
}
function hasLogger(category) {
return logSystem.hasLogger(category);
}
function createLogger(category, description) {
if (logSystem.hasLogger(category)) {
throw new Error(`Failed to create logger: "${category}" has already exists.`);
}
let appender;
if (isObject(description)) {
appender = createAppender(category, description, config.daemon);
} else {
let color;
if (isString(description)) {
color = description;
}
const ref = {
category,
get daemon() {
return config.daemon;
},
get defaultDaemonAppender() {
return _extends({}, defaultFileAppender);
},
get defaultConsoleAppender() {
return {
type: 'console',
layout: {
type: 'pattern',
pattern: `%[%p%] ${colored(color)} %m`
}
};
}
};
appender = () => {
if (isFunction(description)) {
return createAppender(category, description(ref), config.daemon);
} else {
const finalDescription = { color: description };
return createAppender(category, finalDescription, config.daemon);
}
};
}
logSystem.appenders[category] = appender;
// TODO: set level appender
logSystem.requestUpdateAppenders();
logSystem.requestUpdateCategories();
return logSystem.getLogger(category);
}
function ensureLogger(category, description) {
return hasLogger(category) ? getLogger(category) : createLogger(category, description);
}
function flush() {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _options$mode = options.mode;
const mode = _options$mode === undefined ? 0o644 : _options$mode;
var _options$removeDir = options.removeDir;
const removeDir = _options$removeDir === undefined ? false : _options$removeDir;
var _options$logsDir = options.logsDir;
const logsDir = _options$logsDir === undefined ? config.logsDir : _options$logsDir;
if (removeDir) return (0, _fsExtra.remove)(logsDir);
const glob = pattern => (0, _globby2.default)(pattern, { absolute: true });
const each = iterator => arr => Promise.all(arr.map(iterator));
const logFilesPattern = `${logsDir}/*.log`;
const rotatedFilesPattern = `${logsDir}/*.log.[0-9]*`;
const renewFile = file => (0, _fsExtra.open)(file, 'w', mode).then(_fsExtra.close);
const removeFile = file => (0, _fsExtra.remove)(file);
return Promise.all([glob(logFilesPattern).then(each(renewFile)), glob(rotatedFilesPattern).then(each(removeFile))]);
}
const setLoggers = exports.setLoggers = setConfig;
const logger = exports.logger = createLogger(defaultCategory, defaultAppenders.con);
exports.default = logger;