log4js-node/lib/log4js.js
e-cloud 495d87c521 refactor: fallback some code to support Node.js v4
1. reintroduce `semver` to support code >=4 && <6
2. fallback syntax like rest parameter and default parameter
3. update `sandboxed-module` to fix "strict mode error"
2016-07-18 15:42:05 +08:00

509 lines
14 KiB
JavaScript

/* eslint no-prototype-builtins:1,no-restricted-syntax:[1, "ForInStatement"] */
'use strict';
/**
* @fileoverview log4js is a library to log in JavaScript in similar manner
* than in log4j for Java. The API should be nearly the same.
*
* <h3>Example:</h3>
* <pre>
* let logging = require('log4js');
* //add an appender that logs all messages to stdout.
* logging.addAppender(logging.consoleAppender());
* //add an appender that logs 'some-category' to a file
* logging.addAppender(logging.fileAppender('file.log'), 'some-category');
* //get a logger
* let log = logging.getLogger('some-category');
* log.setLevel(logging.levels.TRACE); //set the Level
*
* ...
*
* //call the log
* log.trace('trace me' );
* </pre>
*
* NOTE: the authors below are the original browser-based log4js authors
* don't try to contact them about bugs in this version :)
* @version 1.0
* @author Stephan Strittmatter - http://jroller.com/page/stritti
* @author Seth Chisamore - http://www.chisamore.com
* @since 2005-05-20
* @static
* Website: http://log4js.berlios.de
*/
const fs = require('fs');
const util = require('util');
const layouts = require('./layouts');
const levels = require('./levels');
const loggerModule = require('./logger');
const Logger = loggerModule.Logger;
const connectLogger = require('./connect-logger').connectLogger;
require('./appenders/console');
const ALL_CATEGORIES = '[all]';
const loggers = {};
const appenderMakers = {};
const appenderShutdowns = {};
const defaultConfig = {
appenders: [
{ type: 'console' }
],
replaceConsole: false
};
let appenders = {};
function hasLogger(logger) {
return loggers.hasOwnProperty(logger);
}
// todo: this method should be moved back to levels.js, but for loop require, need some refactor
levels.forName = function (levelStr, levelVal) {
let level;
if (typeof levelStr === 'string' && typeof levelVal === 'number') {
const levelUpper = levelStr.toUpperCase();
level = new levels.Level(levelVal, levelUpper);
loggerModule.addLevelMethods(level);
}
return level;
};
function getBufferedLogger(categoryName) {
const baseLogger = getLogger(categoryName);
const logger = {};
logger.temp = [];
logger.target = baseLogger;
logger.flush = function () {
for (let i = 0; i < logger.temp.length; i++) {
const log = logger.temp[i];
logger.target[log.level](log.message);
delete logger.temp[i];
}
};
logger.trace = function (message) { logger.temp.push({ level: 'trace', message: message }); };
logger.debug = function (message) { logger.temp.push({ level: 'debug', message: message }); };
logger.info = function (message) { logger.temp.push({ level: 'info', message: message }); };
logger.warn = function (message) { logger.temp.push({ level: 'warn', message: message }); };
logger.error = function (message) { logger.temp.push({ level: 'error', message: message }); };
logger.fatal = function (message) { logger.temp.push({ level: 'fatal', message: message }); };
return logger;
}
function normalizeCategory(category) {
return `${category}.`;
}
function doesLevelEntryContainsLogger(levelCategory, loggerCategory) {
const normalizedLevelCategory = normalizeCategory(levelCategory);
const normalizedLoggerCategory = normalizeCategory(loggerCategory);
return normalizedLoggerCategory.substring(0, normalizedLevelCategory.length) === normalizedLevelCategory;
}
function doesAppenderContainsLogger(appenderCategory, loggerCategory) {
const normalizedAppenderCategory = normalizeCategory(appenderCategory);
const normalizedLoggerCategory = normalizeCategory(loggerCategory);
return normalizedLoggerCategory.substring(0, normalizedAppenderCategory.length) === normalizedAppenderCategory;
}
/**
* Get a logger instance. Instance is cached on categoryName level.
* @static
* @param loggerCategoryName
* @return {Logger} instance of logger for the category
*/
function getLogger(loggerCategoryName) {
// Use default logger if categoryName is not specified or invalid
if (typeof loggerCategoryName !== 'string') {
loggerCategoryName = Logger.DEFAULT_CATEGORY;
}
if (!hasLogger(loggerCategoryName)) {
let level;
/* jshint -W073 */
// If there's a 'levels' entry in the configuration
if (levels.config) {
// Goes through the categories in the levels configuration entry,
// starting with the 'higher' ones.
const keys = Object.keys(levels.config).sort();
for (let idx = 0; idx < keys.length; idx++) {
const levelCategory = keys[idx];
if (doesLevelEntryContainsLogger(levelCategory, loggerCategoryName)) {
// level for the logger
level = levels.config[levelCategory];
}
}
}
/* jshint +W073 */
// Create the logger for this name if it doesn't already exist
loggers[loggerCategoryName] = new Logger(loggerCategoryName, level);
/* jshint -W083 */
let appenderList;
for (const appenderCategory in appenders) {
if (doesAppenderContainsLogger(appenderCategory, loggerCategoryName)) {
appenderList = appenders[appenderCategory];
appenderList.forEach(appender => { loggers[loggerCategoryName].addListener('log', appender); });
}
}
/* jshint +W083 */
if (appenders[ALL_CATEGORIES]) {
appenderList = appenders[ALL_CATEGORIES];
appenderList.forEach(appender => { loggers[loggerCategoryName].addListener('log', appender); });
}
}
return loggers[loggerCategoryName];
}
/**
* args are appender, then zero or more categories
*/
function addAppender() {
/* eslint prefer-rest-params:0 */
// todo: once node v4 support dropped, use rest parameter instead
let args = Array.from(arguments);
const appender = args.shift();
if (args.length === 0 || args[0] === undefined) {
args = [ALL_CATEGORIES];
}
// argument may already be an array
if (Array.isArray(args[0])) {
args = args[0];
}
args.forEach(appenderCategory => {
addAppenderToCategory(appender, appenderCategory);
if (appenderCategory === ALL_CATEGORIES) {
addAppenderToAllLoggers(appender);
} else {
for (const loggerCategory in loggers) {
if (doesAppenderContainsLogger(appenderCategory, loggerCategory)) {
loggers[loggerCategory].addListener('log', appender);
}
}
}
});
}
function addAppenderToAllLoggers(appender) {
for (const logger in loggers) {
if (hasLogger(logger)) {
loggers[logger].addListener('log', appender);
}
}
}
function addAppenderToCategory(appender, category) {
if (!appenders[category]) {
appenders[category] = [];
}
appenders[category].push(appender);
}
function clearAppenders() {
appenders = {};
for (const logger in loggers) {
if (hasLogger(logger)) {
loggers[logger].removeAllListeners('log');
}
}
}
function configureAppenders(appenderList, options) {
clearAppenders();
if (appenderList) {
appenderList.forEach(appenderConfig => {
loadAppender(appenderConfig.type);
let appender;
appenderConfig.makers = appenderMakers;
try {
appender = appenderMakers[appenderConfig.type](appenderConfig, options);
addAppender(appender, appenderConfig.category);
} catch (e) {
throw new Error(`log4js configuration problem for ${util.inspect(appenderConfig)}`, e);
}
});
}
}
function configureLevels(_levels) {
levels.config = _levels; // Keep it so we can create loggers later using this cfg
if (_levels) {
const keys = Object.keys(levels.config).sort();
/* eslint-disable guard-for-in */
for (const idx in keys) {
const category = keys[idx];
if (category === ALL_CATEGORIES) {
setGlobalLogLevel(_levels[category]);
}
for (const loggerCategory in loggers) {
if (doesLevelEntryContainsLogger(category, loggerCategory)) {
loggers[loggerCategory].setLevel(_levels[category]);
}
}
}
}
}
function setGlobalLogLevel(level) {
Logger.prototype.level = levels.toLevel(level, levels.TRACE);
}
/**
* Get the default logger instance.
* @return {Logger} instance of default logger
* @static
*/
function getDefaultLogger() {
return getLogger(Logger.DEFAULT_CATEGORY);
}
const configState = {};
function loadConfigurationFile(filename) {
if (filename) {
return JSON.parse(fs.readFileSync(filename, 'utf8'));
}
return undefined;
}
function configureOnceOff(config, options) {
if (config) {
try {
configureLevels(config.levels);
configureAppenders(config.appenders, options);
if (config.replaceConsole) {
replaceConsole();
} else {
restoreConsole();
}
} catch (e) {
throw new Error(
`Problem reading log4js config ${util.inspect(config)}. Error was '${e.message}' (${e.stack})`
);
}
}
}
function reloadConfiguration(options) {
const mtime = getMTime(configState.filename);
if (!mtime) return;
if (configState.lastMTime && (mtime.getTime() > configState.lastMTime.getTime())) {
configureOnceOff(loadConfigurationFile(configState.filename), options);
}
configState.lastMTime = mtime;
}
function getMTime(filename) {
let mtime;
try {
mtime = fs.statSync(configState.filename).mtime;
} catch (e) {
getLogger('log4js').warn(`Failed to load configuration file ${filename}`);
}
return mtime;
}
function initReloadConfiguration(filename, options) {
if (configState.timerId) {
clearInterval(configState.timerId);
delete configState.timerId;
}
configState.filename = filename;
configState.lastMTime = getMTime(filename);
configState.timerId = setInterval(reloadConfiguration, options.reloadSecs * 1000, options);
}
function configure(configurationFileOrObject, options) {
let config = configurationFileOrObject;
config = config || process.env.LOG4JS_CONFIG;
options = options || {};
if (config === undefined || config === null || typeof(config) === 'string') {
if (options.reloadSecs) {
initReloadConfiguration(config, options);
}
config = loadConfigurationFile(config) || defaultConfig;
} else {
if (options.reloadSecs) {
getLogger('log4js').warn(
'Ignoring configuration reload parameter for "object" configuration.'
);
}
}
configureOnceOff(config, options);
}
const originalConsoleFunctions = {
log: console.log,
debug: console.debug,
info: console.info,
warn: console.warn,
error: console.error
};
function replaceConsole(logger) {
function replaceWith(fn) {
return function () {
/* eslint prefer-rest-params:0 */
// todo: once node v4 support dropped, use rest parameter instead
fn.apply(logger, Array.from(arguments));
};
}
logger = logger || getLogger('console');
['log', 'debug', 'info', 'warn', 'error'].forEach(item => {
console[item] = replaceWith(item === 'log' ? logger.info : logger[item]);
});
}
function restoreConsole() {
['log', 'debug', 'info', 'warn', 'error'].forEach(item => { console[item] = originalConsoleFunctions[item]; });
}
/* eslint global-require:0 */
/**
* Load an appenderModule based on the provided appender filepath. Will first
* check if the appender path is a subpath of the log4js 'lib/appenders' directory.
* If not, it will attempt to load the the appender as complete path.
*
* @param {string} appender The filepath for the appender.
* @returns {Object|null} The required appender or null if appender could not be loaded.
* @private
*/
function requireAppender(appender) {
let appenderModule;
try {
appenderModule = require(`./appenders/${appender}`);
} catch (e) {
appenderModule = require(appender);
}
return appenderModule;
}
/**
* Load an appender. Provided the appender path to be loaded. If appenderModule is defined,
* it will be used in place of requiring the appender module.
*
* @param {string} appender The path to the appender module.
* @param {Object|void} [appenderModule] The pre-required appender module. When provided,
* instead of requiring the appender by its path, this object will be used.
* @returns {void}
* @private
*/
function loadAppender(appender, appenderModule) {
appenderModule = appenderModule || requireAppender(appender);
if (!appenderModule) {
throw new Error(`Invalid log4js appender: ${util.inspect(appender)}`);
}
log4js.appenders[appender] = appenderModule.appender.bind(appenderModule);
if (appenderModule.shutdown) {
appenderShutdowns[appender] = appenderModule.shutdown.bind(appenderModule);
}
appenderMakers[appender] = appenderModule.configure.bind(appenderModule);
}
/**
* Shutdown all log appenders. This will first disable all writing to appenders
* and then call the shutdown function each appender.
*
* @params {Function} cb - The callback to be invoked once all appenders have
* shutdown. If an error occurs, the callback will be given the error object
* as the first argument.
*/
function shutdown(cb) {
// First, disable all writing to appenders. This prevents appenders from
// not being able to be drained because of run-away log writes.
loggerModule.disableAllLogWrites();
// Call each of the shutdown functions in parallel
let completed = 0;
let error;
const shutdownFunctions = [];
function complete(err) {
error = error || err;
completed++;
if (completed >= shutdownFunctions.length) {
cb(error);
}
}
for (const category in appenderShutdowns) {
if (appenderShutdowns.hasOwnProperty(category)) {
shutdownFunctions.push(appenderShutdowns[category]);
}
}
if (!shutdownFunctions.length) {
return cb();
}
shutdownFunctions.forEach(shutdownFct => { shutdownFct(complete); });
return null;
}
/**
* @name log4js
* @namespace Log4js
* @property getBufferedLogger
* @property getLogger
* @property getDefaultLogger
* @property hasLogger
* @property addAppender
* @property loadAppender
* @property clearAppenders
* @property configure
* @property shutdown
* @property replaceConsole
* @property restoreConsole
* @property levels
* @property setGlobalLogLevel
* @property layouts
* @property appenders
* @property appenderMakers
* @property connectLogger
*/
const log4js = {
getBufferedLogger,
getLogger,
getDefaultLogger,
hasLogger,
addAppender,
loadAppender,
clearAppenders,
configure,
shutdown,
replaceConsole,
restoreConsole,
levels,
setGlobalLogLevel,
layouts,
appenders: {},
appenderMakers,
connectLogger
};
module.exports = log4js;
// set ourselves up
configure();