UNPKG

occaecatidicta

Version:
315 lines (270 loc) 8.97 kB
import * as log4js from 'log4js'; import { Configuration } from 'log4js'; import * as fs from 'fs'; import * as util from 'util'; let funcs: { [key: string]: (name: string, opts: any) => string } = { 'env': doEnv, 'args': doArgs, 'opts': doOpts }; // 0 log 1 debug, 2 info, 3 warn, 4 error let logLevel = 0; // 支持动态更改日志级别 function setOmeloxLogLevel(newLevel: 0 | 1 | 2 | 3 | 4 | 5) { console.warn('change omelox log level:', newLevel, 'oldLevel:', logLevel); logLevel = newLevel; } function getLogger(...args: string[]) { let categoryName = args[0]; let prefix = ''; for (let i = 1; i < args.length; i++) { if (i !== args.length - 1) prefix = prefix + args[i] + '] ['; else prefix = prefix + args[i]; } if (typeof categoryName === 'string') { // category name is __filename then cut the prefix path categoryName = categoryName.replace(process.cwd(), ''); } let logger = log4js.getLogger(categoryName) as any; let pLogger: any = {}; Object.setPrototypeOf(pLogger, logger); for (let key in logger) { pLogger[key] = logger[key]; } ['log', 'debug', 'info', 'warn', 'error', 'trace', 'fatal'].forEach((item, idx) => { pLogger[item] = function () { // 从根源过滤日志级别 if (idx < logLevel) { return; } let p = ''; if (!process.env.RAW_MESSAGE) { if (process.env.LOGGER_PREFIX) { if (args.length > 1) { p = '[' + process.env.LOGGER_PREFIX + prefix + '] '; } else if (process.env.LOGGER_PREFIX) { p = '[' + process.env.LOGGER_PREFIX + '] '; } } else if (args.length > 1) { p = '[' + prefix + '] '; } if (args.length && process.env.LOGGER_LINE) { p = getLine() + ': ' + p; } } if (args.length) { arguments[0] = p + arguments[0]; } if (item === 'error' && process.env.ERROR_STACK) { arguments[0] += (new Error()).stack; } logger[item].apply(logger, arguments); }; }); return pLogger as log4js.Logger; } let configState: { [key: string]: any } = {}; function initReloadConfiguration(filename: string, reloadSecs: number) { if (configState.timerId) { clearInterval(configState.timerId); delete configState.timerId; } configState.filename = filename; configState.lastMTime = getMTime(filename); configState.timerId = setInterval(reloadConfiguration, reloadSecs * 1000); } function getMTime(filename: string) { let mtime; try { mtime = fs.statSync(filename).mtime; } catch (e) { throw new Error('Cannot find file with given path: ' + filename); } return mtime; } function loadConfigurationFile(filename: string) { if (filename) { return JSON.parse(fs.readFileSync(filename, 'utf8')); } return undefined; } function reloadConfiguration() { let mtime = getMTime(configState.filename); if (!mtime) { return; } if (configState.lastMTime && (mtime.getTime() > configState.lastMTime.getTime())) { configureOnceOff(loadConfigurationFile(configState.filename)); } configState.lastMTime = mtime; } declare global { interface Console { debug(message?: any, ...optionalParams: any[]): void; } } function replaceConsole() { const logger = getLogger('logger', 'console'); console.debug = logger.debug.bind(logger); console.log = logger.info.bind(logger); console.warn = logger.warn.bind(logger); console.error = logger.error.bind(logger); console.trace = logger.trace.bind(logger); } function configureOnceOff(config: Config) { if (config) { try { configureLevels(config.categories); if (config.replaceConsole) { replaceConsole(); } } catch (e) { throw new Error( 'Problem reading log4js config ' + util.inspect(config) + '. Error was "' + e.message + '" (' + e.stack + ')' ); } } } function configureLevels(levels: { [name: string]: { level: string; } }) { if (levels) { for (let category in levels) { if (levels.hasOwnProperty(category)) { log4js.getLogger(category).level = levels[category].level; } } } } export interface ILogger { configure(configOrFilename: string | Config, opts?: { [key: string]: any }): void; } /** * Configure the logger. * Configure file just like log4js.json. And support ${scope:arg-name} format property setting. * It can replace the placeholder in runtime. * scope can be: * env: environment variables, such as: env:PATH * args: command line arguments, such as: args:1 * opts: key/value from opts argument of configure function * * @param {String|Object} config configure file name or configure object * @param {Object} opts options * @return {Void} */ export type CustomConfig = { prefix?: string, errorStack?: boolean, lineDebug?: boolean, rawMessage?: boolean, reloadSecs?: number, replaceConsole?: boolean }; export type Config = Configuration & CustomConfig; function configure(configOrFilename: string | Config, opts?: { [key: string]: any }) { let filename = configOrFilename as string; configOrFilename = configOrFilename || process.env.LOG4JS_CONFIG; opts = opts || {} as Config; let config: Config; if (typeof configOrFilename === 'string') { // modified by sw config = require(configOrFilename) as Config; // config = JSON.parse(fs.readFileSync(configOrFilename, 'utf8')) as Config; } else { config = configOrFilename; } if (config) { config = replaceProperties(config, opts); } if (config && config.errorStack) { process.env.ERROR_STACK = 'true'; } if (config && config.prefix) { process.env.LOGGER_PREFIX = config.prefix; } if (config && config.lineDebug) { process.env.LOGGER_LINE = 'true'; } if (config && config.rawMessage) { process.env.RAW_MESSAGE = 'true'; } if (filename && config && config.reloadSecs) { initReloadConfiguration(filename, config.reloadSecs); } // config object could not turn on the auto reload configure file in log4js log4js.configure(config); if (config.replaceConsole) { replaceConsole(); } } function replaceProperties(configObj: any, opts: any) { if (configObj instanceof Array) { for (let i = 0, l = configObj.length; i < l; i++) { configObj[i] = replaceProperties(configObj[i], opts); } } else if (typeof configObj === 'object') { let field; for (let f in configObj) { if (!configObj.hasOwnProperty(f)) { continue; } field = configObj[f]; if (typeof field === 'string') { configObj[f] = doReplace(field, opts); } else if (typeof field === 'object') { configObj[f] = replaceProperties(field, opts); } } } return configObj; } function doReplace(src: string, opts: object) { if (!src) { return src; } let ptn = /\$\{(.*?)\}/g; let m, pro, ts, scope, name, defaultValue, func, res = '', lastIndex = 0; while ((m = ptn.exec(src))) { pro = m[1]; ts = pro.split(':'); if (ts.length !== 2 && ts.length !== 3) { res += pro; continue; } scope = ts[0]; name = ts[1]; if (ts.length === 3) { defaultValue = ts[2]; } func = funcs[scope]; if (!func && typeof func !== 'function') { res += pro; continue; } res += src.substring(lastIndex, m.index); lastIndex = ptn.lastIndex; res += (func(name, opts) || defaultValue); } if (lastIndex < src.length) { res += src.substring(lastIndex); } return res; } function doEnv(name: string, opts: any): string { return process.env[name]; } function doArgs(name: string, opts: any): string { return process.argv[Number(name)]; } function doOpts(name: string, opts: any): string { return opts ? opts[name] : undefined; } function getLine() { let e = new Error(); // now magic will happen: get line number from callstack if (process.platform === 'win32') { return e.stack.split('\n')[3].split(':')[2]; } return e.stack.split('\n')[3].split(':')[1]; } export { getLogger, configure, setOmeloxLogLevel, };