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:
Gareth Jones 2017-02-05 10:05:49 +11:00
parent db90ea6ecc
commit 5283782ba0
12 changed files with 582 additions and 1420 deletions

View 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
};

View File

@ -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
View 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;

View File

@ -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
];

View File

@ -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);

View File

@ -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;

View File

@ -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);

View 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();
});

View File

@ -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();
});

View File

@ -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();
});
});

View File

@ -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();
});

View File

@ -20,7 +20,7 @@ test('stdout appender', (t) => {
}
}
}
).appender(layouts.messagePassThroughLayout);
).configure({ type: 'stdout', layout: { type: 'messagePassThrough' } }, layouts);
appender({ data: ['cheese'] });
t.plan(2);