fix(tests): tests failing - config listeners not working in sandbox

This commit is contained in:
Gareth Jones 2018-01-17 08:24:50 +11:00
parent da703cdebb
commit 8084e80027
17 changed files with 1025 additions and 942 deletions

13
examples/layouts.js Normal file
View File

@ -0,0 +1,13 @@
const log4js = require('../lib/log4js');
log4js.configure({
appenders: {
out: { type: 'stdout', layout: { type: 'messagePassThrough' } }
},
categories: {
default: { appenders: ['out'], level: 'info' }
}
});
const logger = log4js.getLogger('thing');
logger.info('This should not have a timestamp');

View File

@ -1,81 +1,80 @@
const CircularJSON = require('circular-json');
const levels = require('./levels');
module.exports = (levels) => {
/**
* @name LoggingEvent
* @namespace Log4js
*/
class LoggingEvent {
/**
* @name LoggingEvent
* @namespace Log4js
* Models a logging event.
* @constructor
* @param {String} categoryName name of category
* @param {Log4js.Level} level level of message
* @param {Array} data objects to log
* @author Seth Chisamore
*/
class LoggingEvent {
/**
* Models a logging event.
* @constructor
* @param {String} categoryName name of category
* @param {Log4js.Level} level level of message
* @param {Array} data objects to log
* @author Seth Chisamore
*/
constructor(categoryName, level, data, context) {
this.startTime = new Date();
this.categoryName = categoryName;
this.data = data;
this.level = level;
this.context = Object.assign({}, context);
this.pid = process.pid;
// if (cluster && cluster.isWorker) {
// this.cluster = {
// workerId: cluster.worker.id,
// worker: process.pid
// };
// }
}
constructor(categoryName, level, data, context) {
this.startTime = new Date();
this.categoryName = categoryName;
this.data = data;
this.level = level;
this.context = Object.assign({}, context);
this.pid = process.pid;
// if (cluster && cluster.isWorker) {
// this.cluster = {
// workerId: cluster.worker.id,
// worker: process.pid
// };
// }
}
serialise() {
// JSON.stringify(new Error('test')) returns {}, which is not really useful for us.
// The following allows us to serialize errors correctly.
// Validate that we really are in this case
try {
const logData = this.data.map((e) => {
if (e && e.stack && CircularJSON.stringify(e) === '{}') {
e = { message: e.message, stack: e.stack };
}
return e;
});
this.data = logData;
return CircularJSON.stringify(this);
} catch (e) {
return new LoggingEvent(
'log4js',
levels.ERROR,
['Unable to serialise log event due to :', e]
).serialise();
}
}
static deserialise(serialised) {
let event;
try {
event = CircularJSON.parse(serialised);
event.startTime = new Date(event.startTime);
event.level = levels.getLevel(event.level.levelStr);
event.data = event.data.map((e) => {
if (e && e.stack) {
const fakeError = new Error(e.message);
fakeError.stack = e.stack;
e = fakeError;
}
return e;
});
} catch (e) {
event = new LoggingEvent(
'log4js',
levels.ERROR,
['Unable to parse log:', serialised, 'because: ', e]
);
}
return event;
serialise() {
// JSON.stringify(new Error('test')) returns {}, which is not really useful for us.
// The following allows us to serialize errors correctly.
// Validate that we really are in this case
try {
const logData = this.data.map((e) => {
if (e && e.stack && CircularJSON.stringify(e) === '{}') {
e = { message: e.message, stack: e.stack };
}
return e;
});
this.data = logData;
return CircularJSON.stringify(this);
} catch (e) {
return new LoggingEvent(
'log4js',
levels.ERROR,
['Unable to serialise log event due to :', e]
).serialise();
}
}
return LoggingEvent;
};
static deserialise(serialised) {
let event;
try {
event = CircularJSON.parse(serialised);
event.startTime = new Date(event.startTime);
event.level = levels.getLevel(event.level.levelStr);
event.data = event.data.map((e) => {
if (e && e.stack) {
const fakeError = new Error(e.message);
fakeError.stack = e.stack;
e = fakeError;
}
return e;
});
} catch (e) {
event = new LoggingEvent(
'log4js',
levels.ERROR,
['Unable to parse log:', serialised, 'because: ', e]
);
}
return event;
}
}
module.exports = LoggingEvent;

View File

@ -1,9 +1,13 @@
'use strict';
const debug = require('debug')('log4js:categoryFilter');
function categoryFilter(excludes, appender) {
if (typeof excludes === 'string') excludes = [excludes];
return (logEvent) => {
debug(`Checking ${logEvent.categoryName} against ${excludes}`);
if (excludes.indexOf(logEvent.categoryName) === -1) {
debug('Not excluded, sending to appender');
appender(logEvent);
}
};

97
lib/appenders/index.js Normal file
View File

@ -0,0 +1,97 @@
const path = require('path');
const debug = require('debug')('log4js:appenders');
const configuration = require('../configuration');
const clustering = require('../clustering');
const levels = require('../levels');
const layouts = require('../layouts');
const appenders = new Map();
const tryLoading = (modulePath, config) => {
debug('Loading module from ', modulePath);
try {
return require(modulePath); //eslint-disable-line
} catch (e) {
// if the module was found, and we still got an error, then raise it
configuration.throwExceptionIf(
config,
e.code !== 'MODULE_NOT_FOUND',
`appender "${modulePath}" could not be loaded (error was: ${e})`
);
return undefined;
}
};
const loadAppenderModule = (type, config) => tryLoading(`./${type}`, config) ||
tryLoading(type, config) ||
tryLoading(path.join(path.dirname(require.main.filename), type), config) ||
tryLoading(path.join(process.cwd(), type), config);
const createAppender = (name, config) => {
const appenderConfig = config.appenders[name];
const appenderModule = loadAppenderModule(appenderConfig.type, config);
configuration.throwExceptionIf(
config,
configuration.not(appenderModule),
`appender "${name}" is not valid (type "${appenderConfig.type}" could not be found)`
);
if (appenderModule.appender) {
debug(`DEPRECATION: Appender ${appenderConfig.type} exports an appender function.`);
}
if (appenderModule.shutdown) {
debug(`DEPRECATION: Appender ${appenderConfig.type} exports a shutdown function.`);
}
debug(`${name}: clustering.isMaster ? ${clustering.isMaster()}`);
debug(`${name}: appenderModule is ${require('util').inspect(appenderModule)}`); // eslint-disable-line
return clustering.onlyOnMaster(() => {
debug(`calling appenderModule.configure for ${name} / ${appenderConfig.type}`);
return appenderModule.configure(
appenderConfig,
layouts,
appender => appenders.get(appender),
levels
);
}, () => {});
};
const setup = (config) => {
appenders.clear();
Object.keys(config.appenders).forEach((name) => {
debug(`Creating appender ${name}`);
appenders.set(name, createAppender(name, config));
});
};
// setup({
// appenders: {
// stdout: { type: 'stdout' }
// }
// });
configuration.addListener((config) => {
configuration.throwExceptionIf(
config,
configuration.not(configuration.anObject(config.appenders)),
'must have a property "appenders" of type object.'
);
const appenderNames = Object.keys(config.appenders);
configuration.throwExceptionIf(
config,
configuration.not(appenderNames.length),
'must define at least one appender.'
);
appenderNames.forEach((name) => {
configuration.throwExceptionIf(
config,
configuration.not(config.appenders[name].type),
`appender "${name}" is not valid (must be an object with property "type")`
);
});
});
configuration.addListener(setup);
module.exports = appenders;

125
lib/categories.js Normal file
View File

@ -0,0 +1,125 @@
const configuration = require('./configuration');
const levels = require('./levels');
const appenders = require('./appenders');
const debug = require('debug')('log4js:categories');
const categories = new Map();
configuration.addListener((config) => {
configuration.throwExceptionIf(
config,
configuration.not(configuration.anObject(config.categories)),
'must have a property "categories" of type object.'
);
const categoryNames = Object.keys(config.categories);
configuration.throwExceptionIf(
config,
configuration.not(categoryNames.length),
'must define at least one category.'
);
categoryNames.forEach((name) => {
const category = config.categories[name];
configuration.throwExceptionIf(
config,
[
configuration.not(category.appenders),
configuration.not(category.level)
],
`category "${name}" is not valid (must be an object with properties "appenders" and "level")`
);
configuration.throwExceptionIf(
config,
configuration.not(Array.isArray(category.appenders)),
`category "${name}" is not valid (appenders must be an array of appender names)`
);
configuration.throwExceptionIf(
config,
configuration.not(category.appenders.length),
`category "${name}" is not valid (appenders must contain at least one appender name)`
);
category.appenders.forEach((appender) => {
configuration.throwExceptionIf(
config,
configuration.not(appenders.get(appender)),
`category "${name}" is not valid (appender "${appender}" is not defined)`
);
});
configuration.throwExceptionIf(
config,
configuration.not(levels.getLevel(category.level)),
`category "${name}" is not valid (level "${category.level}" not recognised;` +
` valid levels are ${levels.levels.join(', ')})`
);
});
configuration.throwExceptionIf(
config,
configuration.not(config.categories.default),
'must define a "default" category.'
);
});
const setup = (config) => {
categories.clear();
const categoryNames = Object.keys(config.categories);
categoryNames.forEach((name) => {
const category = config.categories[name];
const categoryAppenders = [];
category.appenders.forEach((appender) => {
categoryAppenders.push(appenders.get(appender));
debug(`Creating category ${name}`);
categories.set(
name,
{ appenders: categoryAppenders, level: levels.getLevel(category.level) }
);
});
});
};
// setup({
// categories: { default: { appenders: ['stdout'], level: 'OFF' } }
// });
configuration.addListener(setup);
const configForCategory = (category) => {
debug(`configForCategory: searching for config for ${category}`);
if (categories.has(category)) {
debug(`configForCategory: ${category} exists in config, returning it`);
return categories.get(category);
}
if (category.indexOf('.') > 0) {
debug(`configForCategory: ${category} has hierarchy, searching for parents`);
return configForCategory(category.substring(0, category.lastIndexOf('.')));
}
debug('configForCategory: returning config for default category');
return configForCategory('default');
};
const appendersForCategory = category => configForCategory(category).appenders;
const getLevelForCategory = category => configForCategory(category).level;
const setLevelForCategory = (category, level) => {
let categoryConfig = categories.get(category);
debug(`setLevelForCategory: found ${categoryConfig} for ${category}`);
if (!categoryConfig) {
const sourceCategoryConfig = configForCategory(category);
debug('setLevelForCategory: no config found for category, ' +
`found ${sourceCategoryConfig} for parents of ${category}`);
categoryConfig = { appenders: sourceCategoryConfig.appenders };
}
categoryConfig.level = level;
categories.set(category, categoryConfig);
};
module.exports = {
appendersForCategory,
getLevelForCategory,
setLevelForCategory
};

View File

@ -1,47 +1,60 @@
const debug = require('debug')('log4js:clustering');
const LoggingEvent = require('./LoggingEvent');
const configuration = require('./configuration');
const cluster = require('cluster');
let cluster;
try {
cluster = require('cluster'); // eslint-disable-line global-require
} catch (e) {
debug('Clustering support disabled because require(cluster) threw an error: ', e);
}
const listeners = [];
module.exports = (config) => {
const disabled = config.disableClustering || !cluster;
const pm2 = config.pm2;
const pm2InstanceVar = config.pm2InstanceVar || 'NODE_APP_INSTANCE';
const listeners = [];
let disabled = false;
let pm2 = false;
let pm2InstanceVar = 'NODE_APP_INSTANCE';
const isPM2Master = () => pm2 && process.env[pm2InstanceVar] === '0';
const isMaster = () => disabled || cluster.isMaster || isPM2Master();
const isWorker = () => !isMaster();
const sendToListeners = (logEvent) => {
listeners.forEach(l => l(logEvent));
};
// in a multi-process node environment, worker loggers will use
// process.send
const receiver = (worker, message) => {
// prior to node v6, the worker parameter was not passed (args were message, handle)
debug('cluster message received from worker ', worker, ': ', message);
if (worker.topic && worker.data) {
message = worker;
worker = undefined;
}
if (message && message.topic && message.topic === 'log4js:message') {
debug('received message: ', message.data);
const logEvent = LoggingEvent.deserialise(message.data);
sendToListeners(logEvent);
}
};
configuration.addListener((config) => {
// clear out the listeners, because configure has been called.
listeners.length = 0;
disabled = config.disableClustering;
pm2 = config.pm2;
pm2InstanceVar = config.pm2InstanceVar || 'NODE_APP_INSTANCE';
debug(`clustering disabled ? ${disabled}`);
debug(`cluster.isMaster ? ${cluster && cluster.isMaster}`);
debug(`cluster.isMaster ? ${cluster.isMaster}`);
debug(`pm2 enabled ? ${pm2}`);
debug(`pm2InstanceVar = ${pm2InstanceVar}`);
debug(`process.env[${pm2InstanceVar}] = ${process.env[pm2InstanceVar]}`);
const isPM2Master = () => pm2 && process.env[pm2InstanceVar] === '0';
const isMaster = () => disabled || cluster.isMaster || isPM2Master();
const isWorker = () => !isMaster();
// in a multi-process node environment, worker loggers will use
// process.send
const receiver = (worker, message) => {
// prior to node v6, the worker parameter was not passed (args were message, handle)
debug('cluster message received from worker ', worker, ': ', message);
if (worker.topic && worker.data) {
message = worker;
worker = undefined;
}
if (message && message.topic && message.topic === 'log4js:message') {
debug('received message: ', message.data);
const logEvent = LoggingEvent.deserialise(message.data);
listeners.forEach(l => l(logEvent));
}
};
// just in case configure is called after shutdown
pm2 && process.removeListener('message', receiver);
cluster.removeListener('message', receiver);
if (pm2) {
process.removeListener('message', receiver);
}
if (cluster.removeListener) {
cluster.removeListener('message', receiver);
}
if (config.disableClustering) {
debug('Not listening for cluster messages, because clustering disabled.');
} else if (isPM2Master()) {
@ -56,26 +69,29 @@ module.exports = (config) => {
} else {
debug('not listening for messages, because we are not a master process');
}
});
return {
onlyOnMaster: (fn) => {
if (isMaster()) {
fn();
module.exports = {
onlyOnMaster: (fn, notMaster) => (isMaster() ? fn() : notMaster),
onlyOnWorker: (fn, notWorker) => (isWorker() ? fn() : notWorker),
isMaster: isMaster,
isWorker: isWorker,
send: (msg) => {
if (isWorker()) {
if (pm2) {
process.send({ type: 'log4js:message', data: msg.serialise() });
} else {
msg.cluster = {
workerId: cluster.worker.id,
worker: process.pid
};
cluster.send({ type: 'log4js:message', data: msg.serialise() });
}
},
onlyOnWorker: (fn) => {
if (isWorker()) {
fn();
}
},
isMaster: isMaster,
isWorker: isWorker,
send: (msg) => {
},
onMessage: (listener) => {
listeners.push(listener);
} else {
sendToListeners(msg);
}
};
},
onMessage: (listener) => {
listeners.push(listener);
}
};

View File

@ -1,201 +1,49 @@
'use strict';
const util = require('util');
const path = require('path');
const levels = require('./levels');
const layouts = require('./layouts');
const clustering = require('./clustering');
const debug = require('debug')('log4js:configuration');
const validColours = [
'white', 'grey', 'black',
'blue', 'cyan', 'green',
'magenta', 'red', 'yellow'
];
const listeners = [];
function not(thing) {
return !thing;
}
const not = thing => !thing;
function anObject(thing) {
return thing && typeof thing === 'object' && !Array.isArray(thing);
}
const anObject = thing => thing && typeof thing === 'object' && !Array.isArray(thing);
function validIdentifier(thing) {
return /^[A-Za-z][A-Za-z0-9_]*$/g.test(thing);
}
const validIdentifier = thing => /^[A-Za-z][A-Za-z0-9_]*$/g.test(thing);
function anInteger(thing) {
return thing && typeof thing === 'number' && Number.isInteger(thing);
}
const anInteger = thing => thing && typeof thing === 'number' && Number.isInteger(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}`);
}
});
const addListener = (fn) => {
if (fn) {
listeners.push(fn);
}
};
tryLoading(modulePath) {
debug('Loading module from ', modulePath);
try {
return require(modulePath); //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;
const throwExceptionIf = (config, checks, message) => {
const tests = Array.isArray(checks) ? checks : [checks];
tests.forEach((test) => {
if (test) {
throw new Error(`Problem with log4js configuration: (${util.inspect(config, { depth: 5 })})` +
` - ${message}`);
}
}
});
};
loadAppenderModule(type) {
return this.tryLoading(`./appenders/${type}`) ||
this.tryLoading(type) ||
this.tryLoading(path.join(path.dirname(require.main.filename), type)) ||
this.tryLoading(path.join(process.cwd(), type));
}
const configure = (candidate) => {
debug('New configuration to be validated: ', candidate);
throwExceptionIf(candidate, not(anObject(candidate)), 'must be an object.');
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)`
);
if (appenderModule.appender) {
debug(`DEPRECATION: Appender ${config.type} exports an appender function.`);
}
if (appenderModule.shutdown) {
debug(`DEPRECATION: Appender ${config.type} exports a shutdown function.`);
}
debug('Calling configuration listeners');
listeners.forEach(listener => listener(candidate));
debug('Configuration finished.');
};
this.clustering.onlyOnMaster(() => appenderModule.configure(
config,
layouts,
this.configuredAppenders.get.bind(this.configuredAppenders),
this.configuredLevels
));
return () => {};
}
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")`
);
debug(`Creating appender ${name}`);
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(this.configuredLevels.getLevel(category.level)),
`category "${name}" is not valid (level "${category.level}" not recognised;` +
` valid levels are ${this.configuredLevels.levels.join(', ')})`
);
debug(`Creating category ${name}`);
this.configuredCategories.set(
name,
{ appenders: appenders, level: this.configuredLevels.getLevel(category.level) }
);
});
this.throwExceptionIf(not(categoryConfig.default), 'must define a "default" category.');
}
get levels() {
return this.configuredLevels;
}
set levels(levelConfig) {
// levels are optional
if (levelConfig) {
this.throwExceptionIf(not(anObject(levelConfig)), 'levels must be an object');
const newLevels = Object.keys(levelConfig);
newLevels.forEach((l) => {
this.throwExceptionIf(
not(validIdentifier(l)),
`level name "${l}" is not a valid identifier (must start with a letter, only contain A-Z,a-z,0-9,_)`
);
this.throwExceptionIf(not(anObject(levelConfig[l])), `level "${l}" must be an object`);
this.throwExceptionIf(not(levelConfig[l].value), `level "${l}" must have a 'value' property`);
this.throwExceptionIf(not(anInteger(levelConfig[l].value)), `level "${l}".value must have an integer value`);
this.throwExceptionIf(not(levelConfig[l].colour), `level "${l}" must have a 'colour' property`);
this.throwExceptionIf(
not(validColours.indexOf(levelConfig[l].colour) > -1),
`level "${l}".colour must be one of ${validColours.join(', ')}`
);
});
}
this.configuredLevels = levels(levelConfig);
}
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.levels = candidate.levels;
this.clustering = clustering(this.candidate);
this.appenders = candidate.appenders;
this.categories = candidate.categories;
}
}
module.exports = Configuration;
module.exports = {
configure,
addListener,
throwExceptionIf,
anObject,
anInteger,
validIdentifier,
not
};

View File

@ -2,6 +2,8 @@
'use strict';
const levels = require('./levels');
const DEFAULT_FORMAT = ':remote-addr - -' +
' ":method :url HTTP/:http-version"' +
' :status :content-length ":referrer"' +
@ -163,8 +165,7 @@ function createNoLogCondition(nolog) {
return regexp;
}
module.exports = function (levels) {
/**
/**
* Log requests with the given `options` or a `format` string.
*
* Options:
@ -192,80 +193,77 @@ module.exports = function (levels) {
* @param options
* @api public
*/
function getLogger(logger4js, options) {
/* eslint no-underscore-dangle:0 */
if (typeof options === 'object') {
options = options || {};
} else if (options) {
options = { format: options };
} else {
options = {};
}
const thisLogger = logger4js;
let level = levels.getLevel(options.level, levels.INFO);
const fmt = options.format || DEFAULT_FORMAT;
const nolog = options.nolog ? createNoLogCondition(options.nolog) : null;
return (req, res, next) => {
// mount safety
if (req._logging) return next();
// nologs
if (nolog && nolog.test(req.originalUrl)) return next();
if (thisLogger.isLevelEnabled(level) || options.level === 'auto') {
const start = new Date();
const writeHead = res.writeHead;
// flag as logging
req._logging = true;
// proxy for statusCode.
res.writeHead = (code, headers) => {
res.writeHead = writeHead;
res.writeHead(code, headers);
res.__statusCode = code;
res.__headers = headers || {};
// status code response level handling
if (options.level === 'auto') {
level = levels.INFO;
if (code >= 300) level = levels.WARN;
if (code >= 400) level = levels.ERROR;
} else {
level = levels.getLevel(options.level, levels.INFO);
}
};
// hook on end request to emit the log entry of the HTTP request.
res.on('finish', () => {
res.responseTime = new Date() - start;
// status code response level handling
if (res.statusCode && options.level === 'auto') {
level = levels.INFO;
if (res.statusCode >= 300) level = levels.WARN;
if (res.statusCode >= 400) level = levels.ERROR;
}
if (thisLogger.isLevelEnabled(level)) {
const combinedTokens = assembleTokens(req, res, options.tokens || []);
if (typeof fmt === 'function') {
const line = fmt(req, res, str => format(str, combinedTokens));
if (line) thisLogger.log(level, line);
} else {
thisLogger.log(level, format(fmt, combinedTokens));
}
}
});
}
// ensure next gets always called
return next();
};
module.exports = function getLogger(logger4js, options) {
/* eslint no-underscore-dangle:0 */
if (typeof options === 'object') {
options = options || {};
} else if (options) {
options = { format: options };
} else {
options = {};
}
return { connectLogger: getLogger };
const thisLogger = logger4js;
let level = levels.getLevel(options.level, levels.INFO);
const fmt = options.format || DEFAULT_FORMAT;
const nolog = options.nolog ? createNoLogCondition(options.nolog) : null;
return (req, res, next) => {
// mount safety
if (req._logging) return next();
// nologs
if (nolog && nolog.test(req.originalUrl)) return next();
if (thisLogger.isLevelEnabled(level) || options.level === 'auto') {
const start = new Date();
const writeHead = res.writeHead;
// flag as logging
req._logging = true;
// proxy for statusCode.
res.writeHead = (code, headers) => {
res.writeHead = writeHead;
res.writeHead(code, headers);
res.__statusCode = code;
res.__headers = headers || {};
// status code response level handling
if (options.level === 'auto') {
level = levels.INFO;
if (code >= 300) level = levels.WARN;
if (code >= 400) level = levels.ERROR;
} else {
level = levels.getLevel(options.level, levels.INFO);
}
};
// hook on end request to emit the log entry of the HTTP request.
res.on('finish', () => {
res.responseTime = new Date() - start;
// status code response level handling
if (res.statusCode && options.level === 'auto') {
level = levels.INFO;
if (res.statusCode >= 300) level = levels.WARN;
if (res.statusCode >= 400) level = levels.ERROR;
}
if (thisLogger.isLevelEnabled(level)) {
const combinedTokens = assembleTokens(req, res, options.tokens || []);
if (typeof fmt === 'function') {
const line = fmt(req, res, str => format(str, combinedTokens));
if (line) thisLogger.log(level, line);
} else {
thisLogger.log(level, format(fmt, combinedTokens));
}
}
});
}
// ensure next gets always called
return next();
};
};

View File

@ -1,60 +1,22 @@
'use strict';
module.exports = function (customLevels) {
/**
* @name Level
* @namespace Log4js
*/
class Level {
constructor(level, levelStr, colour) {
this.level = level;
this.levelStr = levelStr;
this.colour = colour;
}
const configuration = require('./configuration');
toString() {
return this.levelStr;
}
const validColours = [
'white', 'grey', 'black',
'blue', 'cyan', 'green',
'magenta', 'red', 'yellow'
];
isLessThanOrEqualTo(otherLevel) {
if (typeof otherLevel === 'string') {
otherLevel = getLevel(otherLevel);
}
return this.level <= otherLevel.level;
}
isGreaterThanOrEqualTo(otherLevel) {
if (typeof otherLevel === 'string') {
otherLevel = getLevel(otherLevel);
}
return this.level >= otherLevel.level;
}
isEqualTo(otherLevel) {
if (typeof otherLevel === 'string') {
otherLevel = getLevel(otherLevel);
}
return this.level === otherLevel.level;
}
class Level {
constructor(level, levelStr, colour) {
this.level = level;
this.levelStr = levelStr;
this.colour = colour;
}
const defaultLevels = {
ALL: new Level(Number.MIN_VALUE, 'ALL', 'grey'),
TRACE: new Level(5000, 'TRACE', 'blue'),
DEBUG: new Level(10000, 'DEBUG', 'cyan'),
INFO: new Level(20000, 'INFO', 'green'),
WARN: new Level(30000, 'WARN', 'yellow'),
ERROR: new Level(40000, 'ERROR', 'red'),
FATAL: new Level(50000, 'FATAL', 'magenta'),
MARK: new Level(9007199254740992, 'MARK', 'grey'), // 2^53
OFF: new Level(Number.MAX_VALUE, 'OFF', 'grey')
};
if (customLevels) {
const levels = Object.keys(customLevels);
levels.forEach((l) => {
defaultLevels[l.toUpperCase()] = new Level(customLevels[l].value, l.toUpperCase(), customLevels[l].colour);
});
toString() {
return this.levelStr;
}
/**
@ -63,7 +25,7 @@ module.exports = function (customLevels) {
* @param {Level} [defaultLevel] -- default Level, if no String representation
* @return {Level}
*/
function getLevel(sArg, defaultLevel) {
static getLevel(sArg, defaultLevel) {
if (!sArg) {
return defaultLevel;
}
@ -73,15 +35,109 @@ module.exports = function (customLevels) {
}
if (typeof sArg === 'string') {
return defaultLevels[sArg.toUpperCase()] || defaultLevel;
return Level[sArg.toUpperCase()] || defaultLevel;
}
return getLevel(sArg.toString());
return Level.getLevel(sArg.toString());
}
const orderedLevels = Object.keys(defaultLevels).sort((a, b) => b.level - a.level);
defaultLevels.getLevel = getLevel;
defaultLevels.levels = orderedLevels;
static addLevels(customLevels) {
if (customLevels) {
const levels = Object.keys(customLevels);
levels.forEach((l) => {
Level[l.toUpperCase()] = new Level(
customLevels[l].value,
l.toUpperCase(),
customLevels[l].colour
);
Level.levels.push(Level[l.toUpperCase()]);
});
Level.levels.sort((a, b) => a.level - b.level);
}
}
return defaultLevels;
};
isLessThanOrEqualTo(otherLevel) {
if (typeof otherLevel === 'string') {
otherLevel = Level.getLevel(otherLevel);
}
return this.level <= otherLevel.level;
}
isGreaterThanOrEqualTo(otherLevel) {
if (typeof otherLevel === 'string') {
otherLevel = Level.getLevel(otherLevel);
}
return this.level >= otherLevel.level;
}
isEqualTo(otherLevel) {
if (typeof otherLevel === 'string') {
otherLevel = Level.getLevel(otherLevel);
}
return this.level === otherLevel.level;
}
}
Level.levels = [];
Level.addLevels({
ALL: { value: Number.MIN_VALUE, colour: 'grey' },
TRACE: { value: 5000, colour: 'blue' },
DEBUG: { value: 10000, colour: 'cyan' },
INFO: { value: 20000, colour: 'green' },
WARN: { value: 30000, colour: 'yellow' },
ERROR: { value: 40000, colour: 'red' },
FATAL: { value: 50000, colour: 'magenta' },
MARK: { value: 9007199254740992, colour: 'grey' }, // 2^53
OFF: { value: Number.MAX_VALUE, colour: 'grey' }
});
configuration.addListener((config) => {
const levelConfig = config.levels;
if (levelConfig) {
configuration.throwExceptionIf(
config,
configuration.not(configuration.anObject(levelConfig)),
'levels must be an object'
);
const newLevels = Object.keys(levelConfig);
newLevels.forEach((l) => {
configuration.throwExceptionIf(
config,
configuration.not(configuration.validIdentifier(l)),
`level name "${l}" is not a valid identifier (must start with a letter, only contain A-Z,a-z,0-9,_)`
);
configuration.throwExceptionIf(
config,
configuration.not(configuration.anObject(levelConfig[l])),
`level "${l}" must be an object`
);
configuration.throwExceptionIf(
config,
configuration.not(levelConfig[l].value),
`level "${l}" must have a 'value' property`
);
configuration.throwExceptionIf(
config,
configuration.not(configuration.anInteger(levelConfig[l].value)),
`level "${l}".value must have an integer value`
);
configuration.throwExceptionIf(
config,
configuration.not(levelConfig[l].colour),
`level "${l}" must have a 'colour' property`
);
configuration.throwExceptionIf(
config,
configuration.not(validColours.indexOf(levelConfig[l].colour) > -1),
`level "${l}".colour must be one of ${validColours.join(', ')}`
);
});
}
});
configuration.addListener((config) => {
Level.addLevels(config.levels);
});
module.exports = Level;

View File

@ -15,88 +15,33 @@
*
* NOTE: the authors below are the original browser-based log4js authors
* don't try to contact them about bugs in this version :)
* @version 1.0
* @author Stephan Strittmatter - http://jroller.com/page/stritti
* @author Seth Chisamore - http://www.chisamore.com
* @since 2005-05-20
* @static
* Website: http://log4js.berlios.de
*/
const debug = require('debug')('log4js:main');
const fs = require('fs');
const CircularJSON = require('circular-json');
const Configuration = require('./configuration');
const connectModule = require('./connect-logger');
const logger = require('./logger');
const configuration = require('./configuration');
const layouts = require('./layouts');
const levels = require('./levels');
const appenders = require('./appenders');
const categories = require('./categories');
const Logger = require('./logger');
const clustering = require('./clustering');
const connectLogger = require('./connect-logger');
const defaultConfig = {
appenders: {
stdout: { type: 'stdout' }
},
categories: {
default: { appenders: ['stdout'], level: 'OFF' }
}
};
let Logger;
let LoggingEvent;
let config;
let connectLogger;
let clustering;
let enabled = false;
function configForCategory(category) {
debug(`configForCategory: searching for config for ${category}`);
if (config.categories.has(category)) {
debug(`configForCategory: ${category} exists in config, returning it`);
return config.categories.get(category);
}
if (category.indexOf('.') > 0) {
debug(`configForCategory: ${category} has hierarchy, searching for parents`);
return configForCategory(category.substring(0, category.lastIndexOf('.')));
}
debug('configForCategory: returning config for default category');
return configForCategory('default');
}
function appendersForCategory(category) {
return configForCategory(category).appenders;
}
function levelForCategory(category) {
return configForCategory(category).level;
}
function setLevelForCategory(category, level) {
let categoryConfig = config.categories.get(category);
debug(`setLevelForCategory: found ${categoryConfig} for ${category}`);
if (!categoryConfig) {
const sourceCategoryConfig = configForCategory(category);
debug('setLevelForCategory: no config found for category, ' +
`found ${sourceCategoryConfig} for parents of ${category}`);
categoryConfig = { appenders: sourceCategoryConfig.appenders };
}
categoryConfig.level = level;
config.categories.set(category, categoryConfig);
}
function sendLogEventToAppender(logEvent) {
if (!enabled) return;
debug('Received log event ', logEvent);
const appenders = appendersForCategory(logEvent.categoryName);
appenders.forEach((appender) => {
const categoryAppenders = categories.appendersForCategory(logEvent.categoryName);
categoryAppenders.forEach((appender) => {
appender(logEvent);
});
}
function workerDispatch(logEvent) {
debug(`sending message to master from worker ${process.pid}`);
process.send({ topic: 'log4js:message', data: logEvent.serialise() });
}
/**
* Get a logger instance.
* @static
@ -104,9 +49,7 @@ function workerDispatch(logEvent) {
* @return {Logger} instance of logger for the category
*/
function getLogger(category) {
const cat = category || 'default';
debug(`creating logger as ${isMaster() ? 'master' : 'worker'}`);
return new Logger((isMaster() ? sendLogEventToAppender : workerDispatch), cat);
return new Logger(category || 'default');
}
function loadConfigurationFile(filename) {
@ -124,13 +67,7 @@ function configure(configurationFileOrObject) {
configObject = loadConfigurationFile(configurationFileOrObject);
}
debug(`Configuration is ${configObject}`);
config = new Configuration(configObject);
clustering = config.clustering;
module.exports.levels = config.levels;
const loggerModule = logger(config.levels, levelForCategory, setLevelForCategory);
Logger = loggerModule.Logger;
LoggingEvent = loggerModule.LoggingEvent;
module.exports.connectLogger = connectModule(config.levels).connectLogger;
configuration.configure(configObject);
clustering.onMessage(sendLogEventToAppender);
@ -152,8 +89,8 @@ function shutdown(cb) {
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);
const appendersToCheck = Array.from(appenders.values());
const shutdownFunctions = appendersToCheck.reduceRight((accum, next) => (next.shutdown ? accum + 1 : accum), 0);
let completed = 0;
let error;
@ -173,7 +110,7 @@ function shutdown(cb) {
return cb();
}
appenders.filter(a => a.shutdown).forEach(a => a.shutdown(complete));
appendersToCheck.filter(a => a.shutdown).forEach(a => a.shutdown(complete));
return null;
}
@ -190,9 +127,12 @@ const log4js = {
configure,
shutdown,
connectLogger,
levels,
addLayout: layouts.addLayout
};
module.exports = log4js;
// set ourselves up
configure(process.env.LOG4JS_CONFIG || defaultConfig);
if (process.env.LOG4JS_CONFIG) {
configure(process.env.LOG4JS_CONFIG);
}

View File

@ -3,103 +3,100 @@
'use strict';
const debug = require('debug')('log4js:logger');
const loggingEventModule = require('./LoggingEvent');
const LoggingEvent = require('./LoggingEvent');
const levels = require('./levels');
const clustering = require('./clustering');
const categories = require('./categories');
const configuration = require('./configuration');
module.exports = function (levels, getLevelForCategory, setLevelForCategory) {
const LoggingEvent = loggingEventModule(levels);
/**
* Logger to log messages.
* use {@see log4js#getLogger(String)} to get an instance.
*
* @name Logger
* @namespace Log4js
* @param name name of category to log to
* @param level - the loglevel for the category
* @param dispatch - the function which will receive the logevents
*
* @author Stephan Strittmatter
*/
class Logger {
constructor(dispatch, name) {
if (typeof dispatch !== 'function') {
throw new Error('No dispatch function provided.');
}
if (!name) {
throw new Error('No category provided.');
}
this.category = name;
this.dispatch = dispatch;
this.context = {};
debug(`Logger created (${this.category}, ${this.level}, ${this.dispatch})`);
/**
* Logger to log messages.
* use {@see log4js#getLogger(String)} to get an instance.
*
* @name Logger
* @namespace Log4js
* @param name name of category to log to
* @param level - the loglevel for the category
* @param dispatch - the function which will receive the logevents
*
* @author Stephan Strittmatter
*/
class Logger {
constructor(name) {
if (!name) {
throw new Error('No category provided.');
}
this.category = name;
this.context = {};
debug(`Logger created (${this.category}, ${this.level})`);
}
get level() {
return levels.getLevel(getLevelForCategory(this.category), levels.TRACE);
}
get level() {
return levels.getLevel(categories.getLevelForCategory(this.category), levels.TRACE);
}
set level(level) {
setLevelForCategory(this.category, levels.getLevel(level, this.level));
}
set level(level) {
categories.setLevelForCategory(this.category, levels.getLevel(level, 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.getLevel(args[0], levels.INFO);
if (this.isLevelEnabled(logLevel)) {
this._log(logLevel, args.slice(1));
}
}
isLevelEnabled(otherLevel) {
return this.level.isLessThanOrEqualTo(otherLevel);
}
_log(level, data) {
debug(`sending log data (${level}) to appenders`);
const loggingEvent = new LoggingEvent(this.category, level, data, this.context);
this.dispatch(loggingEvent);
}
addContext(key, value) {
this.context[key] = value;
}
removeContext(key) {
delete this.context[key];
}
clearContext() {
this.context = {};
log() {
/* eslint prefer-rest-params:0 */
// todo: once node v4 support dropped, use rest parameter instead
const args = Array.from(arguments);
const logLevel = levels.getLevel(args[0], levels.INFO);
if (this.isLevelEnabled(logLevel)) {
this._log(logLevel, args.slice(1));
}
}
function addLevelMethods(target) {
const level = levels.getLevel(target);
const levelStrLower = level.toString().toLowerCase();
const levelMethod = levelStrLower.replace(/_([a-z])/g, g => g[1].toUpperCase());
const isLevelMethod = levelMethod[0].toUpperCase() + levelMethod.slice(1);
Logger.prototype[`is${isLevelMethod}Enabled`] = function () {
return this.isLevelEnabled(level);
};
Logger.prototype[levelMethod] = function () {
/* eslint prefer-rest-params:0 */
// todo: once node v4 support dropped, use rest parameter instead
const args = Array.from(arguments);
if (this.isLevelEnabled(level)) {
this._log(level, args);
}
};
isLevelEnabled(otherLevel) {
return this.level.isLessThanOrEqualTo(otherLevel);
}
levels.levels.forEach(addLevelMethods);
_log(level, data) {
debug(`sending log data (${level}) to appenders`);
const loggingEvent = new LoggingEvent(this.category, level, data, this.context);
clustering.send(loggingEvent);
}
return {
LoggingEvent: LoggingEvent,
Logger: Logger
addContext(key, value) {
this.context[key] = value;
}
removeContext(key) {
delete this.context[key];
}
clearContext() {
this.context = {};
}
}
function addLevelMethods(target) {
const level = levels.getLevel(target);
const levelStrLower = level.toString().toLowerCase();
const levelMethod = levelStrLower.replace(/_([a-z])/g, g => g[1].toUpperCase());
const isLevelMethod = levelMethod[0].toUpperCase() + levelMethod.slice(1);
Logger.prototype[`is${isLevelMethod}Enabled`] = function () {
return this.isLevelEnabled(level);
};
};
Logger.prototype[levelMethod] = function () {
/* eslint prefer-rest-params:0 */
// todo: once node v4 support dropped, use rest parameter instead
const args = Array.from(arguments);
if (this.isLevelEnabled(level)) {
this._log(level, args);
}
};
}
levels.levels.forEach(addLevelMethods);
configuration.addListener(() => {
levels.levels.forEach(addLevelMethods);
});
module.exports = Logger;

View File

@ -1,328 +1,333 @@
'use strict';
const test = require('tap').test;
const Configuration = require('../../lib/configuration');
const util = require('util');
const path = require('path');
// const util = require('util');
// const path = require('path');
const sandbox = require('sandboxed-module');
// const log4js = require('../../lib/log4js');
// const appenders = require('../../lib/appenders');
// const configuration = require('../../lib/configuration');
const debug = require('debug')('log4js:test.configuration-validation');
function testAppender(label) {
return {
configure: function (config, layouts, findAppender) {
return {
configureCalled: true,
type: config.type,
label: label,
config: config,
layouts: layouts,
findAppender: findAppender
};
}
};
}
const testAppender = (label, result) => ({
configure: function (config, layouts, findAppender) {
debug(`testAppender(${label}).configure called`);
result.configureCalled = true;
result.type = config.type;
result.label = label;
result.config = config;
result.layouts = layouts;
result.findAppender = findAppender;
return { };
}
});
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 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(
// () => configuration.configure(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(() => log4js.configure({}), 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(() => log4js.configure({ categories: {} }), expectedError);
// t.end();
// });
//
// batch.test('should give error if config has no categories', (t) => {
// const expectedError =
// new Error('Problem with log4js configuration: ({ appenders: { out: { type: \'stdout\' } } }) ' +
// '- must have a property "categories" of type object.');
// t.throws(() => log4js.configure({ appenders: { out: { type: 'stdout' } } }), 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(
// () => log4js.configure({ 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(
// () => log4js.configure({ 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(
// () => log4js.configure({ 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(
// () => log4js.configure({ 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(
// () => log4js.configure({
// 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(
// () => log4js.configure({ 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(
// () => log4js.configure({
// 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(
// () => log4js.configure({
// 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(
// () => log4js.configure({
// 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(
// () => log4js.configure({
// 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(
// () => log4js.configure({
// 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',
const thing = {};
const sandboxedLog4js = sandbox.require(
'../../lib/log4js',
{
singleOnly: true,
requires: {
cheese: testAppender('cheesy')
cheese: testAppender('cheesy', thing)
}
}
);
const config = new SandboxedConfiguration({
sandboxedLog4js.configure({
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 load appenders relative to main file if not in core, or node_modules', (t) => {
const mainPath = path.dirname(require.main.filename);
const sandboxConfig = { singleOnly: true, requires: {} };
sandboxConfig.requires[`${mainPath}/cheese`] = testAppender('correct');
// add this one, because when we're running coverage the main path is a bit different
sandboxConfig.requires[
`${path.join(mainPath, '../../node_modules/tap/node_modules/nyc/bin/cheese')}`
] = testAppender('correct');
const SandboxedConfiguration = sandbox.require('../../lib/configuration', sandboxConfig);
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 load appenders relative to process.cwd if not found in core, node_modules', (t) => {
const SandboxedConfiguration = sandbox.require(
'../../lib/configuration',
{
singleOnly: true,
requires: {
'/var/lib/cheese/cheese': testAppender('correct'),
},
globals: {
process: { cwd: () => '/var/lib/cheese', env: {} }
}
}
);
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.test('should load appenders from core first', (t) => {
// const sandboxedLog4js = sandbox.require(
// '../../lib/log4js',
// {
// singleOnly: true,
// requires: {
// './cheese': testAppender('correct'),
// cheese: testAppender('wrong')
// }
// }
// );
//
// sandboxedLog4js.configure({
// appenders: { thing: { type: 'cheese' } },
// categories: { default: { appenders: ['thing'], level: 'ERROR' } }
// });
//
// const thing = appenders.get('thing');
// t.ok(thing.configureCalled);
// t.equal(thing.type, 'cheese');
// t.equal(thing.label, 'correct');
// t.end();
// });
//
// batch.test('should load appenders relative to main file if not in core, or node_modules', (t) => {
// const mainPath = path.dirname(require.main.filename);
// const sandboxConfig = {
// singleOnly: true,
// requires: {}
// };
// sandboxConfig.requires[`${mainPath}/cheese`] = testAppender('correct');
// // add this one, because when we're running coverage the main path is a bit different
// sandboxConfig.requires[
// `${path.join(mainPath, '../../node_modules/tap/node_modules/nyc/bin/cheese')}`
// ] = testAppender('correct');
// const sandboxedLog4js = sandbox.require('../../lib/log4js', sandboxConfig);
//
// sandboxedLog4js.configure({
// appenders: { thing: { type: 'cheese' } },
// categories: { default: { appenders: ['thing'], level: 'ERROR' } }
// });
//
// const thing = appenders.get('thing');
// t.ok(thing.configureCalled);
// t.equal(thing.type, 'cheese');
// t.equal(thing.label, 'correct');
// t.end();
// });
//
// batch.test('should load appenders relative to process.cwd if not found in core, node_modules', (t) => {
// const sandboxedLog4js = sandbox.require(
// '../../lib/log4js',
// {
// singleOnly: true,
// requires: {
// '/var/lib/cheese/cheese': testAppender('correct'),
// },
// globals: {
// process: { cwd: () => '/var/lib/cheese', env: {} }
// }
// }
// );
//
// sandboxedLog4js.configure({
// appenders: { thing: { type: 'cheese' } },
// categories: { default: { appenders: ['thing'], level: 'ERROR' } }
// });
//
// const thing = 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 sandboxedLog4js = sandbox.require(
// '../../lib/log4js',
// {
// singleOnly: true,
// requires: {
// './appenders': appenders,
// cheese: testAppender('cheesy')
// }
// }
// );
//
// sandboxedLog4js.configure({
// appenders: { thing: { type: 'cheese', foo: 'bar' }, thing2: { type: 'cheese' } },
// categories: { default: { appenders: ['thing'], level: 'ERROR' } }
// });
//
// const thing = 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

@ -4,7 +4,7 @@
const test = require('tap').test;
const EE = require('events').EventEmitter;
const levels = require('../../lib/levels')();
const levels = require('../../lib/levels');
class MockLogger {
constructor() {
@ -58,7 +58,7 @@ function request(cl, method, url, code, reqHeaders, resHeaders) {
}
test('log4js connect logger', (batch) => {
const clm = require('../../lib/connect-logger')(levels);
const clm = require('../../lib/connect-logger');
batch.test('getConnectLoggerModule', (t) => {
t.type(clm, 'object', 'should return a connect logger factory');

View File

@ -2,7 +2,7 @@
const test = require('tap').test;
const EE = require('events').EventEmitter;
const levels = require('../../lib/levels')();
const levels = require('../../lib/levels');
class MockLogger {
constructor() {
@ -41,7 +41,7 @@ class MockResponse extends EE {
}
test('log4js connect logger', (batch) => {
const clm = require('../../lib/connect-logger')(levels);
const clm = require('../../lib/connect-logger');
batch.test('with nolog config', (t) => {
const ml = new MockLogger();

View File

@ -1,7 +1,7 @@
'use strict';
const test = require('tap').test;
const levels = require('../../lib/levels')();
const levels = require('../../lib/levels');
function assertThat(assert, level) {
function assertForEach(assertion, testFn, otherLevels) {

View File

@ -1,26 +1,19 @@
'use strict';
const test = require('tap').test;
const levels = require('../../lib/levels')();
const levels = require('../../lib/levels');
const Logger = require('../../lib/logger');
const testConfig = {
level: levels.TRACE
};
const loggerModule = require('../../lib/logger')(
levels,
() => testConfig.level,
(category, level) => { testConfig.level = level; }
);
const Logger = loggerModule.Logger;
const testDispatcher = {
events: [],
dispatch: function (evt) {
this.events.push(evt);
}
};
const dispatch = testDispatcher.dispatch.bind(testDispatcher);
test('../../lib/logger', (batch) => {
batch.beforeEach((done) => {
@ -32,28 +25,20 @@ test('../../lib/logger', (batch) => {
batch.test('constructor with no parameters', (t) => {
t.throws(
() => new Logger(),
new Error('No dispatch function provided.')
);
t.end();
});
batch.test('constructor with only dispatch', (t) => {
t.throws(
() => new Logger(dispatch),
new Error('No category provided.')
);
t.end();
});
batch.test('constructor with category', (t) => {
const logger = new Logger(dispatch, 'cheese');
const logger = new Logger('cheese');
t.equal(logger.category, 'cheese', 'should use category');
t.equal(logger.level, levels.TRACE, 'should use TRACE log level');
t.end();
});
batch.test('set level should delegate', (t) => {
const logger = new Logger(dispatch, 'cheese');
const logger = new Logger('cheese');
logger.level = 'debug';
t.equal(logger.category, 'cheese', 'should use category');
t.equal(logger.level, levels.DEBUG, 'should use level');
@ -61,7 +46,7 @@ test('../../lib/logger', (batch) => {
});
batch.test('isLevelEnabled', (t) => {
const logger = new Logger(dispatch, 'cheese');
const logger = new Logger('cheese');
const functions = [
'isTraceEnabled', 'isDebugEnabled', 'isInfoEnabled',
'isWarnEnabled', 'isErrorEnabled', 'isFatalEnabled'
@ -83,7 +68,7 @@ test('../../lib/logger', (batch) => {
});
batch.test('should send log events to dispatch function', (t) => {
const logger = new Logger(dispatch, 'cheese');
const logger = new Logger('cheese');
logger.debug('Event 1');
logger.debug('Event 2');
logger.debug('Event 3');
@ -97,7 +82,7 @@ test('../../lib/logger', (batch) => {
});
batch.test('should add context values to every event', (t) => {
const logger = new Logger(dispatch, 'fromage');
const logger = new Logger('fromage');
logger.debug('Event 1');
logger.addContext('cheese', 'edam');
logger.debug('Event 2');
@ -121,7 +106,7 @@ test('../../lib/logger', (batch) => {
});
batch.test('should not break when log data has no toString', (t) => {
const logger = new Logger(dispatch, 'thing');
const logger = new Logger('thing');
logger.info('Just testing ', Object.create(null));
const events = testDispatcher.events;

View File

@ -2,8 +2,8 @@ const test = require('tap').test;
const net = require('net');
const log4js = require('../../lib/log4js');
const vcr = require('../../lib/appenders/recording');
const levels = require('../../lib/levels')();
const LoggingEvent = (require('../../lib/logger')(levels)).LoggingEvent;
const levels = require('../../lib/levels');
const LoggingEvent = require('../../lib/LoggingEvent');
log4js.configure({
appenders: {