mirror of
https://github.com/log4js-node/log4js-node.git
synced 2025-12-08 19:26:01 +00:00
refactor(all): basic logging to stdout works again
- removed config reloading - removed console replacement - added recording appender - added config validation - changed config format
This commit is contained in:
parent
db90ea6ecc
commit
5283782ba0
23
lib/appenders/recording.js
Normal file
23
lib/appenders/recording.js
Normal file
@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
let recordedEvents = [];
|
||||
|
||||
function configure() {
|
||||
return function (logEvent) {
|
||||
recordedEvents.push(logEvent);
|
||||
};
|
||||
}
|
||||
|
||||
function replay() {
|
||||
return recordedEvents;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
recordedEvents = [];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
configure: configure,
|
||||
replay: replay,
|
||||
reset: reset
|
||||
};
|
||||
@ -1,21 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const layouts = require('../layouts');
|
||||
|
||||
function stdoutAppender(layout, timezoneOffset) {
|
||||
layout = layout || layouts.colouredLayout;
|
||||
return function (loggingEvent) {
|
||||
process.stdout.write(`${layout(loggingEvent, timezoneOffset)}\n`);
|
||||
};
|
||||
}
|
||||
|
||||
function configure(config) {
|
||||
let layout;
|
||||
function configure(config, layouts) {
|
||||
let layout = layouts.colouredLayout;
|
||||
if (config.layout) {
|
||||
layout = layouts.layout(config.layout.type, config.layout);
|
||||
}
|
||||
return stdoutAppender(layout, config.timezoneOffset);
|
||||
}
|
||||
|
||||
exports.appender = stdoutAppender;
|
||||
exports.configure = configure;
|
||||
|
||||
135
lib/configuration.js
Normal file
135
lib/configuration.js
Normal file
@ -0,0 +1,135 @@
|
||||
'use strict';
|
||||
|
||||
const util = require('util');
|
||||
const levels = require('./levels');
|
||||
const layouts = require('./layouts');
|
||||
|
||||
function not(thing) {
|
||||
return !thing;
|
||||
}
|
||||
|
||||
function anObject(thing) {
|
||||
return thing && typeof thing === 'object' && !Array.isArray(thing);
|
||||
}
|
||||
|
||||
class Configuration {
|
||||
|
||||
throwExceptionIf(checks, message) {
|
||||
const tests = Array.isArray(checks) ? checks : [checks];
|
||||
tests.forEach((test) => {
|
||||
if (test) {
|
||||
throw new Error(
|
||||
`Problem with log4js configuration: (${util.inspect(this.candidate, { depth: 5 })}) - ${message}`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tryLoading(path) {
|
||||
try {
|
||||
return require(path); //eslint-disable-line
|
||||
} catch (e) {
|
||||
// if the module was found, and we still got an error, then raise it
|
||||
this.throwExceptionIf(
|
||||
e.code !== 'MODULE_NOT_FOUND',
|
||||
`appender "${path}" could not be loaded (error was: ${e})`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
loadAppenderModule(type) {
|
||||
return this.tryLoading(`./appenders/${type}`) || this.tryLoading(type);
|
||||
}
|
||||
|
||||
createAppender(name, config) {
|
||||
const appenderModule = this.loadAppenderModule(config.type);
|
||||
this.throwExceptionIf(
|
||||
not(appenderModule),
|
||||
`appender "${name}" is not valid (type "${config.type}" could not be found)`
|
||||
);
|
||||
return appenderModule.configure(config, layouts, this.configuredAppenders.get.bind(this.configuredAppenders));
|
||||
}
|
||||
|
||||
get appenders() {
|
||||
return this.configuredAppenders;
|
||||
}
|
||||
|
||||
set appenders(appenderConfig) {
|
||||
const appenderNames = Object.keys(appenderConfig);
|
||||
this.throwExceptionIf(not(appenderNames.length), 'must define at least one appender.');
|
||||
|
||||
this.configuredAppenders = new Map();
|
||||
appenderNames.forEach((name) => {
|
||||
this.throwExceptionIf(
|
||||
not(appenderConfig[name].type),
|
||||
`appender "${name}" is not valid (must be an object with property "type")`
|
||||
);
|
||||
|
||||
this.configuredAppenders.set(name, this.createAppender(name, appenderConfig[name]));
|
||||
});
|
||||
}
|
||||
|
||||
get categories() {
|
||||
return this.configuredCategories;
|
||||
}
|
||||
|
||||
set categories(categoryConfig) {
|
||||
const categoryNames = Object.keys(categoryConfig);
|
||||
this.throwExceptionIf(not(categoryNames.length), 'must define at least one category.');
|
||||
|
||||
this.configuredCategories = new Map();
|
||||
categoryNames.forEach((name) => {
|
||||
const category = categoryConfig[name];
|
||||
this.throwExceptionIf(
|
||||
[
|
||||
not(category.appenders),
|
||||
not(category.level)
|
||||
],
|
||||
`category "${name}" is not valid (must be an object with properties "appenders" and "level")`
|
||||
);
|
||||
|
||||
this.throwExceptionIf(
|
||||
not(Array.isArray(category.appenders)),
|
||||
`category "${name}" is not valid (appenders must be an array of appender names)`
|
||||
);
|
||||
|
||||
this.throwExceptionIf(
|
||||
not(category.appenders.length),
|
||||
`category "${name}" is not valid (appenders must contain at least one appender name)`
|
||||
);
|
||||
|
||||
const appenders = [];
|
||||
category.appenders.forEach((appender) => {
|
||||
this.throwExceptionIf(
|
||||
not(this.configuredAppenders.get(appender)),
|
||||
`category "${name}" is not valid (appender "${appender}" is not defined)`
|
||||
);
|
||||
appenders.push(this.appenders.get(appender));
|
||||
});
|
||||
|
||||
this.throwExceptionIf(
|
||||
not(levels.toLevel(category.level)),
|
||||
`category "${name}" is not valid (level "${category.level}" not recognised;` +
|
||||
` valid levels are ${levels.levels.join(', ')})`
|
||||
);
|
||||
|
||||
this.configuredCategories.set(name, { appenders: appenders, level: levels.toLevel(category.level) });
|
||||
});
|
||||
|
||||
this.throwExceptionIf(not(categoryConfig.default), 'must define a "default" category.');
|
||||
}
|
||||
|
||||
constructor(candidate) {
|
||||
this.candidate = candidate;
|
||||
|
||||
this.throwExceptionIf(not(anObject(candidate)), 'must be an object.');
|
||||
this.throwExceptionIf(not(anObject(candidate.appenders)), 'must have a property "appenders" of type object.');
|
||||
this.throwExceptionIf(not(anObject(candidate.categories)), 'must have a property "categories" of type object.');
|
||||
|
||||
this.appenders = candidate.appenders;
|
||||
this.categories = candidate.categories;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Configuration;
|
||||
@ -83,3 +83,15 @@ module.exports = {
|
||||
Level: Level,
|
||||
getLevel: getLevel
|
||||
};
|
||||
|
||||
module.exports.levels = [
|
||||
module.exports.ALL,
|
||||
module.exports.TRACE,
|
||||
module.exports.DEBUG,
|
||||
module.exports.INFO,
|
||||
module.exports.WARN,
|
||||
module.exports.ERROR,
|
||||
module.exports.FATAL,
|
||||
module.exports.MARK,
|
||||
module.exports.OFF
|
||||
];
|
||||
|
||||
486
lib/log4js.js
486
lib/log4js.js
@ -1,23 +1,13 @@
|
||||
/* eslint no-prototype-builtins:1,no-restricted-syntax:[1, "ForInStatement"],no-plusplus:0 */
|
||||
|
||||
'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.
|
||||
* than in log4j for Java (but not really).
|
||||
*
|
||||
* <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
|
||||
*
|
||||
* ...
|
||||
* const logging = require('log4js');
|
||||
* const log = logging.getLogger('some-category');
|
||||
*
|
||||
* //call the log
|
||||
* log.trace('trace me' );
|
||||
@ -33,412 +23,75 @@
|
||||
* Website: http://log4js.berlios.de
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
const layouts = require('./layouts');
|
||||
const Configuration = require('./configuration');
|
||||
const levels = require('./levels');
|
||||
const loggerModule = require('./logger');
|
||||
const Logger = require('./logger').Logger;
|
||||
const connectLogger = require('./connect-logger').connectLogger;
|
||||
|
||||
const Logger = loggerModule.Logger;
|
||||
|
||||
const ALL_CATEGORIES = '[all]';
|
||||
const loggers = {};
|
||||
const appenderMakers = {};
|
||||
const appenderShutdowns = {};
|
||||
const defaultConfig = {
|
||||
appenders: [
|
||||
{ type: 'stdout' }
|
||||
],
|
||||
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);
|
||||
appenders: {
|
||||
STDOUT: { type: 'stdout' }
|
||||
},
|
||||
categories: {
|
||||
default: { appenders: ['STDOUT'], level: 'TRACE' }
|
||||
}
|
||||
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 });
|
||||
};
|
||||
let config;
|
||||
let enabled = true;
|
||||
|
||||
return logger;
|
||||
function configForCategory(category) {
|
||||
if (config.categories.has(category)) {
|
||||
return config.categories.get(category);
|
||||
}
|
||||
if (category.indexOf('.') > 0) {
|
||||
return configForCategory(category.substring(0, category.lastIndexOf('.')));
|
||||
}
|
||||
return configForCategory('default');
|
||||
}
|
||||
|
||||
function normalizeCategory(category) {
|
||||
return `${category}.`;
|
||||
function appendersForCategory(category) {
|
||||
return configForCategory(category).appenders;
|
||||
}
|
||||
|
||||
function doesLevelEntryContainsLogger(levelCategory, loggerCategory) {
|
||||
const normalizedLevelCategory = normalizeCategory(levelCategory);
|
||||
const normalizedLoggerCategory = normalizeCategory(loggerCategory);
|
||||
return normalizedLoggerCategory.substring(0, normalizedLevelCategory.length) === normalizedLevelCategory;
|
||||
function levelForCategory(category) {
|
||||
return configForCategory(category).level;
|
||||
}
|
||||
|
||||
function doesAppenderContainsLogger(appenderCategory, loggerCategory) {
|
||||
const normalizedAppenderCategory = normalizeCategory(appenderCategory);
|
||||
const normalizedLoggerCategory = normalizeCategory(loggerCategory);
|
||||
return normalizedLoggerCategory.substring(0, normalizedAppenderCategory.length) === normalizedAppenderCategory;
|
||||
function sendLogEventToAppender(logEvent) {
|
||||
if (!enabled) return;
|
||||
const appenders = appendersForCategory(logEvent.categoryName);
|
||||
appenders.forEach((appender) => {
|
||||
appender(logEvent);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a logger instance. Instance is cached on categoryName level.
|
||||
* Get a logger instance.
|
||||
* @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];
|
||||
function getLogger(category) {
|
||||
const cat = category || 'default';
|
||||
return new Logger(cat, levelForCategory(cat), sendLogEventToAppender);
|
||||
}
|
||||
|
||||
/**
|
||||
* args are appender, optional shutdown function, 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();
|
||||
// check for a shutdown fn
|
||||
if (args.length > 0 && typeof args[0] === 'function') {
|
||||
appenderShutdowns[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() {
|
||||
// if we're calling clearAppenders, we're probably getting ready to write
|
||||
// so turn log writes back on, just in case this is after a shutdown
|
||||
loggerModule.enableAllLogWrites();
|
||||
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;
|
||||
return filename;
|
||||
}
|
||||
|
||||
function configureOnceOff(config, options) {
|
||||
if (config) {
|
||||
try {
|
||||
restoreConsole();
|
||||
configureLevels(config.levels);
|
||||
configureAppenders(config.appenders, options);
|
||||
function configure(configurationFileOrObject) {
|
||||
let configObject = configurationFileOrObject;
|
||||
|
||||
if (config.replaceConsole) {
|
||||
replaceConsole();
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Problem reading log4js config ${util.inspect(config)}. Error was '${e.message}' (${e.stack})`
|
||||
);
|
||||
}
|
||||
if (typeof configObject === 'string') {
|
||||
configObject = loadConfigurationFile(configurationFileOrObject);
|
||||
}
|
||||
}
|
||||
|
||||
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) { // eslint-disable-line
|
||||
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}`); // eslint-disable-line
|
||||
} catch (e) {
|
||||
appenderModule = require(appender); // eslint-disable-line
|
||||
}
|
||||
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);
|
||||
config = new Configuration(configObject);
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -452,39 +105,27 @@ function loadAppender(appender, appenderModule) {
|
||||
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();
|
||||
|
||||
// turn off config reloading
|
||||
if (configState.timerId) {
|
||||
clearInterval(configState.timerId);
|
||||
}
|
||||
enabled = false;
|
||||
|
||||
// Call each of the shutdown functions in parallel
|
||||
const appenders = Array.from(config.appenders.values());
|
||||
const shutdownFunctions = appenders.reduceRight((accum, next) => (next.shutdown ? accum + 1 : accum), 0);
|
||||
let completed = 0;
|
||||
let error;
|
||||
const shutdownFunctions = [];
|
||||
|
||||
function complete(err) {
|
||||
error = error || err;
|
||||
completed++;
|
||||
if (completed >= shutdownFunctions.length) {
|
||||
completed += 1;
|
||||
if (completed >= shutdownFunctions) {
|
||||
cb(error);
|
||||
}
|
||||
}
|
||||
|
||||
for (const category in appenderShutdowns) {
|
||||
if (appenderShutdowns.hasOwnProperty(category)) {
|
||||
shutdownFunctions.push(appenderShutdowns[category]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!shutdownFunctions.length) {
|
||||
if (shutdownFunctions === 0) {
|
||||
return cb();
|
||||
}
|
||||
|
||||
shutdownFunctions.forEach((shutdownFct) => {
|
||||
shutdownFct(complete);
|
||||
});
|
||||
appenders.forEach(a => a.shutdown(complete));
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -492,49 +133,20 @@ function shutdown(cb) {
|
||||
/**
|
||||
* @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();
|
||||
configure(process.env.LOG4JS_CONFIG || defaultConfig);
|
||||
|
||||
@ -3,11 +3,8 @@
|
||||
'use strict';
|
||||
|
||||
const levels = require('./levels');
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const DEFAULT_CATEGORY = '[default]';
|
||||
|
||||
let logWritesEnabled = true;
|
||||
// let logWritesEnabled = true;
|
||||
|
||||
/**
|
||||
* @name LoggingEvent
|
||||
@ -20,15 +17,13 @@ class LoggingEvent {
|
||||
* @param {String} categoryName name of category
|
||||
* @param {Log4js.Level} level level of message
|
||||
* @param {Array} data objects to log
|
||||
* @param {Logger} logger the associated logger
|
||||
* @author Seth Chisamore
|
||||
*/
|
||||
constructor(categoryName, level, data, logger) {
|
||||
constructor(categoryName, level, data) {
|
||||
this.startTime = new Date();
|
||||
this.categoryName = categoryName;
|
||||
this.data = data;
|
||||
this.level = level;
|
||||
this.logger = logger;
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,38 +34,30 @@ class LoggingEvent {
|
||||
* @name Logger
|
||||
* @namespace Log4js
|
||||
* @param name name of category to log to
|
||||
* @param level
|
||||
* @param level - the loglevel for the category
|
||||
* @param dispatch - the function which will receive the logevents
|
||||
*
|
||||
* @author Stephan Strittmatter
|
||||
*/
|
||||
class Logger extends EventEmitter {
|
||||
constructor(name, level) {
|
||||
super();
|
||||
|
||||
this.category = name || DEFAULT_CATEGORY;
|
||||
|
||||
if (level) {
|
||||
this.setLevel(level);
|
||||
}
|
||||
class Logger {
|
||||
constructor(name, level, dispatch) {
|
||||
this.category = name;
|
||||
this.level = levels.toLevel(level, levels.TRACE);
|
||||
this.dispatch = dispatch;
|
||||
}
|
||||
|
||||
setLevel(level) {
|
||||
this.level = levels.toLevel(level, this.level || levels.TRACE);
|
||||
}
|
||||
|
||||
removeLevel() {
|
||||
delete this.level;
|
||||
}
|
||||
|
||||
log() {
|
||||
/* eslint prefer-rest-params:0 */
|
||||
// todo: once node v4 support dropped, use rest parameter instead
|
||||
const args = Array.from(arguments);
|
||||
const logLevel = levels.toLevel(args[0], levels.INFO);
|
||||
if (!this.isLevelEnabled(logLevel)) {
|
||||
return;
|
||||
if (this.isLevelEnabled(logLevel)) {
|
||||
this._log(logLevel, args.slice(1));
|
||||
}
|
||||
this._log(logLevel, args.slice(1));
|
||||
}
|
||||
|
||||
isLevelEnabled(otherLevel) {
|
||||
@ -78,16 +65,11 @@ class Logger extends EventEmitter {
|
||||
}
|
||||
|
||||
_log(level, data) {
|
||||
const loggingEvent = new LoggingEvent(this.category, level, data, this);
|
||||
this.emit('log', loggingEvent);
|
||||
const loggingEvent = new LoggingEvent(this.category, level, data);
|
||||
this.dispatch(loggingEvent);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.DEFAULT_CATEGORY = DEFAULT_CATEGORY;
|
||||
Logger.prototype.level = levels.TRACE;
|
||||
|
||||
['Trace', 'Debug', 'Info', 'Warn', 'Error', 'Fatal', 'Mark'].forEach(addLevelMethods);
|
||||
|
||||
function addLevelMethods(target) {
|
||||
const level = levels.toLevel(target);
|
||||
|
||||
@ -103,30 +85,32 @@ function addLevelMethods(target) {
|
||||
/* eslint prefer-rest-params:0 */
|
||||
// todo: once node v4 support dropped, use rest parameter instead
|
||||
const args = Array.from(arguments);
|
||||
if (logWritesEnabled && this.isLevelEnabled(level)) {
|
||||
if (/* logWritesEnabled &&*/ this.isLevelEnabled(level)) {
|
||||
this._log(level, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
levels.levels.forEach(addLevelMethods);
|
||||
|
||||
/**
|
||||
* Disable all log writes.
|
||||
* @returns {void}
|
||||
*/
|
||||
function disableAllLogWrites() {
|
||||
logWritesEnabled = false;
|
||||
}
|
||||
// function disableAllLogWrites() {
|
||||
// logWritesEnabled = false;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Enable log writes.
|
||||
* @returns {void}
|
||||
*/
|
||||
function enableAllLogWrites() {
|
||||
logWritesEnabled = true;
|
||||
}
|
||||
// function enableAllLogWrites() {
|
||||
// logWritesEnabled = true;
|
||||
// }
|
||||
|
||||
module.exports.LoggingEvent = LoggingEvent;
|
||||
module.exports.Logger = Logger;
|
||||
module.exports.disableAllLogWrites = disableAllLogWrites;
|
||||
module.exports.enableAllLogWrites = enableAllLogWrites;
|
||||
module.exports.addLevelMethods = addLevelMethods;
|
||||
// module.exports.disableAllLogWrites = disableAllLogWrites;
|
||||
// module.exports.enableAllLogWrites = enableAllLogWrites;
|
||||
// module.exports.addLevelMethods = addLevelMethods;
|
||||
|
||||
@ -3,100 +3,7 @@
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
|
||||
function makeTestAppender() {
|
||||
return {
|
||||
configure: function (config, options) {
|
||||
this.configureCalled = true;
|
||||
this.config = config;
|
||||
this.options = options;
|
||||
return this.appender();
|
||||
},
|
||||
appender: function () {
|
||||
const self = this;
|
||||
return function (logEvt) {
|
||||
self.logEvt = logEvt;
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test('log4js configure', (batch) => {
|
||||
batch.test('when appenders specified by type', (t) => {
|
||||
const testAppender = makeTestAppender();
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: {
|
||||
'./appenders/cheese': testAppender
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: [
|
||||
{ type: 'cheese', flavour: 'gouda' }
|
||||
]
|
||||
},
|
||||
{ pants: 'yes' }
|
||||
);
|
||||
t.ok(testAppender.configureCalled, 'should load appender');
|
||||
t.equal(testAppender.config.flavour, 'gouda', 'should pass config to appender');
|
||||
t.equal(testAppender.options.pants, 'yes', 'should pass log4js options to appender');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when core appender loaded via loadAppender', (t) => {
|
||||
const testAppender = makeTestAppender();
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: { './appenders/cheese': testAppender }
|
||||
}
|
||||
);
|
||||
|
||||
log4js.loadAppender('cheese');
|
||||
|
||||
t.ok(log4js.appenders.cheese, 'should load appender from ../../lib/appenders');
|
||||
t.type(log4js.appenderMakers.cheese, 'function', 'should add appender configure function to appenderMakers');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when appender in node_modules loaded via loadAppender', (t) => {
|
||||
const testAppender = makeTestAppender();
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: { 'some/other/external': testAppender }
|
||||
}
|
||||
);
|
||||
|
||||
log4js.loadAppender('some/other/external');
|
||||
t.ok(log4js.appenders['some/other/external'], 'should load appender via require');
|
||||
t.type(
|
||||
log4js.appenderMakers['some/other/external'], 'function',
|
||||
'should add appender configure function to appenderMakers'
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when appender object loaded via loadAppender', (t) => {
|
||||
const testAppender = makeTestAppender();
|
||||
const log4js = sandbox.require('../../lib/log4js');
|
||||
|
||||
log4js.loadAppender('some/other/external', testAppender);
|
||||
|
||||
t.ok(log4js.appenders['some/other/external'], 'should load appender with provided object');
|
||||
t.type(
|
||||
log4js.appenderMakers['some/other/external'], 'function',
|
||||
'should add appender configure function to appenderMakers'
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when configuration file loaded via LOG4JS_CONFIG env variable', (t) => {
|
||||
process.env.LOG4JS_CONFIG = 'some/path/to/mylog4js.json';
|
||||
let fileRead = 0;
|
||||
@ -106,8 +13,10 @@ test('log4js configure', (batch) => {
|
||||
|
||||
const fakeFS = {
|
||||
config: {
|
||||
appenders: [{ type: 'console', layout: { type: 'messagePassThrough' } }],
|
||||
levels: { 'a-test': 'INFO' }
|
||||
appenders: {
|
||||
console: { type: 'console', layout: { type: 'messagePassThrough' } }
|
||||
},
|
||||
categories: { default: { appenders: ['console'], level: 'INFO' } }
|
||||
},
|
||||
readdirSync: function (dir) {
|
||||
return require('fs').readdirSync(dir);
|
||||
|
||||
300
test/tap/configuration-validation-test.js
Normal file
300
test/tap/configuration-validation-test.js
Normal file
@ -0,0 +1,300 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const Configuration = require('../../lib/configuration');
|
||||
const util = require('util');
|
||||
const sandbox = require('sandboxed-module');
|
||||
|
||||
function testAppender(label) {
|
||||
return {
|
||||
configure: function (config, layouts, findAppender) {
|
||||
return {
|
||||
configureCalled: true,
|
||||
type: config.type,
|
||||
label: label,
|
||||
config: config,
|
||||
layouts: layouts,
|
||||
findAppender: findAppender
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test('log4js configuration validation', (batch) => {
|
||||
batch.test('should give error if config is just plain silly', (t) => {
|
||||
[null, undefined, '', []].forEach((config) => {
|
||||
const expectedError = new Error(
|
||||
`Problem with log4js configuration: (${util.inspect(config)}) - must be an object.`
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration(config),
|
||||
expectedError
|
||||
);
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if config is an empty object', (t) => {
|
||||
const expectedError = new Error(
|
||||
'Problem with log4js configuration: ({}) - must have a property "appenders" of type object.'
|
||||
);
|
||||
t.throws(() => new Configuration({}), expectedError);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if config has no appenders', (t) => {
|
||||
const expectedError = new Error(
|
||||
'Problem with log4js configuration: ({ categories: {} }) - must have a property "appenders" of type object.'
|
||||
);
|
||||
t.throws(() => new Configuration({ categories: {} }), expectedError);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if config has no categories', (t) => {
|
||||
const expectedError = new Error(
|
||||
'Problem with log4js configuration: ({ appenders: {} }) - must have a property "categories" of type object.'
|
||||
);
|
||||
t.throws(() => new Configuration({ appenders: {} }), expectedError);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if appenders is not an object', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ({ appenders: [], categories: [] })' +
|
||||
' - must have a property "appenders" of type object.'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: [], categories: [] }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if appenders are not all valid', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ({ appenders: { thing: \'cheese\' }, categories: {} })' +
|
||||
' - appender "thing" is not valid (must be an object with property "type")'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: { thing: 'cheese' }, categories: {} }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should require at least one appender', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ({ appenders: {}, categories: {} })' +
|
||||
' - must define at least one appender.'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: {}, categories: {} }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if categories are not all valid', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n categories: { thing: \'cheese\' } })' +
|
||||
' - category "thing" is not valid (must be an object with properties "appenders" and "level")'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: { stdout: { type: 'stdout' } }, categories: { thing: 'cheese' } }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if default category not defined', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n' +
|
||||
' categories: { thing: { appenders: [ \'stdout\' ], level: \'ERROR\' } } })' +
|
||||
' - must define a "default" category.'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: ['stdout'], level: 'ERROR' } } }
|
||||
),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should require at least one category', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ({ appenders: { stdout: { type: \'stdout\' } }, categories: {} })' +
|
||||
' - must define at least one category.'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: { stdout: { type: 'stdout' } }, categories: {} }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if category.appenders is not an array', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n' +
|
||||
' categories: { thing: { appenders: {}, level: \'ERROR\' } } })' +
|
||||
' - category "thing" is not valid (appenders must be an array of appender names)'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: {}, level: 'ERROR' } }
|
||||
}),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if category.appenders is empty', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n' +
|
||||
' categories: { thing: { appenders: [], level: \'ERROR\' } } })' +
|
||||
' - category "thing" is not valid (appenders must contain at least one appender name)'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: [], level: 'ERROR' } }
|
||||
}),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if categories do not refer to valid appenders', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n' +
|
||||
' categories: { thing: { appenders: [ \'cheese\' ], level: \'ERROR\' } } })' +
|
||||
' - category "thing" is not valid (appender "cheese" is not defined)'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: ['cheese'], level: 'ERROR' } }
|
||||
}),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if category level is not valid', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n' +
|
||||
' categories: { default: { appenders: [ \'stdout\' ], level: \'Biscuits\' } } })' +
|
||||
' - category "default" is not valid (level "Biscuits" not recognised; ' +
|
||||
'valid levels are ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, MARK, OFF)'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { default: { appenders: ['stdout'], level: 'Biscuits' } }
|
||||
}),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if appender type cannot be found', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { thing: { type: \'cheese\' } },\n' +
|
||||
' categories: { default: { appenders: [ \'thing\' ], level: \'ERROR\' } } })' +
|
||||
' - appender "thing" is not valid (type "cheese" could not be found)'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { thing: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
}),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should create appender instances', (t) => {
|
||||
const SandboxedConfiguration = sandbox.require(
|
||||
'../../lib/configuration',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: {
|
||||
cheese: testAppender('cheesy')
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = new SandboxedConfiguration({
|
||||
appenders: { thing: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
});
|
||||
|
||||
const thing = config.appenders.get('thing');
|
||||
t.ok(thing.configureCalled);
|
||||
t.equal(thing.type, 'cheese');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should load appenders from core first', (t) => {
|
||||
const SandboxedConfiguration = sandbox.require(
|
||||
'../../lib/configuration',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: {
|
||||
'./appenders/cheese': testAppender('correct'),
|
||||
cheese: testAppender('wrong')
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = new SandboxedConfiguration({
|
||||
appenders: { thing: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
});
|
||||
|
||||
const thing = config.appenders.get('thing');
|
||||
t.ok(thing.configureCalled);
|
||||
t.equal(thing.type, 'cheese');
|
||||
t.equal(thing.label, 'correct');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should pass config, layout, findAppender to appenders', (t) => {
|
||||
const SandboxedConfiguration = sandbox.require(
|
||||
'../../lib/configuration',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: {
|
||||
cheese: testAppender('cheesy')
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = new SandboxedConfiguration({
|
||||
appenders: { thing: { type: 'cheese', foo: 'bar' }, thing2: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
});
|
||||
|
||||
const thing = config.appenders.get('thing');
|
||||
t.ok(thing.configureCalled);
|
||||
t.equal(thing.type, 'cheese');
|
||||
t.equal(thing.config.foo, 'bar');
|
||||
t.type(thing.layouts, 'object');
|
||||
t.type(thing.layouts.basicLayout, 'function');
|
||||
t.type(thing.findAppender, 'function');
|
||||
t.type(thing.findAppender('thing2'), 'object');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.end();
|
||||
});
|
||||
@ -2,82 +2,16 @@
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
|
||||
function setupConsoleTest() {
|
||||
const fakeConsole = {};
|
||||
const logEvents = [];
|
||||
|
||||
['trace', 'debug', 'log', 'info', 'warn', 'error'].forEach((fn) => {
|
||||
fakeConsole[fn] = function () {
|
||||
throw new Error('this should not be called.');
|
||||
};
|
||||
});
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
globals: {
|
||||
console: fakeConsole
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.clearAppenders();
|
||||
log4js.addAppender((evt) => {
|
||||
logEvents.push(evt);
|
||||
});
|
||||
|
||||
return { log4js: log4js, logEvents: logEvents, fakeConsole: fakeConsole };
|
||||
}
|
||||
const recording = require('../../lib/appenders/recording');
|
||||
|
||||
test('log4js', (batch) => {
|
||||
batch.test('getBufferedLogger', (t) => {
|
||||
const log4js = require('../../lib/log4js');
|
||||
log4js.clearAppenders();
|
||||
const logger = log4js.getBufferedLogger('tests');
|
||||
|
||||
t.test('should take a category and return a logger', (assert) => {
|
||||
assert.equal(logger.target.category, 'tests');
|
||||
assert.type(logger.flush, 'function');
|
||||
assert.type(logger.trace, 'function');
|
||||
assert.type(logger.debug, 'function');
|
||||
assert.type(logger.info, 'function');
|
||||
assert.type(logger.warn, 'function');
|
||||
assert.type(logger.error, 'function');
|
||||
assert.type(logger.fatal, 'function');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('cache events', (assert) => {
|
||||
const events = [];
|
||||
logger.target.setLevel('TRACE');
|
||||
logger.target.addListener('log', (logEvent) => {
|
||||
events.push(logEvent);
|
||||
});
|
||||
logger.debug('Debug event');
|
||||
logger.trace('Trace event 1');
|
||||
logger.trace('Trace event 2');
|
||||
logger.warn('Warning event');
|
||||
logger.error('Aargh!', new Error('Pants are on fire!'));
|
||||
logger.error(
|
||||
'Simulated CouchDB problem',
|
||||
{ err: 127, cause: 'incendiary underwear' }
|
||||
);
|
||||
|
||||
assert.equal(events.length, 0, 'should not emit log events if .flush() is not called.');
|
||||
logger.flush();
|
||||
assert.equal(events.length, 6, 'should emit log events when .flush() is called.');
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
batch.test('getLogger', (t) => {
|
||||
const log4js = require('../../lib/log4js');
|
||||
log4js.clearAppenders();
|
||||
log4js.configure({
|
||||
appenders: { recorder: { type: 'recording' } },
|
||||
categories: { default: { appenders: ['recorder'], level: 'DEBUG' } }
|
||||
});
|
||||
const logger = log4js.getLogger('tests');
|
||||
logger.setLevel('DEBUG');
|
||||
|
||||
t.test('should take a category and return a logger', (assert) => {
|
||||
assert.equal(logger.category, 'tests');
|
||||
@ -91,10 +25,8 @@ test('log4js', (batch) => {
|
||||
});
|
||||
|
||||
t.test('log events', (assert) => {
|
||||
const events = [];
|
||||
logger.addListener('log', (logEvent) => {
|
||||
events.push(logEvent);
|
||||
});
|
||||
recording.reset();
|
||||
|
||||
logger.debug('Debug event');
|
||||
logger.trace('Trace event 1');
|
||||
logger.trace('Trace event 2');
|
||||
@ -102,6 +34,8 @@ test('log4js', (batch) => {
|
||||
logger.error('Aargh!', new Error('Pants are on fire!'));
|
||||
logger.error('Simulated CouchDB problem', { err: 127, cause: 'incendiary underwear' });
|
||||
|
||||
const events = recording.replay();
|
||||
|
||||
assert.equal(events[0].level.toString(), 'DEBUG');
|
||||
assert.equal(events[0].data[0], 'Debug event');
|
||||
assert.type(events[0].startTime, 'Date');
|
||||
@ -128,15 +62,16 @@ test('log4js', (batch) => {
|
||||
requires: {
|
||||
'./appenders/file': {
|
||||
name: 'file',
|
||||
appender: function () {
|
||||
},
|
||||
configure: function () {
|
||||
return function () {
|
||||
function thing() {
|
||||
return null;
|
||||
}
|
||||
|
||||
thing.shutdown = function (cb) {
|
||||
events.appenderShutdownCalled = true;
|
||||
cb();
|
||||
};
|
||||
},
|
||||
shutdown: function (cb) {
|
||||
events.appenderShutdownCalled = true;
|
||||
cb();
|
||||
return thing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,108 +79,24 @@ test('log4js', (batch) => {
|
||||
);
|
||||
|
||||
const config = {
|
||||
appenders: [
|
||||
{
|
||||
appenders: {
|
||||
file: {
|
||||
type: 'file',
|
||||
filename: 'cheesy-wotsits.log',
|
||||
maxLogSize: 1024,
|
||||
backups: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
categories: { default: { appenders: ['file'], level: 'DEBUG' } }
|
||||
};
|
||||
|
||||
log4js.configure(config);
|
||||
log4js.shutdown(() => {
|
||||
// Re-enable log writing so other tests that use logger are not
|
||||
// affected.
|
||||
require('../../lib/logger').enableAllLogWrites();
|
||||
t.ok(events.appenderShutdownCalled, 'should invoke appender shutdowns');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
// 'invalid configuration': {
|
||||
// 'should throw an exception': function () {
|
||||
// assert.throws(() => {
|
||||
// // todo: here is weird, it's not ideal test
|
||||
// require('../../lib/log4js').configure({ type: 'invalid' });
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
|
||||
batch.test('configuration when passed as object', (t) => {
|
||||
let appenderConfig;
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'./appenders/file': {
|
||||
name: 'file',
|
||||
appender: function () {
|
||||
},
|
||||
configure: function (configuration) {
|
||||
appenderConfig = configuration;
|
||||
return function () {
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = {
|
||||
appenders: [
|
||||
{
|
||||
type: 'file',
|
||||
filename: 'cheesy-wotsits.log',
|
||||
maxLogSize: 1024,
|
||||
backups: 3
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
log4js.configure(config);
|
||||
t.equal(appenderConfig.filename, 'cheesy-wotsits.log', 'should be passed to appender config');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('configuration that causes an error', (t) => {
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'./appenders/file': {
|
||||
name: 'file',
|
||||
appender: function () {
|
||||
},
|
||||
configure: function () {
|
||||
throw new Error('oh noes');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = {
|
||||
appenders: [
|
||||
{
|
||||
type: 'file',
|
||||
filename: 'cheesy-wotsits.log',
|
||||
maxLogSize: 1024,
|
||||
backups: 3
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
try {
|
||||
log4js.configure(config);
|
||||
} catch (e) {
|
||||
t.ok(e.message.includes('log4js configuration problem for'));
|
||||
t.end();
|
||||
}
|
||||
});
|
||||
|
||||
batch.test('configuration when passed as filename', (t) => {
|
||||
let appenderConfig;
|
||||
let configFilename;
|
||||
@ -261,12 +112,13 @@ test('log4js', (batch) => {
|
||||
readFileSync: function (filename) {
|
||||
configFilename = filename;
|
||||
return JSON.stringify({
|
||||
appenders: [
|
||||
{
|
||||
appenders: {
|
||||
file: {
|
||||
type: 'file',
|
||||
filename: 'whatever.log'
|
||||
}
|
||||
]
|
||||
},
|
||||
categories: { default: { appenders: ['file'], level: 'DEBUG' } }
|
||||
});
|
||||
},
|
||||
readdirSync: function () {
|
||||
@ -274,9 +126,6 @@ test('log4js', (batch) => {
|
||||
}
|
||||
},
|
||||
'./appenders/file': {
|
||||
name: 'file',
|
||||
appender: function () {
|
||||
},
|
||||
configure: function (configuration) {
|
||||
appenderConfig = configuration;
|
||||
return function () {
|
||||
@ -295,15 +144,11 @@ test('log4js', (batch) => {
|
||||
|
||||
batch.test('with no appenders defined', (t) => {
|
||||
const fakeStdoutAppender = {
|
||||
name: 'stdout',
|
||||
appender: function () {
|
||||
configure: function () {
|
||||
return function (evt) {
|
||||
t.equal(evt.data[0], 'This is a test', 'should default to the stdout appender');
|
||||
t.end();
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeStdoutAppender.appender();
|
||||
}
|
||||
};
|
||||
|
||||
@ -321,288 +166,18 @@ test('log4js', (batch) => {
|
||||
// assert is back at the top, in the fake stdout appender
|
||||
});
|
||||
|
||||
batch.test('addAppender', (t) => {
|
||||
const log4js = require('../../lib/log4js');
|
||||
log4js.clearAppenders();
|
||||
|
||||
t.test('without a category', (assert) => {
|
||||
let appenderEvent;
|
||||
|
||||
const appender = function (evt) {
|
||||
appenderEvent = evt;
|
||||
};
|
||||
|
||||
const logger = log4js.getLogger('tests');
|
||||
|
||||
log4js.addAppender(appender);
|
||||
logger.debug('This is a test');
|
||||
|
||||
assert.equal(
|
||||
appenderEvent.data[0],
|
||||
'This is a test',
|
||||
'should register the function as a listener for all loggers'
|
||||
);
|
||||
assert.equal(appenderEvent.categoryName, 'tests');
|
||||
assert.equal(appenderEvent.level.toString(), 'DEBUG');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('if an appender for a category is defined', (assert) => {
|
||||
let otherEvent;
|
||||
let appenderEvent;
|
||||
|
||||
log4js.addAppender((evt) => {
|
||||
appenderEvent = evt;
|
||||
});
|
||||
log4js.addAppender((evt) => {
|
||||
otherEvent = evt;
|
||||
}, 'cheese');
|
||||
|
||||
const cheeseLogger = log4js.getLogger('cheese');
|
||||
cheeseLogger.debug('This is a test');
|
||||
|
||||
assert.same(appenderEvent, otherEvent, 'should register for that category');
|
||||
assert.equal(otherEvent.data[0], 'This is a test');
|
||||
assert.equal(otherEvent.categoryName, 'cheese');
|
||||
|
||||
otherEvent = undefined;
|
||||
appenderEvent = undefined;
|
||||
log4js.getLogger('pants').debug('this should not be propagated to otherEvent');
|
||||
assert.notOk(otherEvent);
|
||||
assert.equal(appenderEvent.data[0], 'this should not be propagated to otherEvent');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('with a category', (assert) => {
|
||||
let appenderEvent;
|
||||
|
||||
const appender = function (evt) {
|
||||
appenderEvent = evt;
|
||||
};
|
||||
|
||||
const logger = log4js.getLogger('tests');
|
||||
|
||||
log4js.addAppender(appender, 'tests');
|
||||
logger.debug('this is a category test');
|
||||
assert.equal(
|
||||
appenderEvent.data[0],
|
||||
'this is a category test',
|
||||
'should only register the function as a listener for that category'
|
||||
);
|
||||
|
||||
appenderEvent = undefined;
|
||||
log4js.getLogger('some other category').debug('Cheese');
|
||||
assert.notOk(appenderEvent);
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('with multiple categories', (assert) => {
|
||||
let appenderEvent;
|
||||
|
||||
const appender = function (evt) {
|
||||
appenderEvent = evt;
|
||||
};
|
||||
|
||||
const logger = log4js.getLogger('tests');
|
||||
|
||||
log4js.addAppender(appender, 'tests', 'biscuits');
|
||||
|
||||
logger.debug('this is a test');
|
||||
assert.equal(
|
||||
appenderEvent.data[0],
|
||||
'this is a test',
|
||||
'should register the function as a listener for all the categories'
|
||||
);
|
||||
|
||||
appenderEvent = undefined;
|
||||
const otherLogger = log4js.getLogger('biscuits');
|
||||
otherLogger.debug('mmm... garibaldis');
|
||||
assert.equal(appenderEvent.data[0], 'mmm... garibaldis');
|
||||
|
||||
appenderEvent = undefined;
|
||||
|
||||
log4js.getLogger('something else').debug('pants');
|
||||
assert.notOk(appenderEvent);
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('should register the function when the list of categories is an array', (assert) => {
|
||||
let appenderEvent;
|
||||
|
||||
const appender = function (evt) {
|
||||
appenderEvent = evt;
|
||||
};
|
||||
|
||||
log4js.addAppender(appender, ['tests', 'pants']);
|
||||
|
||||
log4js.getLogger('tests').debug('this is a test');
|
||||
assert.equal(appenderEvent.data[0], 'this is a test');
|
||||
|
||||
appenderEvent = undefined;
|
||||
|
||||
log4js.getLogger('pants').debug('big pants');
|
||||
assert.equal(appenderEvent.data[0], 'big pants');
|
||||
|
||||
appenderEvent = undefined;
|
||||
|
||||
log4js.getLogger('something else').debug('pants');
|
||||
assert.notOk(appenderEvent);
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('default setup', (t) => {
|
||||
const appenderEvents = [];
|
||||
|
||||
const fakeStdout = {
|
||||
name: 'stdout',
|
||||
appender: function () {
|
||||
return function (evt) {
|
||||
appenderEvents.push(evt);
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeStdout.appender();
|
||||
}
|
||||
};
|
||||
|
||||
const globalConsole = {
|
||||
log: function () {
|
||||
}
|
||||
};
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'./appenders/stdout': fakeStdout
|
||||
},
|
||||
globals: {
|
||||
console: globalConsole
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const logger = log4js.getLogger('a-test');
|
||||
|
||||
logger.debug('this is a test');
|
||||
globalConsole.log('this should not be logged');
|
||||
|
||||
t.equal(appenderEvents[0].data[0], 'this is a test', 'should configure a stdout appender');
|
||||
t.equal(appenderEvents.length, 1, 'should not replace console.log with log4js version');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('console', (t) => {
|
||||
const setup = setupConsoleTest();
|
||||
|
||||
t.test('when replaceConsole called', (assert) => {
|
||||
setup.log4js.replaceConsole();
|
||||
|
||||
setup.fakeConsole.log('Some debug message someone put in a module');
|
||||
setup.fakeConsole.debug('Some debug');
|
||||
setup.fakeConsole.error('An error');
|
||||
setup.fakeConsole.info('some info');
|
||||
setup.fakeConsole.warn('a warning');
|
||||
|
||||
setup.fakeConsole.log('cheese (%s) and biscuits (%s)', 'gouda', 'garibaldis');
|
||||
setup.fakeConsole.log({ lumpy: 'tapioca' });
|
||||
setup.fakeConsole.log('count %d', 123);
|
||||
setup.fakeConsole.log('stringify %j', { lumpy: 'tapioca' });
|
||||
|
||||
const logEvents = setup.logEvents;
|
||||
assert.equal(logEvents.length, 9);
|
||||
assert.equal(logEvents[0].data[0], 'Some debug message someone put in a module');
|
||||
assert.equal(logEvents[0].level.toString(), 'INFO');
|
||||
assert.equal(logEvents[1].data[0], 'Some debug');
|
||||
assert.equal(logEvents[1].level.toString(), 'DEBUG');
|
||||
assert.equal(logEvents[2].data[0], 'An error');
|
||||
assert.equal(logEvents[2].level.toString(), 'ERROR');
|
||||
assert.equal(logEvents[3].data[0], 'some info');
|
||||
assert.equal(logEvents[3].level.toString(), 'INFO');
|
||||
assert.equal(logEvents[4].data[0], 'a warning');
|
||||
assert.equal(logEvents[4].level.toString(), 'WARN');
|
||||
assert.equal(logEvents[5].data[0], 'cheese (%s) and biscuits (%s)');
|
||||
assert.equal(logEvents[5].data[1], 'gouda');
|
||||
assert.equal(logEvents[5].data[2], 'garibaldis');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('when turned off', (assert) => {
|
||||
setup.log4js.restoreConsole();
|
||||
try {
|
||||
setup.fakeConsole.log('This should cause the error described in the setup');
|
||||
} catch (e) {
|
||||
assert.type(e, 'Error', 'should call the original console methods');
|
||||
assert.equal(e.message, 'this should not be called.');
|
||||
assert.end();
|
||||
}
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('console configuration', (t) => {
|
||||
const setup = setupConsoleTest();
|
||||
|
||||
t.test('when disabled', (assert) => {
|
||||
setup.log4js.replaceConsole();
|
||||
setup.log4js.configure({ replaceConsole: false });
|
||||
try {
|
||||
setup.fakeConsole.log('This should cause the error described in the setup');
|
||||
} catch (e) {
|
||||
assert.type(e, 'Error');
|
||||
assert.equal(e.message, 'this should not be called.');
|
||||
assert.end();
|
||||
}
|
||||
});
|
||||
|
||||
t.test('when enabled', (assert) => {
|
||||
setup.log4js.restoreConsole();
|
||||
setup.log4js.configure({ replaceConsole: true });
|
||||
// log4js.configure clears all appenders
|
||||
setup.log4js.addAppender((evt) => {
|
||||
setup.logEvents.push(evt);
|
||||
});
|
||||
|
||||
setup.fakeConsole.debug('Some debug');
|
||||
|
||||
const logEvents = setup.logEvents;
|
||||
assert.equal(logEvents.length, 1);
|
||||
assert.equal(logEvents[0].level.toString(), 'DEBUG');
|
||||
assert.equal(logEvents[0].data[0], 'Some debug');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('configuration persistence', (t) => {
|
||||
let logEvent;
|
||||
const firstLog4js = require('../../lib/log4js');
|
||||
|
||||
firstLog4js.clearAppenders();
|
||||
firstLog4js.addAppender((evt) => {
|
||||
logEvent = evt;
|
||||
firstLog4js.configure({
|
||||
appenders: { recorder: { type: 'recording' } },
|
||||
categories: { default: { appenders: ['recorder'], level: 'DEBUG' } }
|
||||
});
|
||||
recording.reset();
|
||||
|
||||
const secondLog4js = require('../../lib/log4js');
|
||||
secondLog4js.getLogger().info('This should go to the appender defined in firstLog4js');
|
||||
|
||||
t.equal(logEvent.data[0], 'This should go to the appender defined in firstLog4js');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('getDefaultLogger', (t) => {
|
||||
const logger = require('../../lib/log4js').getDefaultLogger();
|
||||
|
||||
t.test('should return a logger', (assert) => {
|
||||
assert.ok(logger.info);
|
||||
assert.ok(logger.debug);
|
||||
assert.ok(logger.error);
|
||||
assert.end();
|
||||
});
|
||||
t.equal(recording.replay()[0].data[0], 'This should go to the appender defined in firstLog4js');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const path = require('path');
|
||||
const sandbox = require('sandboxed-module');
|
||||
|
||||
test('Reload configuration shutdown hook', (t) => {
|
||||
let timerId;
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
globals: {
|
||||
clearInterval: function (id) {
|
||||
timerId = id;
|
||||
},
|
||||
setInterval: function () {
|
||||
return '1234';
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure(
|
||||
path.join(__dirname, 'test-config.json'),
|
||||
{ reloadSecs: 30 }
|
||||
);
|
||||
|
||||
t.plan(1);
|
||||
log4js.shutdown(() => {
|
||||
t.equal(timerId, '1234', 'Shutdown should clear the reload timer');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
@ -1,350 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
|
||||
function setupConsoleTest() {
|
||||
const fakeConsole = {};
|
||||
const logEvents = [];
|
||||
|
||||
['trace', 'debug', 'log', 'info', 'warn', 'error'].forEach((fn) => {
|
||||
fakeConsole[fn] = function () {
|
||||
throw new Error('this should not be called.');
|
||||
};
|
||||
});
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
globals: {
|
||||
console: fakeConsole
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.clearAppenders();
|
||||
log4js.addAppender((evt) => {
|
||||
logEvents.push(evt);
|
||||
});
|
||||
|
||||
return { log4js: log4js, logEvents: logEvents, fakeConsole: fakeConsole };
|
||||
}
|
||||
|
||||
test('reload configuration', (batch) => {
|
||||
batch.test('with config file changing', (t) => {
|
||||
const pathsChecked = [];
|
||||
const logEvents = [];
|
||||
const modulePath = 'path/to/log4js.json';
|
||||
|
||||
const fakeFS = {
|
||||
lastMtime: Date.now(),
|
||||
config: {
|
||||
appenders: [
|
||||
{ type: 'console', layout: { type: 'messagePassThrough' } }
|
||||
],
|
||||
levels: { 'a-test': 'INFO' }
|
||||
},
|
||||
readFileSync: function (file, encoding) {
|
||||
t.equal(file, modulePath);
|
||||
t.equal(encoding, 'utf8');
|
||||
return JSON.stringify(fakeFS.config);
|
||||
},
|
||||
statSync: function (path) {
|
||||
pathsChecked.push(path);
|
||||
if (path === modulePath) {
|
||||
fakeFS.lastMtime += 1;
|
||||
return { mtime: new Date(fakeFS.lastMtime) };
|
||||
}
|
||||
throw new Error('no such file');
|
||||
}
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
name: 'console',
|
||||
appender: function () {
|
||||
return function (evt) {
|
||||
logEvents.push(evt);
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeConsole.appender();
|
||||
}
|
||||
};
|
||||
|
||||
let setIntervalCallback;
|
||||
|
||||
const fakeSetInterval = function (cb) {
|
||||
setIntervalCallback = cb;
|
||||
};
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
fs: fakeFS,
|
||||
'./appenders/console': fakeConsole
|
||||
},
|
||||
globals: {
|
||||
console: fakeConsole,
|
||||
setInterval: fakeSetInterval,
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure('path/to/log4js.json', { reloadSecs: 30 });
|
||||
const logger = log4js.getLogger('a-test');
|
||||
logger.info('info1');
|
||||
logger.debug('debug2 - should be ignored');
|
||||
fakeFS.config.levels['a-test'] = 'DEBUG';
|
||||
setIntervalCallback();
|
||||
logger.info('info3');
|
||||
logger.debug('debug4');
|
||||
|
||||
t.test('should configure log4js from first log4js.json found', (assert) => {
|
||||
assert.equal(logEvents[0].data[0], 'info1');
|
||||
assert.equal(logEvents[1].data[0], 'info3');
|
||||
assert.equal(logEvents[2].data[0], 'debug4');
|
||||
assert.equal(logEvents.length, 3);
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('with config file staying the same', (t) => {
|
||||
const pathsChecked = [];
|
||||
let fileRead = 0;
|
||||
const logEvents = [];
|
||||
const modulePath = require('path').normalize(`${__dirname}/../../lib/log4js.json`);
|
||||
const mtime = new Date();
|
||||
|
||||
const fakeFS = {
|
||||
config: {
|
||||
appenders: [
|
||||
{ type: 'console', layout: { type: 'messagePassThrough' } }
|
||||
],
|
||||
levels: { 'a-test': 'INFO' }
|
||||
},
|
||||
readFileSync: function (file, encoding) {
|
||||
fileRead += 1;
|
||||
t.type(file, 'string');
|
||||
t.equal(file, modulePath);
|
||||
t.equal(encoding, 'utf8');
|
||||
return JSON.stringify(fakeFS.config);
|
||||
},
|
||||
statSync: function (path) {
|
||||
pathsChecked.push(path);
|
||||
if (path === modulePath) {
|
||||
return { mtime: mtime };
|
||||
}
|
||||
throw new Error('no such file');
|
||||
}
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
name: 'console',
|
||||
appender: function () {
|
||||
return function (evt) {
|
||||
logEvents.push(evt);
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeConsole.appender();
|
||||
}
|
||||
};
|
||||
|
||||
let setIntervalCallback;
|
||||
|
||||
const fakeSetInterval = function (cb) {
|
||||
setIntervalCallback = cb;
|
||||
};
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
fs: fakeFS,
|
||||
'./appenders/console': fakeConsole
|
||||
},
|
||||
globals: {
|
||||
console: fakeConsole,
|
||||
setInterval: fakeSetInterval,
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure(modulePath, { reloadSecs: 3 });
|
||||
const logger = log4js.getLogger('a-test');
|
||||
logger.info('info1');
|
||||
logger.debug('debug2 - should be ignored');
|
||||
setIntervalCallback();
|
||||
logger.info('info3');
|
||||
logger.debug('debug4');
|
||||
|
||||
t.equal(fileRead, 1, 'should only read the configuration file once');
|
||||
t.test('should configure log4js from first log4js.json found', (assert) => {
|
||||
assert.equal(logEvents.length, 2);
|
||||
assert.equal(logEvents[0].data[0], 'info1');
|
||||
assert.equal(logEvents[1].data[0], 'info3');
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when config file is removed', (t) => {
|
||||
let fileRead = 0;
|
||||
const logEvents = [];
|
||||
const modulePath = require('path').normalize(`${__dirname}/../../lib/log4js.json`);
|
||||
|
||||
const fakeFS = {
|
||||
config: {
|
||||
appenders: [
|
||||
{ type: 'console', layout: { type: 'messagePassThrough' } }
|
||||
],
|
||||
levels: { 'a-test': 'INFO' }
|
||||
},
|
||||
readFileSync: function (file, encoding) {
|
||||
fileRead += 1;
|
||||
t.type(file, 'string');
|
||||
t.equal(file, modulePath);
|
||||
t.equal(encoding, 'utf8');
|
||||
return JSON.stringify(fakeFS.config);
|
||||
},
|
||||
statSync: function () {
|
||||
this.statSync = function () {
|
||||
throw new Error('no such file');
|
||||
};
|
||||
return { mtime: new Date() };
|
||||
}
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
name: 'console',
|
||||
appender: function () {
|
||||
return function (evt) {
|
||||
logEvents.push(evt);
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeConsole.appender();
|
||||
}
|
||||
};
|
||||
|
||||
let setIntervalCallback;
|
||||
|
||||
const fakeSetInterval = function (cb) {
|
||||
setIntervalCallback = cb;
|
||||
};
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
fs: fakeFS,
|
||||
'./appenders/console': fakeConsole
|
||||
},
|
||||
globals: {
|
||||
console: fakeConsole,
|
||||
setInterval: fakeSetInterval,
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure(modulePath, { reloadSecs: 3 });
|
||||
const logger = log4js.getLogger('a-test');
|
||||
logger.info('info1');
|
||||
logger.debug('debug2 - should be ignored');
|
||||
setIntervalCallback();
|
||||
logger.info('info3');
|
||||
logger.debug('debug4');
|
||||
|
||||
t.equal(fileRead, 1, 'should only read the configuration file once');
|
||||
t.test('should not clear configuration when config file not found', (assert) => {
|
||||
assert.equal(logEvents.length, 3);
|
||||
assert.equal(logEvents[0].data[0], 'info1');
|
||||
assert.equal(logEvents[1].level.toString(), 'WARN');
|
||||
assert.include(logEvents[1].data[0], 'Failed to load configuration file');
|
||||
assert.equal(logEvents[2].data[0], 'info3');
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when passed an object', (t) => {
|
||||
const setup = setupConsoleTest();
|
||||
setup.log4js.configure({}, { reloadSecs: 30 });
|
||||
const events = setup.logEvents;
|
||||
|
||||
t.test('should log a warning', (assert) => {
|
||||
assert.equal(events[0].level.toString(), 'WARN');
|
||||
assert.equal(
|
||||
events[0].data[0],
|
||||
'Ignoring configuration reload parameter for "object" configuration.'
|
||||
);
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when called twice with reload options', (t) => {
|
||||
const modulePath = require('path').normalize(`${__dirname}/../../lib/log4js.json`);
|
||||
|
||||
const fakeFS = {
|
||||
readFileSync: function () {
|
||||
return JSON.stringify({});
|
||||
},
|
||||
statSync: function () {
|
||||
return { mtime: new Date() };
|
||||
}
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
name: 'console',
|
||||
appender: function () {
|
||||
return function () {
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeConsole.appender();
|
||||
}
|
||||
};
|
||||
|
||||
let setIntervalCallback; // eslint-disable-line
|
||||
let intervalCleared = false;
|
||||
let clearedId;
|
||||
|
||||
const fakeSetInterval = function (cb) {
|
||||
setIntervalCallback = cb;
|
||||
return 1234;
|
||||
};
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
fs: fakeFS,
|
||||
'./appenders/console': fakeConsole
|
||||
},
|
||||
globals: {
|
||||
console: fakeConsole,
|
||||
setInterval: fakeSetInterval,
|
||||
clearInterval: function (interval) {
|
||||
intervalCleared = true;
|
||||
clearedId = interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure(modulePath, { reloadSecs: 3 });
|
||||
log4js.configure(modulePath, { reloadSecs: 15 });
|
||||
|
||||
t.test('should clear the previous interval', (assert) => {
|
||||
assert.ok(intervalCleared);
|
||||
assert.equal(clearedId, 1234);
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.end();
|
||||
});
|
||||
@ -20,7 +20,7 @@ test('stdout appender', (t) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
).appender(layouts.messagePassThroughLayout);
|
||||
).configure({ type: 'stdout', layout: { type: 'messagePassThrough' } }, layouts);
|
||||
|
||||
appender({ data: ['cheese'] });
|
||||
t.plan(2);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user