Merge pull request #459 from nomiddlename/version-2

Version 2
This commit is contained in:
Gareth Jones 2017-03-27 08:11:06 +11:00 committed by GitHub
commit 406084ab3a
70 changed files with 2526 additions and 3592 deletions

View File

@ -1,7 +1,5 @@
'use strict';
const log4js = require('../log4js');
function categoryFilter(excludes, appender) {
if (typeof excludes === 'string') excludes = [excludes];
return (logEvent) => {
@ -11,11 +9,9 @@ function categoryFilter(excludes, appender) {
};
}
function configure(config, options) {
log4js.loadAppender(config.appender.type);
const appender = log4js.appenderMakers[config.appender.type](config.appender, options);
function configure(config, layouts, findAppender) {
const appender = findAppender(config.appender);
return categoryFilter(config.exclude, appender);
}
module.exports.appender = categoryFilter;
module.exports.configure = configure;

View File

@ -39,7 +39,7 @@ function deserializeLoggingEvent(loggingEventString) {
try {
loggingEvent = JSON.parse(loggingEventString);
loggingEvent.startTime = new Date(loggingEvent.startTime);
loggingEvent.level = log4js.levels.toLevel(loggingEvent.level.levelStr);
loggingEvent.level = log4js.levels.getLevel(loggingEvent.level.levelStr);
// Unwrap serialized errors
for (let i = 0; i < loggingEvent.data.length; i++) {
const item = loggingEvent.data[i];

View File

@ -1,23 +1,19 @@
'use strict';
const layouts = require('../layouts');
const consoleLog = console.log.bind(console);
function consoleAppender(layout, timezoneOffset) {
layout = layout || layouts.colouredLayout;
return (loggingEvent) => {
consoleLog(layout(loggingEvent, timezoneOffset));
};
}
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 consoleAppender(layout, config.timezoneOffset);
}
module.exports.appender = consoleAppender;
module.exports.configure = configure;

View File

@ -1,19 +1,9 @@
'use strict';
const streams = require('streamroller');
const layouts = require('../layouts');
const path = require('path');
const os = require('os');
const eol = os.EOL || '\n';
const openFiles = [];
// close open files on process exit.
process.on('exit', () => {
openFiles.forEach((file) => {
file.end();
});
});
/**
* File appender that rolls files according to a date pattern.
@ -30,21 +20,27 @@ function appender(
options,
timezoneOffset
) {
layout = layout || layouts.basicLayout;
const logFile = new streams.DateRollingFileStream(
filename,
pattern,
options
);
openFiles.push(logFile);
return (logEvent) => {
const app = function (logEvent) {
logFile.write(layout(logEvent, timezoneOffset) + eol, 'utf8');
};
app.shutdown = function (complete) {
logFile.write('', 'utf-8', () => {
logFile.end(complete);
});
};
return app;
}
function configure(config, options) {
let layout;
function configure(config, layouts) {
let layout = layouts.basicLayout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
@ -54,10 +50,6 @@ function configure(config, options) {
config.alwaysIncludePattern = false;
}
if (options && options.cwd && !config.absolute) {
config.filename = path.join(options.cwd, config.filename);
}
return appender(
config.filename,
config.pattern,
@ -67,27 +59,4 @@ function configure(config, options) {
);
}
function shutdown(cb) {
let completed = 0;
let error;
const complete = (err) => {
error = error || err;
completed++; // eslint-disable-line no-plusplus
if (completed >= openFiles.length) {
cb(error);
}
};
if (!openFiles.length) {
return cb();
}
return openFiles.forEach((file) => {
file.write('', 'utf-8', () => {
file.end(complete);
});
});
}
module.exports.appender = appender;
module.exports.configure = configure;
module.exports.shutdown = shutdown;

View File

@ -1,31 +1,25 @@
'use strict';
const debug = require('debug')('log4js:file');
const layouts = require('../layouts');
const path = require('path');
const streams = require('streamroller');
const os = require('os');
const eol = os.EOL || '\n';
const openFiles = [];
// close open files on process exit.
process.on('exit', () => {
debug('Exit handler called.');
openFiles.forEach((file) => {
file.end();
function openTheStream(file, fileSize, numFiles, options) {
const stream = new streams.RollingFileStream(
file,
fileSize,
numFiles,
options
);
stream.on('error', (err) => {
console.error('log4js.fileAppender - Writing to file %s, error happened ', file, err); //eslint-disable-line
});
});
return stream;
}
// On SIGHUP, close and reopen all files. This allows this appender to work with
// logrotate. Note that if you are using logrotate, you should not set
// `logSize`.
process.on('SIGHUP', () => {
debug('SIGHUP handler called.');
openFiles.forEach((writer) => {
writer.closeTheStream(writer.openTheStream.bind(writer));
});
});
/**
* File Appender writing the logs to a text file. Supports rolling of logs by size.
@ -42,7 +36,6 @@ process.on('SIGHUP', () => {
*/
function fileAppender(file, layout, logSize, numBackups, options, timezoneOffset) {
file = path.normalize(file);
layout = layout || layouts.basicLayout;
numBackups = numBackups === undefined ? 5 : numBackups;
// there has to be at least one backup if logSize has been specified
numBackups = numBackups === 0 ? 1 : numBackups;
@ -54,40 +47,40 @@ function fileAppender(file, layout, logSize, numBackups, options, timezoneOffset
options, ', ',
timezoneOffset, ')'
);
const writer = openTheStream(file, logSize, numBackups, options);
// push file to the stack of open handlers
openFiles.push(writer);
return function (loggingEvent) {
const app = function (loggingEvent) {
writer.write(layout(loggingEvent, timezoneOffset) + eol, 'utf8');
};
}
function openTheStream(file, fileSize, numFiles, options) {
const stream = new streams.RollingFileStream(
file,
fileSize,
numFiles,
options
);
stream.on('error', (err) => {
console.error('log4js.fileAppender - Writing to file %s, error happened ', file, err);
app.reopen = function () {
writer.closeTheStream(writer.openTheStream.bind(writer));
};
app.shutdown = function (complete) {
writer.write('', 'utf-8', () => {
writer.end(complete);
});
};
// On SIGHUP, close and reopen all files. This allows this appender to work with
// logrotate. Note that if you are using logrotate, you should not set
// `logSize`.
process.on('SIGHUP', () => {
debug('SIGHUP handler called.');
app.reopen();
});
return stream;
return app;
}
function configure(config, options) {
let layout;
function configure(config, layouts) {
let layout = layouts.basicLayout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
if (options && options.cwd && !config.absolute) {
config.filename = path.join(options.cwd, config.filename);
}
return fileAppender(
config.filename,
layout,
@ -98,27 +91,4 @@ function configure(config, options) {
);
}
function shutdown(cb) {
let completed = 0;
let error;
const complete = (err) => {
error = error || err;
completed++; // eslint-disable-line no-plusplus
if (completed >= openFiles.length) {
cb(error);
}
};
if (!openFiles.length) {
return cb();
}
return openFiles.forEach((file) => {
file.write('', 'utf-8', () => {
file.end(complete);
});
});
}
module.exports.appender = fileAppender;
module.exports.configure = configure;
module.exports.shutdown = shutdown;

View File

@ -1,7 +1,6 @@
'use strict';
const debug = require('debug')('log4js:fileSync');
const layouts = require('../layouts');
const path = require('path');
const fs = require('fs');
const os = require('os');
@ -135,7 +134,6 @@ class RollingFileSync {
function fileAppender(file, layout, logSize, numBackups, timezoneOffset) {
debug('fileSync appender created');
file = path.normalize(file);
layout = layout || layouts.basicLayout;
numBackups = numBackups === undefined ? 5 : numBackups;
// there has to be at least one backup if logSize has been specified
numBackups = numBackups === 0 ? 1 : numBackups;
@ -174,16 +172,12 @@ function fileAppender(file, layout, logSize, numBackups, timezoneOffset) {
};
}
function configure(config, options) {
let layout;
function configure(config, layouts) {
let layout = layouts.basicLayout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
if (options && options.cwd && !config.absolute) {
config.filename = path.join(options.cwd, config.filename);
}
return fileAppender(
config.filename,
layout,
@ -193,5 +187,4 @@ function configure(config, options) {
);
}
module.exports.appender = fileAppender;
module.exports.configure = configure;

View File

@ -1,8 +1,7 @@
'use strict';
const zlib = require('zlib');
const layouts = require('../layouts');
const levels = require('../levels');
// const levels = require('../levels');
const dgram = require('dgram');
const util = require('util');
const OS = require('os');
@ -18,43 +17,31 @@ const LOG_NOTICE = 5; // normal, but significant, condition(unused)
const LOG_INFO = 6; // informational message
const LOG_DEBUG = 7; // debug-level message
const levelMapping = {};
levelMapping[levels.ALL] = LOG_DEBUG;
levelMapping[levels.TRACE] = LOG_DEBUG;
levelMapping[levels.DEBUG] = LOG_DEBUG;
levelMapping[levels.INFO] = LOG_INFO;
levelMapping[levels.WARN] = LOG_WARNING;
levelMapping[levels.ERROR] = LOG_ERROR;
levelMapping[levels.FATAL] = LOG_CRIT;
let client;
/**
* GELF appender that supports sending UDP packets to a GELF compatible server such as Graylog
*
* @param layout a function that takes a logevent and returns a string (defaults to none).
* @param host - host to which to send logs (default:localhost)
* @param port - port at which to send logs to (default:12201)
* @param hostname - hostname of the current host (default:OS hostname)
* @param facility - facility to log to (default:nodejs-server)
* @param config.host - host to which to send logs (default:localhost)
* @param config.port - port at which to send logs to (default:12201)
* @param config.hostname - hostname of the current host (default:OS hostname)
* @param config.facility - facility to log to (default:nodejs-server)
*/
/* eslint no-underscore-dangle:0 */
function gelfAppender(layout, host, port, hostname, facility) {
let config;
let customFields;
if (typeof host === 'object') {
config = host;
host = config.host;
port = config.port;
hostname = config.hostname;
facility = config.facility;
customFields = config.customFields;
}
function gelfAppender(layout, config, levels) {
const levelMapping = {};
levelMapping[levels.ALL] = LOG_DEBUG;
levelMapping[levels.TRACE] = LOG_DEBUG;
levelMapping[levels.DEBUG] = LOG_DEBUG;
levelMapping[levels.INFO] = LOG_INFO;
levelMapping[levels.WARN] = LOG_WARNING;
levelMapping[levels.ERROR] = LOG_ERROR;
levelMapping[levels.FATAL] = LOG_CRIT;
host = host || 'localhost';
port = port || 12201;
hostname = hostname || OS.hostname();
layout = layout || layouts.messagePassThroughLayout;
const host = config.host || 'localhost';
const port = config.port || 12201;
const hostname = config.hostname || OS.hostname();
const facility = config.facility;
const customFields = config.customFields;
const defaultCustomFields = customFields || {};
@ -62,7 +49,7 @@ function gelfAppender(layout, host, port, hostname, facility) {
defaultCustomFields._facility = facility;
}
client = dgram.createSocket('udp4');
const client = dgram.createSocket('udp4');
process.on('exit', () => {
if (client) client.close();
@ -123,7 +110,7 @@ function gelfAppender(layout, host, port, hostname, facility) {
});
}
return (loggingEvent) => {
const app = (loggingEvent) => {
const message = preparePacket(loggingEvent);
zlib.gzip(new Buffer(JSON.stringify(message)), (err, packet) => {
if (err) {
@ -137,23 +124,21 @@ function gelfAppender(layout, host, port, hostname, facility) {
}
});
};
app.shutdown = function (cb) {
if (client) {
client.close(cb);
}
};
return app;
}
function configure(config) {
let layout;
function configure(config, layouts, findAppender, levels) {
let layout = layouts.messagePassThroughLayout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
return gelfAppender(layout, config);
return gelfAppender(layout, config, levels);
}
function shutdown(cb) {
if (client) {
client.close(cb);
client = null;
}
}
module.exports.appender = gelfAppender;
module.exports.configure = configure;
module.exports.shutdown = shutdown;

View File

@ -1,17 +1,12 @@
'use strict';
const hipchat = require('hipchat-notifier');
const layouts = require('../layouts');
module.exports.name = 'hipchat';
module.exports.appender = hipchatAppender;
module.exports.configure = hipchatConfigure;
/**
@invoke as
log4js.configure({
'appenders': [
'appenders': { 'hipchat':
{
'type' : 'hipchat',
'hipchat_token': '< User token with Notification Privileges >',
@ -21,7 +16,8 @@ module.exports.configure = hipchatConfigure;
'hipchat_notify': '[ notify boolean to bug people ]',
'hipchat_host' : 'api.hipchat.com'
}
]
},
categories: { default: { appenders: ['hipchat'], level: 'debug' }}
});
var logger = log4js.getLogger('hipchat');
@ -29,17 +25,16 @@ module.exports.configure = hipchatConfigure;
@invoke
*/
/* eslint no-unused-vars:0 */
function hipchatNotifierResponseCallback(err, response, body) {
function hipchatNotifierResponseCallback(err) {
if (err) {
throw err;
}
}
function hipchatAppender(config) {
function hipchatAppender(config, layout) {
const notifier = hipchat.make(config.hipchat_room, config.hipchat_token);
// @lint W074 This function's cyclomatic complexity is too high. (10)
return (loggingEvent) => {
let notifierFn;
@ -68,7 +63,7 @@ function hipchatAppender(config) {
}
// @TODO, re-work in timezoneOffset ?
const layoutMessage = config.layout(loggingEvent);
const layoutMessage = layout(loggingEvent);
// dispatch hipchat api request, do not return anything
// [overide hipchatNotifierResponseCallback]
@ -77,12 +72,14 @@ function hipchatAppender(config) {
};
}
function hipchatConfigure(config) {
let layout;
function hipchatConfigure(config, layouts) {
let layout = layouts.messagePassThroughLayout;
if (!config.layout) {
config.layout = layouts.messagePassThroughLayout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
return hipchatAppender(config, layout);
}
module.exports.configure = hipchatConfigure;

View File

@ -0,0 +1,89 @@
/**
* logFaces appender sends JSON formatted log events to logFaces receivers.
* There are two types of receivers supported - raw UDP sockets (for server side apps),
* and HTTP (for client side apps). Depending on the usage, this appender
* requires either of the two:
*
* For UDP require 'dgram', see 'https://nodejs.org/api/dgram.html'
* For HTTP require 'axios', see 'https://www.npmjs.com/package/axios'
*
* Make sure your project have relevant dependancy installed before using this appender.
*/
/* eslint global-require:0 */
'use strict';
const util = require('util');
const axios = require('axios');
/**
*
* For HTTP (browsers or node.js) use the following configuration params:
* {
* "type": "logFaces-HTTP", // must be present for instantiation
* "application": "LFS-TEST", // name of the application (domain)
* "url": "http://lfs-server/logs", // logFaces receiver servlet URL
* }
*/
function logFacesAppender(config) {
const sender = axios.create({
baseURL: config.url,
timeout: config.timeout || 5000,
headers: { 'Content-Type': 'application/json' },
withCredentials: true
});
return function log(event) {
// convert to logFaces compact json format
const lfsEvent = {
a: config.application || '', // application name
t: event.startTime.getTime(), // time stamp
p: event.level.levelStr, // level (priority)
g: event.categoryName, // logger name
m: format(event.data) // message text
};
// add context variables if exist
Object.keys(event.context).forEach((key) => {
lfsEvent[`p_${key}`] = event.context[key];
});
// send to server
sender.post('', lfsEvent)
.catch((error) => {
if (error.response) {
console.error(
`log4js.logFaces-HTTP Appender error posting to ${config.url}: ${error.response.status} - ${error.response.data}`
);
return;
}
console.error(`log4js.logFaces-HTTP Appender error: ${error.message}`);
});
};
}
function configure(config) {
return logFacesAppender(config);
}
function format(logData) {
const data = Array.isArray(logData) ?
logData : Array.prototype.slice.call(arguments);
return util.format.apply(util, wrapErrorsWithInspect(data));
}
function wrapErrorsWithInspect(items) {
return items.map((item) => {
if ((item instanceof Error) && item.stack) {
return {
inspect: function () {
return `${util.format(item)}\n${item.stack}`;
}
};
}
return item;
});
}
module.exports.configure = configure;

View File

@ -0,0 +1,90 @@
/**
* logFaces appender sends JSON formatted log events to logFaces receivers.
* There are two types of receivers supported - raw UDP sockets (for server side apps),
* and HTTP (for client side apps). Depending on the usage, this appender
* requires either of the two:
*
* For UDP require 'dgram', see 'https://nodejs.org/api/dgram.html'
* For HTTP require 'axios', see 'https://www.npmjs.com/package/axios'
*
* Make sure your project have relevant dependancy installed before using this appender.
*/
'use strict';
const util = require('util');
const dgram = require('dgram');
function datagram(config) {
const sock = dgram.createSocket('udp4');
const host = config.remoteHost || '127.0.0.1';
const port = config.port || 55201;
return function (event) {
const buff = new Buffer(JSON.stringify(event));
sock.send(buff, 0, buff.length, port, host, (err) => {
if (err) {
console.error(`log4js.logFacesUDPAppender error sending to ${host}:${port}, error: `, err);
}
});
};
}
/**
* For UDP (node.js) use the following configuration params:
* {
* "type": "logFaces-UDP", // must be present for instantiation
* "application": "LFS-TEST", // name of the application (domain)
* "remoteHost": "127.0.0.1", // logFaces server address (hostname)
* "port": 55201 // UDP receiver listening port
* }
*
*/
function logFacesUDPAppender(config) {
const send = datagram(config);
return function log(event) {
// convert to logFaces compact json format
const lfsEvent = {
a: config.application || '', // application name
t: event.startTime.getTime(), // time stamp
p: event.level.levelStr, // level (priority)
g: event.categoryName, // logger name
m: format(event.data) // message text
};
// add context variables if exist
Object.keys(event.context).forEach((key) => {
lfsEvent[`p_${key}`] = event.context[key];
});
// send to server
send(lfsEvent);
};
}
function configure(config) {
return logFacesUDPAppender(config);
}
function wrapErrorsWithInspect(items) {
return items.map((item) => {
if ((item instanceof Error) && item.stack) {
return {
inspect: function () {
return `${util.format(item)}\n${item.stack}`;
}
};
}
return item;
});
}
function format(logData) {
const data = Array.isArray(logData) ?
logData : Array.prototype.slice.call(arguments);
return util.format.apply(util, wrapErrorsWithInspect(data));
}
module.exports.configure = configure;

View File

@ -1,130 +0,0 @@
/**
* logFaces appender sends JSON formatted log events to logFaces receivers.
* There are two types of receivers supported - raw UDP sockets (for server side apps),
* and HTTP (for client side apps). Depending on the usage, this appender
* requires either of the two:
*
* For UDP require 'dgram', see 'https://nodejs.org/api/dgram.html'
* For HTTP require 'axios', see 'https://www.npmjs.com/package/axios'
*
* Make sure your project have relevant dependancy installed before using this appender.
*/
/* eslint global-require:0 */
'use strict';
const util = require('util');
const context = {};
function datagram(config) {
const sock = require('dgram').createSocket('udp4');
const host = config.remoteHost || '127.0.0.1';
const port = config.port || 55201;
return function (event) {
const buff = new Buffer(JSON.stringify(event));
sock.send(buff, 0, buff.length, port, host, (err) => {
if (err) {
console.error('log4js.logFacesAppender failed to %s:%d, error: %s',
host, port, err);
}
});
};
}
function servlet(config) {
const axios = require('axios').create();
axios.defaults.baseURL = config.url;
axios.defaults.timeout = config.timeout || 5000;
axios.defaults.headers = { 'Content-Type': 'application/json' };
axios.defaults.withCredentials = true;
return function (lfsEvent) {
axios.post('', lfsEvent)
.then((response) => {
if (response.status !== 200) {
console.error('log4js.logFacesAppender post to %s failed: %d',
config.url, response.status);
}
})
.catch((response) => {
console.error('log4js.logFacesAppender post to %s excepted: %s',
config.url, response.status);
});
};
}
/**
* For UDP (node.js) use the following configuration params:
* {
* "type": "logFacesAppender", // must be present for instantiation
* "application": "LFS-TEST", // name of the application (domain)
* "remoteHost": "127.0.0.1", // logFaces server address (hostname)
* "port": 55201 // UDP receiver listening port
* }
*
* For HTTP (browsers or node.js) use the following configuration params:
* {
* "type": "logFacesAppender", // must be present for instantiation
* "application": "LFS-TEST", // name of the application (domain)
* "url": "http://lfs-server/logs", // logFaces receiver servlet URL
* }
*/
function logFacesAppender(config) {
let send = config.send;
if (send === undefined) {
send = (config.url === undefined) ? datagram(config) : servlet(config);
}
return function log(event) {
// convert to logFaces compact json format
const lfsEvent = {
a: config.application || '', // application name
t: event.startTime.getTime(), // time stamp
p: event.level.levelStr, // level (priority)
g: event.categoryName, // logger name
m: format(event.data) // message text
};
// add context variables if exist
Object.keys(context).forEach((key) => {
lfsEvent[`p_${key}`] = context[key];
});
// send to server
send(lfsEvent);
};
}
function configure(config) {
return logFacesAppender(config);
}
function setContext(key, value) {
context[key] = value;
}
function format(logData) {
const data = Array.isArray(logData) ?
logData : Array.prototype.slice.call(arguments);
return util.format.apply(util, wrapErrorsWithInspect(data));
}
function wrapErrorsWithInspect(items) {
return items.map((item) => {
if ((item instanceof Error) && item.stack) {
return {
inspect: function () {
return `${util.format(item)}\n${item.stack}`;
}
};
}
return item;
});
}
module.exports.appender = logFacesAppender;
module.exports.configure = configure;
module.exports.setContext = setContext;

View File

@ -1,11 +1,8 @@
'use strict';
const levels = require('../levels');
const log4js = require('../log4js');
function logLevelFilter(minLevelString, maxLevelString, appender) {
const minLevel = levels.toLevel(minLevelString);
const maxLevel = levels.toLevel(maxLevelString, levels.FATAL);
function logLevelFilter(minLevelString, maxLevelString, appender, levels) {
const minLevel = levels.getLevel(minLevelString);
const maxLevel = levels.getLevel(maxLevelString, levels.FATAL);
return (logEvent) => {
const eventLevel = logEvent.level;
if (eventLevel.isGreaterThanOrEqualTo(minLevel) && eventLevel.isLessThanOrEqualTo(maxLevel)) {
@ -14,11 +11,9 @@ function logLevelFilter(minLevelString, maxLevelString, appender) {
};
}
function configure(config, options) {
log4js.loadAppender(config.appender.type);
const appender = log4js.appenderMakers[config.appender.type](config.appender, options);
return logLevelFilter(config.level, config.maxLevel, appender);
function configure(config, layouts, findAppender, levels) {
const appender = findAppender(config.appender);
return logLevelFilter(config.level, config.maxLevel, appender, levels);
}
module.exports.appender = logLevelFilter;
module.exports.configure = configure;

View File

@ -2,27 +2,16 @@
'use strict';
const layouts = require('../layouts');
const debug = require('debug')('log4js:loggly');
const loggly = require('loggly');
const os = require('os');
const passThrough = layouts.messagePassThroughLayout;
let openRequests = 0;
let shutdownCB;
function isAnyObject(value) {
return value !== null && (typeof value === 'object' || typeof value === 'function');
}
function numKeys(obj) {
let res = 0;
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
res++; // eslint-disable-line no-plusplus
}
}
return res;
return Object.keys(obj).length;
}
/**
@ -64,9 +53,12 @@ function processTags(msgListArgs) {
*/
function logglyAppender(config, layout) {
const client = loggly.createClient(config);
if (!layout) layout = passThrough;
let openRequests = 0;
let shutdownCB;
return (loggingEvent) => {
debug('creating appender.');
function app(loggingEvent) {
const result = processTags(loggingEvent.data);
const deTaggedData = result.deTaggedData;
const additionalTags = result.additionalTags;
@ -77,45 +69,52 @@ function logglyAppender(config, layout) {
const msg = layout(loggingEvent);
openRequests += 1;
debug('sending log event to loggly');
client.log(
{
msg: msg,
level: loggingEvent.level.levelStr,
category: loggingEvent.categoryName,
hostname: os.hostname().toString(),
},
additionalTags,
(error) => {
if (error) {
console.error('log4js.logglyAppender - error occurred: ', error);
}
client.log({
msg: msg,
level: loggingEvent.level.levelStr,
category: loggingEvent.categoryName,
hostname: os.hostname().toString(),
}, additionalTags, (error) => {
if (error) {
console.error('log4js.logglyAppender - error occurred: ', error);
debug('log event received by loggly.');
openRequests -= 1;
if (shutdownCB && openRequests === 0) {
shutdownCB();
shutdownCB = undefined;
}
}
);
}
openRequests -= 1;
if (shutdownCB && openRequests === 0) {
shutdownCB();
shutdownCB = undefined;
}
});
app.shutdown = function (cb) {
debug('shutdown called');
if (openRequests === 0) {
cb();
} else {
shutdownCB = cb;
}
};
return app;
}
function configure(config) {
let layout;
function configure(config, layouts) {
let layout = layouts.messagePassThroughLayout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
debug('configuring new appender');
return logglyAppender(config, layout);
}
function shutdown(cb) {
if (openRequests === 0) {
cb();
} else {
shutdownCB = cb;
}
}
module.exports.name = 'loggly';
module.exports.appender = logglyAppender;
module.exports.configure = configure;
module.exports.shutdown = shutdown;

View File

@ -1,19 +1,29 @@
'use strict';
const layouts = require('../layouts');
const dgram = require('dgram');
const util = require('util');
function sendLog(udp, host, port, logObject) {
const buffer = new Buffer(JSON.stringify(logObject));
/* eslint no-unused-vars:0 */
udp.send(buffer, 0, buffer.length, port, host, (err, bytes) => {
if (err) {
console.error('log4js.logstashUDP - %s:%p Error: %s', host, port, util.inspect(err));
}
});
}
function logstashUDP(config, layout) {
const udp = dgram.createSocket('udp4');
const type = config.logType ? config.logType : config.category;
layout = layout || layouts.dummyLayout;
if (!config.fields) {
config.fields = {};
}
return function log(loggingEvent) {
function log(loggingEvent) {
/*
https://gist.github.com/jordansissel/2996677
{
@ -30,11 +40,9 @@ function logstashUDP(config, layout) {
/* eslint no-prototype-builtins:1,no-restricted-syntax:[1, "ForInStatement"] */
if (loggingEvent.data.length > 1) {
const secondEvData = loggingEvent.data[1];
for (const key in secondEvData) {
if (secondEvData.hasOwnProperty(key)) {
config.fields[key] = secondEvData[key];
}
}
Object.keys(secondEvData).forEach((key) => {
config.fields[key] = secondEvData[key];
});
}
config.fields.level = loggingEvent.level.levelStr;
config.fields.category = loggingEvent.categoryName;
@ -52,22 +60,17 @@ function logstashUDP(config, layout) {
logObject[keys[i]] = config.fields[keys[i]];
}
sendLog(udp, config.host, config.port, logObject);
}
log.shutdown = function (cb) {
udp.close(cb);
};
return log;
}
function sendLog(udp, host, port, logObject) {
const buffer = new Buffer(JSON.stringify(logObject));
/* eslint no-unused-vars:0 */
udp.send(buffer, 0, buffer.length, port, host, (err, bytes) => {
if (err) {
console.error('log4js.logstashUDP - %s:%p Error: %s', host, port, util.inspect(err));
}
});
}
function configure(config) {
let layout;
function configure(config, layouts) {
let layout = layouts.dummyLayout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
@ -75,5 +78,4 @@ function configure(config) {
return logstashUDP(config, layout);
}
module.exports.appender = logstashUDP;
module.exports.configure = configure;

View File

@ -1,21 +1,18 @@
'use strict';
const layouts = require('../layouts');
const mailgunFactory = require('mailgun-js');
let layout;
let config;
let mailgun;
function mailgunAppender(_config, _layout) {
config = _config;
layout = _layout || layouts.basicLayout;
function mailgunAppender(config, layout) {
const mailgun = mailgunFactory({
apiKey: config.apikey,
domain: config.domain
});
return (loggingEvent) => {
const data = {
from: _config.from,
to: _config.to,
subject: _config.subject,
from: config.from,
to: config.to,
subject: config.subject,
text: layout(loggingEvent, config.timezoneOffset)
};
@ -26,20 +23,13 @@ function mailgunAppender(_config, _layout) {
};
}
function configure(_config) {
config = _config;
if (_config.layout) {
layout = layouts.layout(_config.layout.type, _config.layout);
function configure(config, layouts) {
let layout = layouts.basicLayout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
mailgun = mailgunFactory({
apiKey: _config.apikey,
domain: _config.domain
});
return mailgunAppender(_config, layout);
return mailgunAppender(config, layout);
}
module.exports.appender = mailgunAppender;
module.exports.configure = configure;

View File

@ -1,33 +1,33 @@
'use strict';
const log4js = require('../log4js');
const debug = require('debug')('log4js:multiprocess');
const net = require('net');
const END_MSG = '__LOG4JS__';
const servers = [];
/**
* Creates a server, listening on config.loggerPort, config.loggerHost.
* Output goes to config.actualAppender (config.appender is used to
* set up that appender).
*/
function logServer(config) {
function logServer(config, actualAppender, levels) {
/**
* Takes a utf-8 string, returns an object with
* the correct log properties.
*/
function deserializeLoggingEvent(clientSocket, msg) {
debug('deserialising log event');
let loggingEvent;
try {
loggingEvent = JSON.parse(msg);
loggingEvent.startTime = new Date(loggingEvent.startTime);
loggingEvent.level = log4js.levels.toLevel(loggingEvent.level.levelStr);
loggingEvent.level = levels.getLevel(loggingEvent.level.levelStr);
} catch (e) {
// JSON.parse failed, just log the contents probably a naughty.
loggingEvent = {
startTime: new Date(),
categoryName: 'log4js',
level: log4js.levels.ERROR,
level: levels.ERROR,
data: ['Unable to parse log:', msg]
};
}
@ -38,8 +38,6 @@ function logServer(config) {
return loggingEvent;
}
const actualAppender = config.actualAppender;
/* eslint prefer-arrow-callback:0 */
const server = net.createServer(function serverCreated(clientSocket) {
clientSocket.setEncoding('utf8');
@ -47,11 +45,13 @@ function logServer(config) {
function logTheMessage(msg) {
if (logMessage.length > 0) {
debug('deserialising log event and sending to actual appender');
actualAppender(deserializeLoggingEvent(clientSocket, msg));
}
}
function chunkReceived(chunk) {
debug('chunk of data received');
let event;
logMessage += chunk || '';
if (logMessage.indexOf(END_MSG) > -1) {
@ -68,12 +68,22 @@ function logServer(config) {
});
server.listen(config.loggerPort || 5000, config.loggerHost || 'localhost', function () {
servers.push(server);
debug('master server listening');
// allow the process to exit, if this is the only socket active
server.unref();
});
return actualAppender;
function app(event) {
debug('log event sent directly to actual appender (local event)');
return actualAppender(event);
}
app.shutdown = function (cb) {
debug('master shutdown called, closing server');
server.close(cb);
};
return app;
}
function workerAppender(config) {
@ -82,19 +92,24 @@ function workerAppender(config) {
let socket;
function write(loggingEvent) {
debug('Writing log event to socket');
// 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
if (loggingEvent && loggingEvent.stack && JSON.stringify(loggingEvent) === '{}') {
loggingEvent = { stack: loggingEvent.stack };
}
const logData = loggingEvent.data.map((e) => {
if (e && e.stack && JSON.stringify(e) === '{}') {
e = { stack: e.stack };
}
return e;
});
loggingEvent.data = logData;
socket.write(JSON.stringify(loggingEvent), 'utf8');
socket.write(END_MSG, 'utf8');
}
function emptyBuffer() {
let evt;
debug('emptying worker buffer');
/* eslint no-cond-assign:0 */
while ((evt = buffer.shift())) {
write(evt);
@ -102,8 +117,10 @@ function workerAppender(config) {
}
function createSocket() {
debug(`worker appender creating socket to ${config.loggerHost || 'localhost'}:${config.loggerPort || 5000}`);
socket = net.createConnection(config.loggerPort || 5000, config.loggerHost || 'localhost');
socket.on('connect', () => {
debug('worker socket connected');
emptyBuffer();
canWrite = true;
});
@ -114,45 +131,48 @@ function workerAppender(config) {
createSocket();
return function log(loggingEvent) {
function log(loggingEvent) {
if (canWrite) {
write(loggingEvent);
} else {
debug('worker buffering log event because it cannot write at the moment');
buffer.push(loggingEvent);
}
}
log.shutdown = function (cb) {
debug('worker shutdown called');
socket.removeAllListeners('close');
socket.close(cb);
};
return log;
}
function createAppender(config) {
function createAppender(config, appender, levels) {
if (config.mode === 'master') {
return logServer(config);
debug('Creating master appender');
return logServer(config, appender, levels);
}
debug('Creating worker appender');
return workerAppender(config);
}
function configure(config, options) {
let actualAppender;
if (config.appender && config.mode === 'master') {
log4js.loadAppender(config.appender.type);
actualAppender = log4js.appenderMakers[config.appender.type](config.appender, options);
config.actualAppender = actualAppender;
function configure(config, layouts, findAppender, levels) {
let appender;
debug(`configure with mode = ${config.mode}`);
if (config.mode === 'master') {
if (!config.appender) {
debug(`no appender found in config ${config}`);
throw new Error('multiprocess master must have an "appender" defined');
}
debug(`actual appender is ${config.appender}`);
appender = findAppender(config.appender);
if (!appender) {
debug(`actual appender "${config.appender}" not found`);
throw new Error(`multiprocess master appender "${config.appender}" not defined`);
}
}
return createAppender(config);
return createAppender(config, appender, levels);
}
function shutdown(done) {
let toBeClosed = servers.length;
servers.forEach(function (server) {
server.close(function () {
toBeClosed -= 1;
if (toBeClosed < 1) {
done();
}
});
});
}
module.exports.appender = createAppender;
module.exports.configure = configure;
module.exports.shutdown = shutdown;

View File

@ -0,0 +1,28 @@
'use strict';
const debug = require('debug')('log4js:recording');
let recordedEvents = [];
function configure() {
return function (logEvent) {
debug(`received logEvent, number of events now ${recordedEvents.length + 1}`);
recordedEvents.push(logEvent);
};
}
function replay() {
return recordedEvents;
}
function reset() {
recordedEvents = [];
}
module.exports = {
configure: configure,
replay: replay,
playback: replay,
reset: reset,
erase: reset
};

View File

@ -1,29 +1,32 @@
'use strict';
const layouts = require('../layouts');
const redis = require('redis');
const util = require('util');
function redisAppender(config, layout) {
layout = layout || layouts.messagePassThroughLayout;
const redisClient = redis.createClient(config.port, config.host, { auth_pass: config.pass });
const host = config.host || '127.0.0.1';
const port = config.port || 6379;
const auth = config.pass ? { auth_pass: config.pass } : {};
const redisClient = redis.createClient(port, host, auth);
redisClient.on('error', (err) => {
if (err) {
console.error('log4js.redisAppender - %s:%p Error: %s', config.host, config.port, util.inspect(err));
console.error(`log4js.redisAppender - ${host}:${port} Error: ${util.inspect(err)}`);
}
});
return function (loggingEvent) {
const message = layout(loggingEvent);
redisClient.publish(config.channel, message, (err) => {
if (err) {
console.error('log4js.redisAppender - %s:%p Error: %s', config.host, config.port, util.inspect(err));
console.error(`log4js.redisAppender - ${host}:${port} Error: ${util.inspect(err)}`);
}
});
};
}
function configure(config) {
let layout;
function configure(config, layouts) {
let layout = layouts.messagePassThroughLayout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
@ -31,5 +34,4 @@ function configure(config) {
return redisAppender(config, layout);
}
module.exports.appender = redisAppender;
module.exports.configure = configure;

View File

@ -1,14 +1,8 @@
'use strict';
const Slack = require('slack-node');
const layouts = require('../layouts');
let layout;
let slack;
function slackAppender(_config, _layout) {
layout = _layout || layouts.basicLayout;
function slackAppender(_config, layout, slack) {
return (loggingEvent) => {
const data = {
channel_id: _config.channel_id,
@ -31,16 +25,15 @@ function slackAppender(_config, _layout) {
};
}
function configure(_config) {
function configure(_config, layouts) {
const slack = new Slack(_config.token);
let layout = layouts.basicLayout;
if (_config.layout) {
layout = layouts.layout(_config.layout.type, _config.layout);
}
slack = new Slack(_config.token);
return slackAppender(_config, layout);
return slackAppender(_config, layout, slack);
}
module.exports.name = 'slack';
module.exports.appender = slackAppender;
module.exports.configure = configure;

View File

@ -1,89 +1,8 @@
'use strict';
const layouts = require('../layouts');
const mailer = require('nodemailer');
const os = require('os');
const logEventBuffer = [];
let subjectLayout;
let layout;
let unsentCount = 0;
let shutdownTimeout;
let sendInterval;
let sendTimer;
let config;
function sendBuffer() {
if (logEventBuffer.length > 0) {
const transportOpts = getTransportOptions(config);
const transport = mailer.createTransport(transportOpts);
const firstEvent = logEventBuffer[0];
let body = '';
const count = logEventBuffer.length;
while (logEventBuffer.length > 0) {
body += `${layout(logEventBuffer.shift(), config.timezoneOffset)}\n`;
}
const msg = {
to: config.recipients,
subject: config.subject || subjectLayout(firstEvent),
headers: { Hostname: os.hostname() }
};
if (config.attachment.enable === true) {
msg[config.html ? 'html' : 'text'] = config.attachment.message;
msg.attachments = [
{
filename: config.attachment.filename,
contentType: 'text/x-log',
content: body
}
];
} else {
msg[config.html ? 'html' : 'text'] = body;
}
if (config.sender) {
msg.from = config.sender;
}
transport.sendMail(msg, (error) => {
if (error) {
console.error('log4js.smtpAppender - Error happened', error);
}
transport.close();
unsentCount -= count;
});
}
}
function getTransportOptions() {
let transportOpts = null;
if (config.SMTP) {
transportOpts = config.SMTP;
} else if (config.transport) {
const plugin = config.transport.plugin || 'smtp';
const transportModule = `nodemailer-${plugin}-transport`;
/* eslint global-require:0 */
const transporter = require(transportModule); // eslint-disable-line
transportOpts = transporter(config.transport.options);
}
return transportOpts;
}
function scheduleSend() {
if (!sendTimer) {
sendTimer = setTimeout(() => {
sendTimer = null;
sendBuffer();
}, sendInterval);
}
}
/**
* SMTP Appender. Sends logging events using SMTP protocol.
* It can either send an email on each event or group several
@ -95,9 +14,7 @@ function scheduleSend() {
* config.shutdownTimeout time to give up remaining emails (in seconds; defaults to 5).
* @param _layout a function that takes a logevent and returns a string (defaults to basicLayout).
*/
function smtpAppender(_config, _layout) {
config = _config;
function smtpAppender(config, layout, subjectLayout) {
if (!config.attachment) {
config.attachment = {};
}
@ -105,13 +22,97 @@ function smtpAppender(_config, _layout) {
config.attachment.enable = !!config.attachment.enable;
config.attachment.message = config.attachment.message || 'See logs as attachment';
config.attachment.filename = config.attachment.filename || 'default.log';
layout = _layout || layouts.basicLayout;
subjectLayout = layouts.messagePassThroughLayout;
sendInterval = config.sendInterval * 1000 || 0;
shutdownTimeout = ('shutdownTimeout' in config ? config.shutdownTimeout : 5) * 1000;
const sendInterval = config.sendInterval * 1000 || 0;
const shutdownTimeout = ('shutdownTimeout' in config ? config.shutdownTimeout : 5) * 1000;
const transport = mailer.createTransport(getTransportOptions());
const logEventBuffer = [];
return (loggingEvent) => {
let unsentCount = 0;
let sendTimer;
function sendBuffer() {
if (logEventBuffer.length > 0) {
const firstEvent = logEventBuffer[0];
let body = '';
const count = logEventBuffer.length;
while (logEventBuffer.length > 0) {
body += `${layout(logEventBuffer.shift(), config.timezoneOffset)}\n`;
}
const msg = {
to: config.recipients,
subject: config.subject || subjectLayout(firstEvent),
headers: { Hostname: os.hostname() }
};
if (config.attachment.enable === true) {
msg[config.html ? 'html' : 'text'] = config.attachment.message;
msg.attachments = [
{
filename: config.attachment.filename,
contentType: 'text/x-log',
content: body
}
];
} else {
msg[config.html ? 'html' : 'text'] = body;
}
if (config.sender) {
msg.from = config.sender;
}
transport.sendMail(msg, (error) => {
if (error) {
console.error('log4js.smtpAppender - Error happened', error);
}
transport.close();
unsentCount -= count;
});
}
}
function getTransportOptions() {
let options = null;
if (config.SMTP) {
options = config.SMTP;
} else if (config.transport) {
options = config.transport.options || {};
options.transport = config.transport.plugin || 'smtp';
}
return options;
}
function scheduleSend() {
if (!sendTimer) {
sendTimer = setTimeout(() => {
sendTimer = null;
sendBuffer();
}, sendInterval);
}
}
function shutdown(cb) {
if (shutdownTimeout > 0) {
setTimeout(() => {
if (sendTimer) {
clearTimeout(sendTimer);
}
sendBuffer();
}, shutdownTimeout);
}
(function checkDone() {
if (unsentCount > 0) {
setTimeout(checkDone, 100);
} else {
cb();
}
}());
}
const appender = (loggingEvent) => {
unsentCount++; // eslint-disable-line no-plusplus
logEventBuffer.push(loggingEvent);
if (sendInterval > 0) {
@ -120,37 +121,20 @@ function smtpAppender(_config, _layout) {
sendBuffer();
}
};
appender.shutdown = shutdown;
return appender;
}
function configure(_config) {
config = _config;
if (_config.layout) {
layout = layouts.layout(_config.layout.type, _config.layout);
function configure(config, layouts) {
const subjectLayout = layouts.messagePassThroughLayout;
let layout = layouts.basicLayout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
return smtpAppender(_config, layout);
return smtpAppender(config, layout, subjectLayout);
}
function shutdown(cb) {
if (shutdownTimeout > 0) {
setTimeout(() => {
if (sendTimer) {
clearTimeout(sendTimer);
}
sendBuffer();
}, shutdownTimeout);
}
(function checkDone() {
if (unsentCount > 0) {
setTimeout(checkDone, 100);
} else {
cb();
}
}());
}
module.exports.name = 'smtp';
module.exports.appender = smtpAppender;
module.exports.configure = configure;
module.exports.shutdown = shutdown;

View File

@ -1,21 +1,17 @@
'use strict';
const layouts = require('../layouts');
function stderrAppender(layout, timezoneOffset) {
layout = layout || layouts.colouredLayout;
return (loggingEvent) => {
process.stderr.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 stderrAppender(layout, config.timezoneOffset);
}
module.exports.appender = stderrAppender;
module.exports.configure = configure;

View File

@ -1,21 +1,17 @@
'use strict';
const layouts = require('../layouts');
function stdoutAppender(layout, timezoneOffset) {
layout = layout || layouts.colouredLayout;
return function (loggingEvent) {
return (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;

179
lib/configuration.js Normal file
View File

@ -0,0 +1,179 @@
'use strict';
const util = require('util');
const levels = require('./levels');
const layouts = require('./layouts');
const debug = require('debug')('log4js:configuration');
function not(thing) {
return !thing;
}
function anObject(thing) {
return thing && typeof thing === 'object' && !Array.isArray(thing);
}
function validIdentifier(thing) {
return /^[A-Za-z][A-Za-z0-9_]*$/g.test(thing);
}
function anInteger(thing) {
return 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}`
);
}
});
}
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)`
);
if (appenderModule.appender) {
debug(`DEPRECATION: Appender ${config.type} exports an appender function.`);
}
if (appenderModule.shutdown) {
debug(`DEPRECATION: Appender ${config.type} exports a shutdown function.`);
}
return appenderModule.configure(config, layouts, this.configuredAppenders.get.bind(this.configuredAppenders), this.configuredLevels);
}
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(anInteger(levelConfig[l])),
`level "${l}" must have an integer value`
);
});
}
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.appenders = candidate.appenders;
this.categories = candidate.categories;
}
}
module.exports = Configuration;

View File

@ -2,133 +2,43 @@
'use strict';
const levels = require('./levels');
const DEFAULT_FORMAT = ':remote-addr - -' +
' ":method :url HTTP/:http-version"' +
' :status :content-length ":referrer"' +
' ":user-agent"';
/**
* Log requests with the given `options` or a `format` string.
*
* Options:
*
* - `format` Format string, see below for tokens
* - `level` A log4js levels instance. Supports also 'auto'
* - `nolog` A string or RegExp to exclude target logs
*
* Tokens:
*
* - `:req[header]` ex: `:req[Accept]`
* - `:res[header]` ex: `:res[Content-Length]`
* - `:http-version`
* - `:response-time`
* - `:remote-addr`
* - `:date`
* - `:method`
* - `:url`
* - `:referrer`
* - `:user-agent`
* - `:status`
*
* @return {Function}
* @param logger4js
* @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 = {};
}
/**
* Return request url path,
* adding this function prevents the Cyclomatic Complexity,
* for the assemble_tokens function at low, to pass the tests.
*
* @param {IncomingMessage} req
* @return {String}
* @api private
*/
const thisLogger = logger4js;
let level = levels.toLevel(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.toLevel(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();
};
function getUrl(req) {
return req.originalUrl || req.url;
}
/**
* Adds custom {token, replacement} objects to defaults,
* overwriting the defaults if any tokens clash
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @param {Array} customTokens
* [{ token: string-or-regexp, replacement: string-or-replace-function }]
* @return {Array}
*/
/**
* Adds custom {token, replacement} objects to defaults,
* overwriting the defaults if any tokens clash
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @param {Array} customTokens
* [{ token: string-or-regexp, replacement: string-or-replace-function }]
* @return {Array}
*/
function assembleTokens(req, res, customTokens) {
const arrayUniqueTokens = (array) => {
const a = array.concat();
for (let i = 0; i < a.length; ++i) {
for (let j = i + 1; j < a.length; ++j) {
// not === because token can be regexp object
/* eslint eqeqeq:0 */
// not === because token can be regexp object
/* eslint eqeqeq:0 */
if (a[i].token == a[j].token) {
a.splice(j--, 1);
}
@ -156,20 +66,20 @@ function assembleTokens(req, res, customTokens) {
defaultTokens.push({
token: ':remote-addr',
replacement: req.headers['x-forwarded-for'] ||
req.ip ||
req._remoteAddress ||
(req.socket &&
(req.socket.remoteAddress ||
(req.socket.socket && req.socket.socket.remoteAddress)
req.ip ||
req._remoteAddress ||
(req.socket &&
(req.socket.remoteAddress ||
(req.socket.socket && req.socket.socket.remoteAddress)
)
)
)
});
defaultTokens.push({ token: ':user-agent', replacement: req.headers['user-agent'] });
defaultTokens.push({
token: ':content-length',
replacement: (res._headers && res._headers['content-length']) ||
(res.__headers && res.__headers['Content-Length']) ||
'-'
(res.__headers && res.__headers['Content-Length']) ||
'-'
});
defaultTokens.push({
token: /:req\[([^\]]+)]/g,
@ -181,36 +91,22 @@ function assembleTokens(req, res, customTokens) {
token: /:res\[([^\]]+)]/g,
replacement: function (_, field) {
return res._headers ?
(res._headers[field.toLowerCase()] || res.__headers[field])
: (res.__headers && res.__headers[field]);
(res._headers[field.toLowerCase()] || res.__headers[field])
: (res.__headers && res.__headers[field]);
}
});
return arrayUniqueTokens(customTokens.concat(defaultTokens));
}
/**
* Return request url path,
* adding this function prevents the Cyclomatic Complexity,
* for the assemble_tokens function at low, to pass the tests.
*
* @param {IncomingMessage} req
* @return {String}
* @api private
*/
function getUrl(req) {
return req.originalUrl || req.url;
}
/**
* Return formatted log line.
*
* @param {String} str
* @param {Array} tokens
* @return {String}
* @api private
*/
/**
* Return formatted log line.
*
* @param {String} str
* @param {Array} tokens
* @return {String}
* @api private
*/
function format(str, tokens) {
for (let i = 0; i < tokens.length; i++) {
str = str.replace(tokens[i].token, tokens[i].replacement);
@ -218,33 +114,33 @@ function format(str, tokens) {
return str;
}
/**
* Return RegExp Object about nolog
*
* @param {String|Array} nolog
* @return {RegExp}
* @api private
*
* syntax
* 1. String
* 1.1 "\\.gif"
* NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.gif?fuga
* LOGGING http://example.com/hoge.agif
* 1.2 in "\\.gif|\\.jpg$"
* NOT LOGGING http://example.com/hoge.gif and
* http://example.com/hoge.gif?fuga and http://example.com/hoge.jpg?fuga
* LOGGING http://example.com/hoge.agif,
* http://example.com/hoge.ajpg and http://example.com/hoge.jpg?hoge
* 1.3 in "\\.(gif|jpe?g|png)$"
* NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.jpeg
* LOGGING http://example.com/hoge.gif?uid=2 and http://example.com/hoge.jpg?pid=3
* 2. RegExp
* 2.1 in /\.(gif|jpe?g|png)$/
* SAME AS 1.3
* 3. Array
* 3.1 ["\\.jpg$", "\\.png", "\\.gif"]
* SAME AS "\\.jpg|\\.png|\\.gif"
*/
/**
* Return RegExp Object about nolog
*
* @param {String|Array} nolog
* @return {RegExp}
* @api private
*
* syntax
* 1. String
* 1.1 "\\.gif"
* NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.gif?fuga
* LOGGING http://example.com/hoge.agif
* 1.2 in "\\.gif|\\.jpg$"
* NOT LOGGING http://example.com/hoge.gif and
* http://example.com/hoge.gif?fuga and http://example.com/hoge.jpg?fuga
* LOGGING http://example.com/hoge.agif,
* http://example.com/hoge.ajpg and http://example.com/hoge.jpg?hoge
* 1.3 in "\\.(gif|jpe?g|png)$"
* NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.jpeg
* LOGGING http://example.com/hoge.gif?uid=2 and http://example.com/hoge.jpg?pid=3
* 2. RegExp
* 2.1 in /\.(gif|jpe?g|png)$/
* SAME AS 1.3
* 3. Array
* 3.1 ["\\.jpg$", "\\.png", "\\.gif"]
* SAME AS "\\.jpg|\\.png|\\.gif"
*/
function createNoLogCondition(nolog) {
let regexp = null;
@ -258,7 +154,7 @@ function createNoLogCondition(nolog) {
}
if (Array.isArray(nolog)) {
// convert to strings
// convert to strings
const regexpsAsStrings = nolog.map(reg => (reg.source ? reg.source : reg));
regexp = new RegExp(regexpsAsStrings.join('|'));
}
@ -267,4 +163,109 @@ function createNoLogCondition(nolog) {
return regexp;
}
module.exports.connectLogger = getLogger;
module.exports = function (levels) {
/**
* Log requests with the given `options` or a `format` string.
*
* Options:
*
* - `format` Format string, see below for tokens
* - `level` A log4js levels instance. Supports also 'auto'
* - `nolog` A string or RegExp to exclude target logs
*
* Tokens:
*
* - `:req[header]` ex: `:req[Accept]`
* - `:res[header]` ex: `:res[Content-Length]`
* - `:http-version`
* - `:response-time`
* - `:remote-addr`
* - `:date`
* - `:method`
* - `:url`
* - `:referrer`
* - `:user-agent`
* - `:status`
*
* @return {Function}
* @param logger4js
* @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();
};
}
return { connectLogger: getLogger };
};

View File

@ -1,85 +1,87 @@
'use strict';
/**
* @name Level
* @namespace Log4js
*/
class Level {
constructor(level, levelStr) {
this.level = level;
this.levelStr = levelStr;
}
toString() {
return this.levelStr;
}
isLessThanOrEqualTo(otherLevel) {
if (typeof otherLevel === 'string') {
otherLevel = toLevel(otherLevel);
module.exports = function (customLevels) {
/**
* @name Level
* @namespace Log4js
*/
class Level {
constructor(level, levelStr) {
this.level = level;
this.levelStr = levelStr;
}
return this.level <= otherLevel.level;
}
isGreaterThanOrEqualTo(otherLevel) {
if (typeof otherLevel === 'string') {
otherLevel = toLevel(otherLevel);
toString() {
return this.levelStr;
}
return this.level >= otherLevel.level;
}
isEqualTo(otherLevel) {
if (typeof otherLevel === 'string') {
otherLevel = toLevel(otherLevel);
isLessThanOrEqualTo(otherLevel) {
if (typeof otherLevel === 'string') {
otherLevel = getLevel(otherLevel);
}
return this.level <= otherLevel.level;
}
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;
}
}
}
const defaultLevels = {
ALL: new Level(Number.MIN_VALUE, 'ALL'),
TRACE: new Level(5000, 'TRACE'),
DEBUG: new Level(10000, 'DEBUG'),
INFO: new Level(20000, 'INFO'),
WARN: new Level(30000, 'WARN'),
ERROR: new Level(40000, 'ERROR'),
FATAL: new Level(50000, 'FATAL'),
MARK: new Level(9007199254740992, 'MARK'), // 2^53
OFF: new Level(Number.MAX_VALUE, 'OFF')
};
/**
* converts given String to corresponding Level
* @param {Level|String} sArg -- String value of Level OR Log4js.Level
* @param {Level} [defaultLevel] -- default Level, if no String representation
* @return {Level}
*/
function toLevel(sArg, defaultLevel) {
if (!sArg) {
return defaultLevel;
if (customLevels) {
const levels = Object.keys(customLevels);
levels.forEach((l) => {
defaultLevels[l.toUpperCase()] = new Level(customLevels[l], l.toUpperCase());
});
}
if (sArg instanceof Level) {
module.exports[sArg.toString()] = sArg;
return sArg;
/**
* converts given String to corresponding Level
* @param {Level|String} sArg -- String value of Level OR Log4js.Level
* @param {Level} [defaultLevel] -- default Level, if no String representation
* @return {Level}
*/
function getLevel(sArg, defaultLevel) {
if (!sArg) {
return defaultLevel;
}
if (sArg instanceof Level) {
return sArg;
}
if (typeof sArg === 'string') {
return defaultLevels[sArg.toUpperCase()] || defaultLevel;
}
return getLevel(sArg.toString());
}
if (typeof sArg === 'string') {
return module.exports[sArg.toUpperCase()] || defaultLevel;
}
const orderedLevels = Object.keys(defaultLevels).sort((a, b) => b.level - a.level);
defaultLevels.getLevel = getLevel;
defaultLevels.levels = orderedLevels;
return toLevel(sArg.toString());
}
function getLevel(levelStr) {
let level;
if (typeof levelStr === 'string') {
const levelUpper = levelStr.toUpperCase();
level = toLevel(levelUpper);
}
return level;
}
module.exports = {
ALL: new Level(Number.MIN_VALUE, 'ALL'),
TRACE: new Level(5000, 'TRACE'),
DEBUG: new Level(10000, 'DEBUG'),
INFO: new Level(20000, 'INFO'),
WARN: new Level(30000, 'WARN'),
ERROR: new Level(40000, 'ERROR'),
FATAL: new Level(50000, 'FATAL'),
MARK: new Level(9007199254740992, 'MARK'), // 2^53
OFF: new Level(Number.MAX_VALUE, 'OFF'),
toLevel: toLevel,
Level: Level,
getLevel: getLevel
return defaultLevels;
};

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' );
@ -32,413 +22,83 @@
* @static
* Website: http://log4js.berlios.de
*/
const debug = require('debug')('log4js:main');
const fs = require('fs');
const util = require('util');
const layouts = require('./layouts');
const levels = require('./levels');
const loggerModule = require('./logger');
const connectLogger = require('./connect-logger').connectLogger;
const Configuration = require('./configuration');
const connectModule = require('./connect-logger');
const logger = require('./logger');
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 Logger;
let config;
let connectLogger;
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(sendLogEventToAppender, cat, levelForCategory(cat));
}
/**
* 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) {
debug(`Loading configuration from ${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);
debug(`Configuration is ${configObject}`);
config = new Configuration(configObject);
module.exports.levels = config.levels;
Logger = logger(config.levels).Logger;
connectLogger = connectModule(config.levels).connectLogger;
enabled = true;
}
/**
@ -450,41 +110,34 @@ function loadAppender(appender, appenderModule) {
* as the first argument.
*/
function shutdown(cb) {
debug('Shutdown called. Disabling all log writing.');
// 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 = [];
debug(`Found ${shutdownFunctions} appenders with shutdown functions.`);
function complete(err) {
error = error || err;
completed++;
if (completed >= shutdownFunctions.length) {
completed += 1;
debug(`Appender shutdowns complete: ${completed} / ${shutdownFunctions}`);
if (completed >= shutdownFunctions) {
debug('All shutdown functions completed.');
cb(error);
}
}
for (const category in appenderShutdowns) {
if (appenderShutdowns.hasOwnProperty(category)) {
shutdownFunctions.push(appenderShutdowns[category]);
}
}
if (!shutdownFunctions.length) {
if (shutdownFunctions === 0) {
debug('No appenders with shutdown functions found.');
return cb();
}
shutdownFunctions.forEach((shutdownFct) => {
shutdownFct(complete);
});
appenders.filter(a => a.shutdown).forEach(a => a.shutdown(complete));
return null;
}
@ -492,49 +145,19 @@ 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

@ -2,12 +2,7 @@
'use strict';
const levels = require('./levels');
const EventEmitter = require('events');
const DEFAULT_CATEGORY = '[default]';
let logWritesEnabled = true;
const debug = require('debug')('log4js:logger');
/**
* @name LoggingEvent
@ -20,113 +15,104 @@ 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, context) {
this.startTime = new Date();
this.categoryName = categoryName;
this.data = data;
this.level = level;
this.logger = logger;
this.context = Object.assign({}, context);
}
}
/**
* 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
*
* @author Stephan Strittmatter
*/
class Logger extends EventEmitter {
constructor(name, level) {
super();
module.exports = function (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, level) {
if (typeof dispatch !== 'function') {
throw new Error('No dispatch function provided.');
}
this.category = name;
this.level = levels.getLevel(level, levels.TRACE);
this.dispatch = dispatch;
this.context = {};
debug(`Logger created (${name}, ${level})`);
}
this.category = name || DEFAULT_CATEGORY;
setLevel(level) {
this.level = levels.getLevel(level, this.level || levels.TRACE);
}
if (level) {
this.setLevel(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}, ${data}) 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 = {};
}
}
setLevel(level) {
this.level = levels.toLevel(level, this.level || levels.TRACE);
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.toString());
};
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);
}
};
}
removeLevel() {
delete this.level;
}
levels.levels.forEach(addLevelMethods);
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;
}
this._log(logLevel, args.slice(1));
}
isLevelEnabled(otherLevel) {
return this.level.isLessThanOrEqualTo(otherLevel);
}
_log(level, data) {
const loggingEvent = new LoggingEvent(this.category, level, data, this);
this.emit('log', 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);
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.toString());
return {
LoggingEvent: LoggingEvent,
Logger: Logger
};
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 (logWritesEnabled && this.isLevelEnabled(level)) {
this._log(level, args);
}
};
}
/**
* Disable all log writes.
* @returns {void}
*/
function disableAllLogWrites() {
logWritesEnabled = false;
}
/**
* Enable log writes.
* @returns {void}
*/
function enableAllLogWrites() {
logWritesEnabled = true;
}
module.exports.LoggingEvent = LoggingEvent;
module.exports.Logger = Logger;
module.exports.disableAllLogWrites = disableAllLogWrites;
module.exports.enableAllLogWrites = enableAllLogWrites;
module.exports.addLevelMethods = addLevelMethods;
};

View File

@ -40,7 +40,7 @@
"date-format": "^1.0.0",
"debug": "^2.2.0",
"semver": "^5.3.0",
"streamroller": "^0.3.0"
"streamroller": "^0.4.0"
},
"devDependencies": {
"codecov": "^1.0.1",

View File

@ -1,78 +1,62 @@
'use strict';
const test = require('tap').test;
const fs = require('fs');
const EOL = require('os').EOL || '\n';
const log4js = require('../../lib/log4js');
function remove(filename) {
try {
fs.unlinkSync(filename);
} catch (e) {
// doesn't really matter if it failed
}
}
function cleanup(done) {
remove(`${__dirname}/categoryFilter-web.log`);
remove(`${__dirname}/categoryFilter-noweb.log`);
done();
}
const recording = require('../../lib/appenders/recording');
test('log4js categoryFilter', (batch) => {
batch.beforeEach(cleanup);
batch.beforeEach((done) => { recording.reset(); done(); });
batch.test('appender should exclude categories', (t) => {
const logEvents = [];
const appender = require(
'../../lib/appenders/categoryFilter'
).appender(
['app'],
(evt) => {
logEvents.push(evt);
}
);
log4js.clearAppenders();
log4js.addAppender(appender, ['app', 'web']);
log4js.configure({
appenders: {
recorder: { type: 'recording' },
filtered: {
type: 'categoryFilter',
exclude: 'web',
appender: 'recorder'
}
},
categories: { default: { appenders: ['filtered'], level: 'DEBUG' } }
});
const webLogger = log4js.getLogger('web');
const appLogger = log4js.getLogger('app');
webLogger.debug('This should get logged');
appLogger.debug('This should not');
webLogger.debug('This should not get logged');
appLogger.debug('This should get logged');
webLogger.debug('Hello again');
log4js.getLogger('db').debug('This shouldn\'t be included by the appender anyway');
log4js.getLogger('db').debug('This should be included by the appender anyway');
const logEvents = recording.replay();
t.equal(logEvents.length, 2);
t.equal(logEvents[0].data[0], 'This should get logged');
t.equal(logEvents[1].data[0], 'Hello again');
t.equal(logEvents[1].data[0], 'This should be included by the appender anyway');
t.end();
});
batch.test('should work with configuration file', (t) => {
log4js.configure('test/tap/with-categoryFilter.json');
const logger = log4js.getLogger('app');
const weblogger = log4js.getLogger('web');
batch.test('should not really need a category filter any more', (t) => {
log4js.configure({
appenders: { recorder: { type: 'recording' } },
categories: {
default: { appenders: ['recorder'], level: 'DEBUG' },
web: { appenders: ['recorder'], level: 'OFF' }
}
});
const appLogger = log4js.getLogger('app');
const webLogger = log4js.getLogger('web');
logger.info('Loading app');
logger.info('Initialising indexes');
weblogger.info('00:00:00 GET / 200');
weblogger.warn('00:00:00 GET / 500');
webLogger.debug('This should not get logged');
appLogger.debug('This should get logged');
webLogger.debug('Hello again');
log4js.getLogger('db').debug('This should be included by the appender anyway');
setTimeout(() => {
fs.readFile(`${__dirname}/categoryFilter-noweb.log`, 'utf8', (err, contents) => {
const noWebMessages = contents.trim().split(EOL);
t.same(noWebMessages, ['Loading app', 'Initialising indexes']);
fs.readFile(`${__dirname}/categoryFilter-web.log`, 'utf8', (e, c) => {
const messages = c.trim().split(EOL);
t.same(messages, ['00:00:00 GET / 200', '00:00:00 GET / 500']);
t.end();
});
});
}, 500);
const logEvents = recording.replay();
t.equal(logEvents.length, 2);
t.equal(logEvents[0].data[0], 'This should get logged');
t.equal(logEvents[1].data[0], 'This should be included by the appender anyway');
t.end();
});
batch.afterEach(cleanup);
batch.end();
});

View File

@ -2,7 +2,7 @@
const test = require('tap').test;
const sandbox = require('sandboxed-module');
const LoggingEvent = require('../../lib/logger').LoggingEvent;
const LoggingEvent = require('../../lib/logger')(require('../../lib/levels')()).LoggingEvent;
test('log4js cluster appender', (batch) => {
batch.test('when in master mode', (t) => {

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

@ -1,38 +0,0 @@
'use strict';
// This test shows unexpected behaviour for log4js.configure() in log4js-node@0.4.3 and earlier:
// 1) log4js.configure(), log4js.configure(null),
// log4js.configure({}), log4js.configure(<some object with no levels prop>)
// all set all loggers levels to trace, even if they were previously set to something else.
// 2) log4js.configure({levels:{}}), log4js.configure({levels: {foo:
// bar}}) leaves previously set logger levels intact.
//
const test = require('tap').test;
// setup the configurations we want to test
const configs = [
undefined,
null,
{},
{ foo: 'bar' },
{ levels: null },
{ levels: {} },
{ levels: { foo: 'bar' } },
{ levels: { A: 'INFO' } }
];
test('log4js dodgy config', (batch) => {
const log4js = require('../../lib/log4js');
const logger = log4js.getLogger('test-logger');
const error = log4js.levels.ERROR;
logger.setLevel('ERROR');
configs.forEach((config) => {
batch.test(`config of ${config} should not change logger level`, (t) => {
log4js.configure(config);
t.equal(logger.level, error);
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');
const clm = require('../../lib/connect-logger')(levels);
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');
const clm = require('../../lib/connect-logger')(levels);
batch.test('with nolog config', (t) => {
const ml = new MockLogger();

View File

@ -1,7 +1,6 @@
'use strict';
const test = require('tap').test;
const layouts = require('../../lib/layouts');
const sandbox = require('sandboxed-module');
test('log4js console appender', (batch) => {
@ -12,17 +11,20 @@ test('log4js console appender', (batch) => {
messages.push(msg);
}
};
const appenderModule = sandbox.require(
'../../lib/appenders/console',
const log4js = sandbox.require(
'../../lib/log4js',
{
globals: {
console: fakeConsole
}
}
);
log4js.configure({
appenders: { console: { type: 'console', layout: { type: 'messagePassThrough' } } },
categories: { default: { appenders: ['console'], level: 'DEBUG' } }
});
const appender = appenderModule.appender(layouts.messagePassThroughLayout);
appender({ data: ['blah'] });
log4js.getLogger().info('blah');
t.equal(messages[0], 'blah');
t.end();

View File

@ -3,83 +3,26 @@
const test = require('tap').test;
const path = require('path');
const fs = require('fs');
const sandbox = require('sandboxed-module');
const log4js = require('../../lib/log4js');
const EOL = require('os').EOL || '\n';
function removeFile(filename) {
try {
fs.unlinkSync(path.join(__dirname, filename));
} catch (e) {}
} catch (e) {
// doesn't matter
}
}
test('../../lib/appenders/dateFile', (batch) => {
batch.test('adding multiple dateFileAppenders', (t) => {
const listenersCount = process.listeners('exit').length;
const dateFileAppender = require('../../lib/appenders/dateFile');
let count = 5;
let logfile;
while (count--) {
logfile = path.join(__dirname, `datefa-default-test${count}.log`);
log4js.addAppender(dateFileAppender.appender(logfile));
}
t.teardown(() => {
removeFile('datefa-default-test0.log');
removeFile('datefa-default-test1.log');
removeFile('datefa-default-test2.log');
removeFile('datefa-default-test3.log');
removeFile('datefa-default-test4.log');
});
t.equal(process.listeners('exit').length, listenersCount + 1, 'should only add one exit listener');
t.end();
});
batch.test('exit listener', (t) => {
let exitListener;
const openedFiles = [];
const dateFileAppender = sandbox.require(
'../../lib/appenders/dateFile',
{
globals: {
process: {
on: function (evt, listener) {
exitListener = listener;
}
}
},
requires: {
streamroller: {
DateRollingFileStream: function (filename) {
openedFiles.push(filename);
this.end = function () {
openedFiles.shift();
};
}
}
}
}
);
for (let i = 0; i < 5; i += 1) {
dateFileAppender.appender(`test${i}`);
}
t.equal(openedFiles.length, 5);
exitListener();
t.equal(openedFiles.length, 0, 'should close all opened files');
t.end();
});
batch.test('with default settings', (t) => {
const testFile = path.join(__dirname, 'date-appender-default.log');
const appender = require('../../lib/appenders/dateFile').appender(testFile);
log4js.configure({
appenders: { date: { type: 'dateFile', filename: testFile } },
categories: { default: { appenders: ['date'], level: 'DEBUG' } }
});
const logger = log4js.getLogger('default-settings');
log4js.clearAppenders();
log4js.addAppender(appender, 'default-settings');
logger.info('This should be in the file.');
t.teardown(() => { removeFile('date-appender-default.log'); });
@ -97,9 +40,17 @@ test('../../lib/appenders/dateFile', (batch) => {
});
batch.test('configure with dateFileAppender', (t) => {
// this config file defines one file appender (to ./date-file-test.log)
// and sets the log level for "tests" to WARN
log4js.configure('test/tap/with-dateFile.json');
log4js.configure({
appenders: {
date: {
type: 'dateFile',
filename: 'test/tap/date-file-test.log',
pattern: '-from-MM-dd',
layout: { type: 'messagePassThrough' }
}
},
categories: { default: { appenders: ['date'], level: 'WARN' } }
});
const logger = log4js.getLogger('tests');
logger.info('this should not be written to the file');
logger.warn('this should be written to the file');
@ -117,8 +68,8 @@ test('../../lib/appenders/dateFile', (batch) => {
const format = require('date-format');
const options = {
appenders: [
{
appenders: {
date: {
category: 'tests',
type: 'dateFile',
filename: 'test/tap/date-file-test',
@ -128,16 +79,16 @@ test('../../lib/appenders/dateFile', (batch) => {
type: 'messagePassThrough'
}
}
]
},
categories: { default: { appenders: ['date'], level: 'debug' } }
};
const thisTime = format.asString(options.appenders[0].pattern, new Date());
const thisTime = format.asString(options.appenders.date.pattern, new Date());
fs.writeFileSync(
path.join(__dirname, `date-file-test${thisTime}`),
`this is existing data${EOL}`,
'utf8'
);
log4js.clearAppenders();
log4js.configure(options);
const logger = log4js.getLogger('tests');
logger.warn('this should be written to the file with the appended date');
@ -154,49 +105,14 @@ test('../../lib/appenders/dateFile', (batch) => {
}, 100);
});
batch.test('configure with cwd option', (t) => {
let fileOpened;
const appender = sandbox.require(
'../../lib/appenders/dateFile',
{
requires: {
streamroller: {
DateRollingFileStream: function (file) {
fileOpened = file;
return {
on: function () {
},
end: function () {
}
};
}
}
}
}
);
appender.configure(
{
filename: 'whatever.log',
maxLogSize: 10
},
{ cwd: '/absolute/path/to' }
);
const expected = path.sep + path.join('absolute', 'path', 'to', 'whatever.log');
t.equal(fileOpened, expected, 'should prepend options.cwd to config.filename');
t.end();
});
batch.test('should flush logs on shutdown', (t) => {
const testFile = path.join(__dirname, 'date-appender-default.log');
const appender = require('../../lib/appenders/dateFile').appender(testFile);
log4js.configure({
appenders: { test: { type: 'dateFile', filename: testFile } },
categories: { default: { appenders: ['test'], level: 'trace' } }
});
const logger = log4js.getLogger('default-settings');
log4js.clearAppenders();
log4js.addAppender(appender, 'default-settings');
logger.info('1');
logger.info('2');
logger.info('3');

View File

@ -29,11 +29,15 @@ test('file appender SIGHUP', (t) => {
this.end = function () {
};
this.write = function () {
return true;
};
}
}
}
}
).appender('sighup-test-file');
).configure({ type: 'file', filename: 'sighup-test-file' }, { basicLayout: function () {} });
process.kill(process.pid, 'SIGHUP');
t.plan(2);

View File

@ -8,9 +8,7 @@ const log4js = require('../../lib/log4js');
const zlib = require('zlib');
const EOL = require('os').EOL || '\n';
log4js.clearAppenders();
function remove(filename) {
function removeFile(filename) {
try {
fs.unlinkSync(filename);
} catch (e) {
@ -19,76 +17,17 @@ function remove(filename) {
}
test('log4js fileAppender', (batch) => {
batch.test('adding multiple fileAppenders', (t) => {
const initialCount = process.listeners('exit').length;
let count = 5;
let logfile;
while (count--) {
logfile = path.join(__dirname, `fa-default-test${count}.log`);
log4js.addAppender(
require('../../lib/appenders/file').appender(logfile),
'default-settings'
);
}
t.equal(initialCount + 1, process.listeners('exit').length, 'should not add more than one exit listener');
t.end();
});
batch.test('exit listener', (t) => {
let exitListener;
const openedFiles = [];
const fileAppender = sandbox.require(
'../../lib/appenders/file',
{
globals: {
process: {
on: function (evt, listener) {
if (evt === 'exit') {
exitListener = listener;
}
}
}
},
singleOnly: true,
requires: {
streamroller: {
RollingFileStream: function (filename) {
openedFiles.push(filename);
this.end = function () {
openedFiles.shift();
};
this.on = function () {
};
}
}
}
}
);
for (let i = 0; i < 5; i += 1) {
fileAppender.appender(`test${i}`, null, 100);
}
t.ok(openedFiles);
exitListener();
t.equal(openedFiles.length, 0, 'should close all open files');
t.end();
});
batch.test('with default fileAppender settings', (t) => {
const testFile = path.join(__dirname, 'fa-default-test.log');
const logger = log4js.getLogger('default-settings');
remove(testFile);
removeFile(testFile);
log4js.clearAppenders();
log4js.addAppender(
require('../../lib/appenders/file').appender(testFile),
'default-settings'
);
t.tearDown(() => { removeFile(testFile); });
log4js.configure({
appenders: { file: { type: 'file', filename: testFile } },
categories: { default: { appenders: ['file'], level: 'debug' } }
});
logger.info('This should be in the file.');
@ -106,16 +45,13 @@ test('log4js fileAppender', (batch) => {
batch.test('should flush logs on shutdown', (t) => {
const testFile = path.join(__dirname, 'fa-default-test.log');
const logger = log4js.getLogger('default-settings');
remove(testFile);
removeFile(testFile);
log4js.clearAppenders();
const fileAppender = require('../../lib/appenders/file');
log4js.addAppender(
fileAppender.appender(testFile),
fileAppender.shutdown,
'default-settings'
);
log4js.configure({
appenders: { test: { type: 'file', filename: testFile } },
categories: { default: { appenders: ['test'], level: 'trace' } }
});
const logger = log4js.getLogger('default-settings');
logger.info('1');
logger.info('2');
@ -134,86 +70,25 @@ test('log4js fileAppender', (batch) => {
});
});
batch.test('fileAppender subcategories', (t) => {
log4js.clearAppenders();
function addAppender(cat) {
const testFile = path.join(
__dirname,
`fa-subcategories-test-${cat.join('-').replace(/\./g, '_')}.log`
);
remove(testFile);
log4js.addAppender(require('../../lib/appenders/file').appender(testFile), cat);
return testFile;
}
/* eslint-disable camelcase */
const file_sub1 = addAppender(['sub1']);
const file_sub1_sub12$sub1_sub13 = addAppender(['sub1.sub12', 'sub1.sub13']);
const file_sub1_sub12 = addAppender(['sub1.sub12']);
const logger_sub1_sub12_sub123 = log4js.getLogger('sub1.sub12.sub123');
const logger_sub1_sub13_sub133 = log4js.getLogger('sub1.sub13.sub133');
const logger_sub1_sub14 = log4js.getLogger('sub1.sub14');
const logger_sub2 = log4js.getLogger('sub2');
logger_sub1_sub12_sub123.info('sub1_sub12_sub123');
logger_sub1_sub13_sub133.info('sub1_sub13_sub133');
logger_sub1_sub14.info('sub1_sub14');
logger_sub2.info('sub2');
setTimeout(() => {
t.test('file contents', (assert) => {
const fileContents = {
file_sub1: fs.readFileSync(file_sub1).toString(),
file_sub1_sub12$sub1_sub13: fs.readFileSync(file_sub1_sub12$sub1_sub13).toString(),
file_sub1_sub12: fs.readFileSync(file_sub1_sub12).toString()
};
// everything but category 'sub2'
assert.match(
fileContents.file_sub1,
/^(\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}] \[INFO] (sub1.sub12.sub123 - sub1_sub12_sub123|sub1.sub13.sub133 - sub1_sub13_sub133|sub1.sub14 - sub1_sub14)[\s\S]){3}$/ // eslint-disable-line
);
assert.ok(
fileContents.file_sub1.match(/sub123/) &&
fileContents.file_sub1.match(/sub133/) &&
fileContents.file_sub1.match(/sub14/)
);
assert.ok(!fileContents.file_sub1.match(/sub2/));
// only catgories starting with 'sub1.sub12' and 'sub1.sub13'
assert.match(
fileContents.file_sub1_sub12$sub1_sub13,
/^(\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}] \[INFO] (sub1.sub12.sub123 - sub1_sub12_sub123|sub1.sub13.sub133 - sub1_sub13_sub133)[\s\S]){2}$/ // eslint-disable-line
);
assert.ok(
fileContents.file_sub1_sub12$sub1_sub13.match(/sub123/) &&
fileContents.file_sub1_sub12$sub1_sub13.match(/sub133/)
);
assert.ok(!fileContents.file_sub1_sub12$sub1_sub13.match(/sub14|sub2/));
// only catgories starting with 'sub1.sub12'
assert.match(
fileContents.file_sub1_sub12,
/^(\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}] \[INFO] (sub1.sub12.sub123 - sub1_sub12_sub123)[\s\S]){1}$/ // eslint-disable-line
);
assert.ok(!fileContents.file_sub1_sub12.match(/sub14|sub2|sub13/));
assert.end();
});
t.end();
}, 3000);
});
batch.test('with a max file size and no backups', (t) => {
const testFile = path.join(__dirname, 'fa-maxFileSize-test.log');
const logger = log4js.getLogger('max-file-size');
remove(testFile);
remove(`${testFile}.1`);
t.tearDown(() => {
removeFile(testFile);
removeFile(`${testFile}.1`);
});
removeFile(testFile);
removeFile(`${testFile}.1`);
// log file of 100 bytes maximum, no backups
log4js.clearAppenders();
log4js.addAppender(
require('../../lib/appenders/file').appender(testFile, log4js.layouts.basicLayout, 100, 0),
'max-file-size'
);
log4js.configure({
appenders: {
file: { type: 'file', filename: testFile, maxLogSize: 100, backups: 0 }
},
categories: { default: { appenders: ['file'], level: 'debug' } }
});
logger.info('This is the first log message.');
logger.info('This is an intermediate log message.');
logger.info('This is the second log message.');
@ -236,16 +111,24 @@ test('log4js fileAppender', (batch) => {
batch.test('with a max file size and 2 backups', (t) => {
const testFile = path.join(__dirname, 'fa-maxFileSize-with-backups-test.log');
const logger = log4js.getLogger('max-file-size-backups');
remove(testFile);
remove(`${testFile}.1`);
remove(`${testFile}.2`);
removeFile(testFile);
removeFile(`${testFile}.1`);
removeFile(`${testFile}.2`);
t.tearDown(() => {
removeFile(testFile);
removeFile(`${testFile}.1`);
removeFile(`${testFile}.2`);
});
// log file of 50 bytes maximum, 2 backups
log4js.clearAppenders();
log4js.addAppender(
require('../../lib/appenders/file').appender(testFile, log4js.layouts.basicLayout, 50, 2),
'max-file-size-backups'
);
log4js.configure({
appenders: {
file: { type: 'file', filename: testFile, maxLogSize: 50, backups: 2 }
},
categories: { default: { appenders: ['file'], level: 'debug' } }
});
logger.info('This is the first log message.');
logger.info('This is the second log message.');
logger.info('This is the third log message.');
@ -288,18 +171,23 @@ test('log4js fileAppender', (batch) => {
batch.test('with a max file size and 2 compressed backups', (t) => {
const testFile = path.join(__dirname, 'fa-maxFileSize-with-backups-compressed-test.log');
const logger = log4js.getLogger('max-file-size-backups');
remove(testFile);
remove(`${testFile}.1.gz`);
remove(`${testFile}.2.gz`);
removeFile(testFile);
removeFile(`${testFile}.1.gz`);
removeFile(`${testFile}.2.gz`);
t.tearDown(() => {
removeFile(testFile);
removeFile(`${testFile}.1.gz`);
removeFile(`${testFile}.2.gz`);
});
// log file of 50 bytes maximum, 2 backups
log4js.clearAppenders();
log4js.addAppender(
require('../../lib/appenders/file').appender(
testFile, log4js.layouts.basicLayout, 50, 2, { compress: true }
),
'max-file-size-backups'
);
log4js.configure({
appenders: {
file: { type: 'file', filename: testFile, maxLogSize: 50, backups: 2, compress: true }
},
categories: { default: { appenders: ['file'], level: 'debug' } }
});
logger.info('This is the first log message.');
logger.info('This is the second log message.');
logger.info('This is the third log message.');
@ -339,24 +227,6 @@ test('log4js fileAppender', (batch) => {
}, 1000);
});
batch.test('configure with fileAppender', (t) => {
// this config file defines one file appender (to ./tmp-tests.log)
// and sets the log level for "tests" to WARN
log4js.configure('./test/tap/log4js.json');
const logger = log4js.getLogger('tests');
logger.info('this should not be written to the file');
logger.warn('this should be written to the file');
// wait for the file system to catch up
setTimeout(() => {
fs.readFile('tmp-tests.log', 'utf8', (err, contents) => {
t.include(contents, `this should be written to the file${EOL}`);
t.equal(contents.indexOf('this should not be written to the file'), -1);
t.end();
});
}, 100);
});
batch.test('when underlying stream errors', (t) => {
let consoleArgs;
let errorHandler;
@ -381,13 +251,16 @@ test('log4js fileAppender', (batch) => {
errorHandler = cb;
}
};
this.write = function () {
return true;
};
}
}
}
}
);
fileAppender.appender('test1.log', null, 100);
fileAppender.configure({ filename: 'test1.log', maxLogSize: 100 }, { basicLayout: function () {} });
errorHandler({ error: 'aargh' });
t.test('should log the error to console.error', (assert) => {

View File

@ -6,8 +6,6 @@ const path = require('path');
const log4js = require('../../lib/log4js');
const EOL = require('os').EOL || '\n';
log4js.clearAppenders();
function remove(filename) {
try {
fs.unlinkSync(filename);
@ -22,11 +20,14 @@ test('log4js fileSyncAppender', (batch) => {
const logger = log4js.getLogger('default-settings');
remove(testFile);
log4js.clearAppenders();
log4js.addAppender(
require('../../lib/appenders/fileSync').appender(testFile),
'default-settings'
);
t.tearDown(() => {
remove(testFile);
});
log4js.configure({
appenders: { sync: { type: 'fileSync', filename: testFile } },
categories: { default: { appenders: ['sync'], level: 'debug' } }
});
logger.info('This should be in the file.');
@ -43,21 +44,20 @@ test('log4js fileSyncAppender', (batch) => {
batch.test('with a max file size and no backups', (t) => {
const testFile = path.join(__dirname, '/fa-maxFileSize-sync-test.log');
const logger = log4js.getLogger('max-file-size');
remove(testFile);
remove(`${testFile}.1`);
t.tearDown(() => {
remove(testFile);
remove(`${testFile}.1`);
});
// log file of 100 bytes maximum, no backups
log4js.clearAppenders();
log4js.addAppender(
require(
'../../lib/appenders/fileSync'
).appender(
testFile,
log4js.layouts.basicLayout,
100,
0
),
'max-file-size'
);
log4js.configure({
appenders: { sync: { type: 'fileSync', filename: testFile, maxLogSize: 100, backups: 0 } },
categories: { default: { appenders: ['sync'], level: 'debug' } }
});
logger.info('This is the first log message.');
logger.info('This is an intermediate log message.');
logger.info('This is the second log message.');
@ -89,17 +89,17 @@ test('log4js fileSyncAppender', (batch) => {
remove(`${testFile}.1`);
remove(`${testFile}.2`);
t.tearDown(() => {
remove(testFile);
remove(`${testFile}.1`);
remove(`${testFile}.2`);
});
// log file of 50 bytes maximum, 2 backups
log4js.clearAppenders();
log4js.addAppender(
require('../../lib/appenders/fileSync').appender(
testFile,
log4js.layouts.basicLayout,
50,
2
),
'max-file-size-backups'
);
log4js.configure({
appenders: { sync: { type: 'fileSync', filename: testFile, maxLogSize: 50, backups: 2 } },
categories: { default: { appenders: ['sync'], level: 'debug' } }
});
logger.info('This is the first log message.');
logger.info('This is the second log message.');
logger.info('This is the third log message.');
@ -136,16 +136,16 @@ test('log4js fileSyncAppender', (batch) => {
// this config defines one file appender (to ./tmp-sync-tests.log)
// and sets the log level for "tests" to WARN
log4js.configure({
appenders: [
{
category: 'tests',
type: 'file',
filename: 'tmp-sync-tests.log',
layout: { type: 'messagePassThrough' }
}
],
levels: { tests: 'WARN' }
appenders: { sync: {
type: 'fileSync',
filename: 'tmp-sync-tests.log',
layout: { type: 'messagePassThrough' }
}
},
categories: {
default: { appenders: ['sync'], level: 'debug' },
tests: { appenders: ['sync'], level: 'warn' }
}
});
const logger = log4js.getLogger('tests');
logger.info('this should not be written to the file');

View File

@ -2,7 +2,6 @@
const test = require('tap').test;
const sandbox = require('sandboxed-module');
const log4js = require('../../lib/log4js');
const realLayouts = require('../../lib/layouts');
const setupLogging = function (options, category, compressedLength) {
@ -11,8 +10,9 @@ const setupLogging = function (options, category, compressedLength) {
socket: {
packetLength: 0,
closed: false,
close: function () {
close: function (cb) {
this.closed = true;
if (cb) cb();
},
send: function (pkt, offset, pktLength, port, host) {
fakeDgram.sent = true;
@ -62,12 +62,12 @@ const setupLogging = function (options, category, compressedLength) {
messagePassThroughLayout: realLayouts.messagePassThroughLayout
};
const appender = sandbox.require('../../lib/appenders/gelf', {
singleOnly: true,
const log4js = sandbox.require('../../lib/log4js', {
// singleOnly: true,
requires: {
dgram: fakeDgram,
zlib: fakeZlib,
'../layouts': fakeLayouts
'./layouts': fakeLayouts
},
globals: {
process: {
@ -75,21 +75,29 @@ const setupLogging = function (options, category, compressedLength) {
if (evt === 'exit') {
exitHandler = handler;
}
}
},
env: {}
},
console: fakeConsole
}
});
log4js.clearAppenders();
log4js.addAppender(appender.configure(options || {}), category || 'gelf-test');
options = options || {};
options.type = 'gelf';
log4js.configure({
appenders: { gelf: options },
categories: { default: { appenders: ['gelf'], level: 'debug' } }
});
return {
dgram: fakeDgram,
compress: fakeZlib,
exitHandler: exitHandler,
console: fakeConsole,
layouts: fakeLayouts,
logger: log4js.getLogger(category || 'gelf-test')
logger: log4js.getLogger(category || 'gelf-test'),
log4js: log4js
};
};
@ -163,6 +171,14 @@ test('log4js gelfAppender', (batch) => {
t.end();
});
batch.test('on shutdown should close open sockets', (t) => {
const setup = setupLogging();
setup.log4js.shutdown(() => {
t.ok(setup.dgram.socket.closed);
t.end();
});
});
batch.test('on zlib error should output to console.error', (t) => {
const setup = setupLogging();
setup.compress.shouldError = true;

View File

@ -1,126 +0,0 @@
'use strict';
const test = require('tap').test;
test('log4js global loglevel', (batch) => {
batch.test('global loglevel', (t) => {
const log4js = require('../../lib/log4js');
t.test('set global loglevel on creation', (assert) => {
const log1 = log4js.getLogger('log1');
let level = 'OFF';
if (log1.level.toString() === level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
log4js.setGlobalLogLevel(level);
assert.equal(log1.level.toString(), level);
const log2 = log4js.getLogger('log2');
assert.equal(log2.level.toString(), level);
assert.end();
});
t.test('global change loglevel', (assert) => {
const log1 = log4js.getLogger('log1');
const log2 = log4js.getLogger('log2');
let level = 'OFF';
if (log1.level.toString() === level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
log4js.setGlobalLogLevel(level);
assert.equal(log1.level.toString(), level);
assert.equal(log2.level.toString(), level);
assert.end();
});
t.test('override loglevel', (assert) => {
const log1 = log4js.getLogger('log1');
const log2 = log4js.getLogger('log2');
let level = 'OFF';
if (log1.level.toString() === level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
const oldLevel = log1.level.toString();
assert.equal(log2.level.toString(), oldLevel);
log2.setLevel(level);
assert.equal(log1.level.toString(), oldLevel);
assert.equal(log2.level.toString(), level);
assert.notEqual(oldLevel, level);
log2.removeLevel();
assert.equal(log1.level.toString(), oldLevel);
assert.equal(log2.level.toString(), oldLevel);
assert.end();
});
t.test('preload loglevel', (assert) => {
const log1 = log4js.getLogger('log1');
let level = 'OFF';
if (log1.level.toString() === level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
const oldLevel = log1.level.toString();
log4js.getLogger('log2').setLevel(level);
assert.equal(log1.level.toString(), oldLevel);
// get again same logger but as different variable
const log2 = log4js.getLogger('log2');
assert.equal(log2.level.toString(), level);
assert.notEqual(oldLevel, level);
log2.removeLevel();
assert.equal(log1.level.toString(), oldLevel);
assert.equal(log2.level.toString(), oldLevel);
assert.end();
});
t.test('set level on all categories', (assert) => {
// Get 2 loggers
const log1 = log4js.getLogger('log1');
const log2 = log4js.getLogger('log2');
// First a test with 2 categories with different levels
const config = {
levels: {
log1: 'ERROR',
log2: 'WARN'
}
};
log4js.configure(config);
// Check if the levels are set correctly
assert.equal('ERROR', log1.level.toString());
assert.equal('WARN', log2.level.toString());
log1.removeLevel();
log2.removeLevel();
// Almost identical test, but now we set
// level on all categories
const config2 = {
levels: {
'[all]': 'DEBUG'
}
};
log4js.configure(config2);
// Check if the loggers got the DEBUG level
assert.equal('DEBUG', log1.level.toString());
assert.equal('DEBUG', log2.level.toString());
assert.end();
});
t.end();
});
batch.end();
});

View File

@ -1,7 +1,6 @@
'use strict';
const test = require('tap').test;
const log4js = require('../../lib/log4js');
const sandbox = require('sandboxed-module');
function setupLogging(category, options) {
@ -50,13 +49,19 @@ function setupLogging(category, options) {
}
};
const hipchatModule = sandbox.require('../../lib/appenders/hipchat', {
const log4js = sandbox.require('../../lib/log4js', {
requires: {
'hipchat-notifier': fakeHipchatNotifier
}
});
log4js.clearAppenders();
log4js.addAppender(hipchatModule.configure(options), category);
options = options || {};
options.type = 'hipchat';
log4js.configure({
appenders: { hipchat: options },
categories: { default: { appenders: ['hipchat'], level: 'debug' } }
});
return {
logger: log4js.getLogger(category),
@ -112,7 +117,7 @@ test('HipChat appender', (batch) => {
batch.test('when basicLayout is provided', (t) => {
const topic = setupLogging('myLogger', {
type: 'hipchat',
layout: log4js.layouts.basicLayout
layout: { type: 'basic' }
});
topic.logger.debug('Log event #3');

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) {
@ -74,7 +74,7 @@ test('levels', (batch) => {
levels.OFF
]
);
assertThat(assert, all).isEqualTo([levels.toLevel('ALL')]);
assertThat(assert, all).isEqualTo([levels.getLevel('ALL')]);
assertThat(assert, all).isNotEqualTo(
[
levels.TRACE,
@ -116,7 +116,7 @@ test('levels', (batch) => {
levels.OFF
]
);
assertThat(assert, trace).isEqualTo([levels.toLevel('TRACE')]);
assertThat(assert, trace).isEqualTo([levels.getLevel('TRACE')]);
assertThat(assert, trace).isNotEqualTo(
[
levels.ALL,
@ -156,7 +156,7 @@ test('levels', (batch) => {
levels.OFF
]
);
assertThat(assert, debug).isEqualTo([levels.toLevel('DEBUG')]);
assertThat(assert, debug).isEqualTo([levels.getLevel('DEBUG')]);
assertThat(assert, debug).isNotEqualTo(
[
levels.ALL,
@ -190,7 +190,7 @@ test('levels', (batch) => {
levels.MARK,
levels.OFF
]);
assertThat(assert, info).isEqualTo([levels.toLevel('INFO')]);
assertThat(assert, info).isEqualTo([levels.getLevel('INFO')]);
assertThat(assert, info).isNotEqualTo([
levels.ALL,
levels.TRACE,
@ -222,7 +222,7 @@ test('levels', (batch) => {
assertThat(assert, warn).isNotGreaterThanOrEqualTo([
levels.ERROR, levels.FATAL, levels.MARK, levels.OFF
]);
assertThat(assert, warn).isEqualTo([levels.toLevel('WARN')]);
assertThat(assert, warn).isEqualTo([levels.getLevel('WARN')]);
assertThat(assert, warn).isNotEqualTo([
levels.ALL,
levels.TRACE,
@ -253,7 +253,7 @@ test('levels', (batch) => {
levels.WARN
]);
assertThat(assert, error).isNotGreaterThanOrEqualTo([levels.FATAL, levels.MARK, levels.OFF]);
assertThat(assert, error).isEqualTo([levels.toLevel('ERROR')]);
assertThat(assert, error).isEqualTo([levels.getLevel('ERROR')]);
assertThat(assert, error).isNotEqualTo([
levels.ALL,
levels.TRACE,
@ -287,7 +287,7 @@ test('levels', (batch) => {
levels.ERROR
]);
assertThat(assert, fatal).isNotGreaterThanOrEqualTo([levels.MARK, levels.OFF]);
assertThat(assert, fatal).isEqualTo([levels.toLevel('FATAL')]);
assertThat(assert, fatal).isEqualTo([levels.getLevel('FATAL')]);
assertThat(assert, fatal).isNotEqualTo([
levels.ALL,
levels.TRACE,
@ -323,7 +323,7 @@ test('levels', (batch) => {
levels.FATAL
]);
assertThat(assert, mark).isNotGreaterThanOrEqualTo([levels.OFF]);
assertThat(assert, mark).isEqualTo([levels.toLevel('MARK')]);
assertThat(assert, mark).isEqualTo([levels.getLevel('MARK')]);
assertThat(assert, mark).isNotEqualTo([
levels.ALL,
levels.TRACE,
@ -359,7 +359,7 @@ test('levels', (batch) => {
levels.FATAL,
levels.MARK
]);
assertThat(assert, off).isEqualTo([levels.toLevel('OFF')]);
assertThat(assert, off).isEqualTo([levels.getLevel('OFF')]);
assertThat(assert, off).isNotEqualTo([
levels.ALL,
levels.TRACE,
@ -396,11 +396,11 @@ test('levels', (batch) => {
});
batch.test('toLevel', (t) => {
t.equal(levels.toLevel('debug'), levels.DEBUG);
t.equal(levels.toLevel('DEBUG'), levels.DEBUG);
t.equal(levels.toLevel('DeBuG'), levels.DEBUG);
t.notOk(levels.toLevel('cheese'));
t.equal(levels.toLevel('cheese', levels.DEBUG), levels.DEBUG);
t.equal(levels.getLevel('debug'), levels.DEBUG);
t.equal(levels.getLevel('DEBUG'), levels.DEBUG);
t.equal(levels.getLevel('DeBuG'), levels.DEBUG);
t.notOk(levels.getLevel('cheese'));
t.equal(levels.getLevel('cheese', levels.DEBUG), levels.DEBUG);
t.end();
});

View File

@ -1,88 +0,0 @@
'use strict';
const test = require('tap').test;
const path = require('path');
const sandbox = require('sandboxed-module');
test('log4js-abspath', (batch) => {
batch.test('options', (t) => {
let appenderOptions;
const log4js = sandbox.require(
'../../lib/log4js',
{
singleOnly: true,
requires: {
'./appenders/fake': {
name: 'fake',
appender: function () {
},
configure: function (configuration, options) {
appenderOptions = options;
return function () {
};
}
}
}
}
);
const config = {
appenders: [
{
type: 'fake',
filename: 'cheesy-wotsits.log'
}
]
};
log4js.configure(config, {
cwd: '/absolute/path/to'
});
t.test('should be passed to appenders during configuration', (assert) => {
assert.equal(appenderOptions.cwd, '/absolute/path/to');
assert.end();
});
t.end();
});
batch.test('file appender', (t) => {
let fileOpened;
const fileAppender = sandbox.require(
'../../lib/appenders/file',
{
requires: {
streamroller: {
RollingFileStream: function (file) {
fileOpened = file;
return {
on: function () {
},
end: function () {
}
};
}
}
}
}
);
fileAppender.configure(
{
filename: 'whatever.log',
maxLogSize: 10
},
{ cwd: '/absolute/path/to' }
);
t.test('should prepend options.cwd to config.filename', (assert) => {
const expected = path.sep + path.join('absolute', 'path', 'to', 'whatever.log');
assert.equal(fileOpened, expected);
assert.end();
});
t.end();
});
batch.end();
});

View File

@ -0,0 +1,102 @@
'use strict';
const test = require('tap').test;
const sandbox = require('sandboxed-module');
function setupLogging(category, options) {
const fakeAxios = {
create: function (config) {
this.config = config;
return {
post: function (emptyString, event) {
fakeAxios.args = [emptyString, event];
return {
catch: function (cb) {
fakeAxios.errorCb = cb;
}
};
}
};
}
};
const fakeConsole = {
error: function (msg) {
this.msg = msg;
}
};
const log4js = sandbox.require('../../lib/log4js', {
requires: {
axios: fakeAxios
},
globals: {
console: fakeConsole
}
});
options.type = 'logFaces-HTTP';
log4js.configure({
appenders: { http: options },
categories: { default: { appenders: ['http'], level: 'trace' } }
});
return {
logger: log4js.getLogger(category),
fakeAxios: fakeAxios,
fakeConsole: fakeConsole
};
}
test('logFaces appender', (batch) => {
batch.test('when using HTTP receivers', (t) => {
const setup = setupLogging('myCategory', {
application: 'LFS-HTTP',
url: 'http://localhost/receivers/rx1'
});
t.test('axios should be configured', (assert) => {
assert.equal(setup.fakeAxios.config.baseURL, 'http://localhost/receivers/rx1');
assert.equal(setup.fakeAxios.config.timeout, 5000);
assert.equal(setup.fakeAxios.config.withCredentials, true);
assert.same(setup.fakeAxios.config.headers, { 'Content-Type': 'application/json' });
assert.end();
});
setup.logger.addContext('foo', 'bar');
setup.logger.addContext('bar', 'foo');
setup.logger.warn('Log event #1');
t.test('an event should be sent', (assert) => {
const event = setup.fakeAxios.args[1];
assert.equal(event.a, 'LFS-HTTP');
assert.equal(event.m, 'Log event #1');
assert.equal(event.g, 'myCategory');
assert.equal(event.p, 'WARN');
assert.equal(event.p_foo, 'bar');
assert.equal(event.p_bar, 'foo');
// Assert timestamp, up to hours resolution.
const date = new Date(event.t);
assert.equal(
date.toISOString().substring(0, 14),
new Date().toISOString().substring(0, 14)
);
assert.end();
});
t.test('errors should be sent to console.error', (assert) => {
setup.fakeAxios.errorCb({ response: { status: 500, data: 'oh no' } });
assert.equal(
setup.fakeConsole.msg,
'log4js.logFaces-HTTP Appender error posting to http://localhost/receivers/rx1: 500 - oh no'
);
setup.fakeAxios.errorCb(new Error('oh dear'));
assert.equal(setup.fakeConsole.msg, 'log4js.logFaces-HTTP Appender error: oh dear');
assert.end();
});
t.end();
});
batch.end();
});

View File

@ -0,0 +1,94 @@
'use strict';
const test = require('tap').test;
const sandbox = require('sandboxed-module');
function setupLogging(category, options) {
const fakeDgram = {
createSocket: function (type) {
fakeDgram.type = type;
return {
send: function (buffer, start, end, port, host, cb) {
fakeDgram.buffer = buffer;
fakeDgram.start = start;
fakeDgram.end = end;
fakeDgram.port = port;
fakeDgram.host = host;
fakeDgram.cb = cb;
}
};
}
};
const fakeConsole = {
error: function (msg, err) {
this.msg = msg;
this.err = err;
}
};
const log4js = sandbox.require('../../lib/log4js', {
requires: {
dgram: fakeDgram
},
globals: {
console: fakeConsole
}
});
options.type = 'logFaces-UDP';
log4js.configure({
appenders: {
udp: options
},
categories: { default: { appenders: ['udp'], level: 'trace' } }
});
return {
logger: log4js.getLogger(category),
dgram: fakeDgram,
console: fakeConsole
};
}
test('logFaces appender', (batch) => {
batch.test('when using UDP receivers', (t) => {
const setup = setupLogging('udpCategory', {
application: 'LFS-UDP',
remoteHost: '127.0.0.1',
port: 55201
});
setup.logger.addContext('foo', 'bar');
setup.logger.addContext('bar', 'foo');
setup.logger.error('Log event #2');
t.test('an event should be sent', (assert) => {
const event = JSON.parse(setup.dgram.buffer.toString());
assert.equal(event.a, 'LFS-UDP');
assert.equal(event.m, 'Log event #2');
assert.equal(event.g, 'udpCategory');
assert.equal(event.p, 'ERROR');
assert.equal(event.p_foo, 'bar');
assert.equal(event.p_bar, 'foo');
// Assert timestamp, up to hours resolution.
const date = new Date(event.t);
assert.equal(
date.toISOString().substring(0, 14),
new Date().toISOString().substring(0, 14)
);
assert.end();
});
t.test('dgram errors should be sent to console.error', (assert) => {
setup.dgram.cb('something went wrong');
assert.equal(setup.console.msg, 'log4js.logFacesUDPAppender error sending to 127.0.0.1:55201, error: ');
assert.equal(setup.console.err, 'something went wrong');
assert.end();
});
t.end();
});
batch.end();
});

View File

@ -1,89 +0,0 @@
'use strict';
const test = require('tap').test;
const log4js = require('../../lib/log4js');
function setupLogging(category, options) {
const sent = {};
function fake(event) {
Object.keys(event).forEach((key) => {
sent[key] = event[key];
});
}
const lfsModule = require('../../lib/appenders/logFacesAppender');
options.send = fake;
log4js.clearAppenders();
log4js.addAppender(lfsModule.configure(options), category);
lfsModule.setContext('foo', 'bar');
lfsModule.setContext('bar', 'foo');
return {
logger: log4js.getLogger(category),
results: sent
};
}
test('logFaces appender', (batch) => {
batch.test('when using HTTP receivers', (t) => {
const setup = setupLogging('myCategory', {
type: 'logFacesAppender',
application: 'LFS-HTTP',
url: 'http://localhost/receivers/rx1'
});
setup.logger.warn('Log event #1');
t.test('an event should be sent', (assert) => {
const event = setup.results;
assert.equal(event.a, 'LFS-HTTP');
assert.equal(event.m, 'Log event #1');
assert.equal(event.g, 'myCategory');
assert.equal(event.p, 'WARN');
assert.equal(event.p_foo, 'bar');
assert.equal(event.p_bar, 'foo');
// Assert timestamp, up to hours resolution.
const date = new Date(event.t);
assert.equal(
date.toISOString().substring(0, 14),
new Date().toISOString().substring(0, 14)
);
assert.end();
});
t.end();
});
batch.test('when using UDP receivers', (t) => {
const setup = setupLogging('udpCategory', {
type: 'logFacesAppender',
application: 'LFS-UDP',
remoteHost: '127.0.0.1',
port: 55201
});
setup.logger.error('Log event #2');
t.test('an event should be sent', (assert) => {
const event = setup.results;
assert.equal(event.a, 'LFS-UDP');
assert.equal(event.m, 'Log event #2');
assert.equal(event.g, 'udpCategory');
assert.equal(event.p, 'ERROR');
assert.equal(event.p_foo, 'bar');
assert.equal(event.p_bar, 'foo');
// Assert timestamp, up to hours resolution.
const date = new Date(event.t);
assert.equal(
date.toISOString().substring(0, 14),
new Date().toISOString().substring(0, 14)
);
assert.end();
});
t.end();
});
batch.end();
});

View File

@ -17,20 +17,17 @@ function remove(filename) {
test('log4js logLevelFilter', (batch) => {
batch.test('appender', (t) => {
const log4js = require('../../lib/log4js');
const logEvents = [];
const recording = require('../../lib/appenders/recording');
log4js.clearAppenders();
log4js.addAppender(
require('../../lib/appenders/logLevelFilter')
.appender(
'ERROR',
undefined,
(evt) => {
logEvents.push(evt);
}
),
'logLevelTest'
);
log4js.configure({
appenders: {
recorder: { type: 'recording' },
filtered: { type: 'logLevelFilter', appender: 'recorder', level: 'ERROR' }
},
categories: {
default: { appenders: ['filtered'], level: 'debug' }
}
});
const logger = log4js.getLogger('logLevelTest');
logger.debug('this should not trigger an event');
@ -38,6 +35,8 @@ test('log4js logLevelFilter', (batch) => {
logger.error('this should, though');
logger.fatal('so should this');
const logEvents = recording.replay();
t.test('should only pass log events greater than or equal to its own level', (assert) => {
assert.equal(logEvents.length, 2);
assert.equal(logEvents[0].data[0], 'this should, though');
@ -54,7 +53,47 @@ test('log4js logLevelFilter', (batch) => {
remove(`${__dirname}/logLevelFilter-warnings.log`);
remove(`${__dirname}/logLevelFilter-debugs.log`);
log4js.configure('test/tap/with-logLevelFilter.json');
t.tearDown(() => {
remove(`${__dirname}/logLevelFilter.log`);
remove(`${__dirname}/logLevelFilter-warnings.log`);
remove(`${__dirname}/logLevelFilter-debugs.log`);
});
log4js.configure({
appenders: {
'warning-file': {
type: 'file',
filename: 'test/tap/logLevelFilter-warnings.log',
layout: { type: 'messagePassThrough' }
},
warnings: {
type: 'logLevelFilter',
level: 'WARN',
appender: 'warning-file'
},
'debug-file': {
type: 'file',
filename: 'test/tap/logLevelFilter-debugs.log',
layout: { type: 'messagePassThrough' }
},
debugs: {
type: 'logLevelFilter',
level: 'TRACE',
maxLevel: 'DEBUG',
appender: 'debug-file'
},
tests: {
type: 'file',
filename: 'test/tap/logLevelFilter.log',
layout: {
type: 'messagePassThrough'
}
}
},
categories: {
default: { appenders: ['tests', 'warnings', 'debugs'], level: 'trace' }
}
});
const logger = log4js.getLogger('tests');
logger.debug('debug');
logger.info('info');

View File

@ -1,35 +1,55 @@
'use strict';
const test = require('tap').test;
const levels = require('../../lib/levels');
const loggerModule = require('../../lib/logger');
const levels = require('../../lib/levels')();
const loggerModule = require('../../lib/logger')(levels);
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) => {
testDispatcher.events = [];
done();
});
batch.test('constructor with no parameters', (t) => {
const logger = new Logger();
t.throws(
() => new Logger(),
new Error('No dispatch function provided.')
);
t.end();
});
batch.test('constructor with only dispatch', (t) => {
const logger = new Logger(dispatch);
t.equal(logger.category, Logger.DEFAULT_CATEGORY, 'should use default category');
t.equal(logger.level, levels.TRACE, 'should use TRACE log level');
t.end();
});
batch.test('constructor with category', (t) => {
const logger = new Logger('cheese');
const logger = new Logger(dispatch, 'cheese');
t.equal(logger.category, 'cheese', 'should use category');
t.equal(logger.level, levels.TRACE, 'should use TRACE log level');
t.end();
});
batch.test('constructor with category and level', (t) => {
const logger = new Logger('cheese', 'debug');
const logger = new Logger(dispatch, 'cheese', 'debug');
t.equal(logger.category, 'cheese', 'should use category');
t.equal(logger.level, levels.DEBUG, 'should use level');
t.end();
});
batch.test('isLevelEnabled', (t) => {
const logger = new Logger('cheese', 'info');
const logger = new Logger(dispatch, 'cheese', 'info');
const functions = [
'isTraceEnabled', 'isDebugEnabled', 'isInfoEnabled',
'isWarnEnabled', 'isErrorEnabled', 'isFatalEnabled'
@ -52,28 +72,41 @@ test('../../lib/logger', (batch) => {
t.end();
});
batch.test('should emit log events', (t) => {
const events = [];
const logger = new Logger();
logger.addListener('log', (logEvent) => {
events.push(logEvent);
});
batch.test('should send log events to dispatch function', (t) => {
const logger = new Logger(dispatch);
logger.debug('Event 1');
loggerModule.disableAllLogWrites();
logger.debug('Event 2');
loggerModule.enableAllLogWrites();
logger.debug('Event 3');
const events = testDispatcher.events;
t.test('when log writes are enabled', (assert) => {
assert.equal(events[0].data[0], 'Event 1');
assert.end();
});
t.equal(events.length, 3);
t.equal(events[0].data[0], 'Event 1');
t.equal(events[1].data[0], 'Event 2');
t.equal(events[2].data[0], 'Event 3');
t.end();
});
t.test('but not when log writes are disabled', (assert) => {
assert.equal(events.length, 2);
assert.equal(events[1].data[0], 'Event 3');
assert.end();
});
batch.test('should add context values to every event', (t) => {
const logger = new Logger(dispatch);
logger.debug('Event 1');
logger.addContext('cheese', 'edam');
logger.debug('Event 2');
logger.debug('Event 3');
logger.addContext('biscuits', 'timtam');
logger.debug('Event 4');
logger.removeContext('cheese');
logger.debug('Event 5');
logger.clearContext();
logger.debug('Event 6');
const events = testDispatcher.events;
t.equal(events.length, 6);
t.same(events[0].context, {});
t.same(events[1].context, { cheese: 'edam' });
t.same(events[2].context, { cheese: 'edam' });
t.same(events[3].context, { cheese: 'edam', biscuits: 'timtam' });
t.same(events[4].context, { biscuits: 'timtam' });
t.same(events[5].context, {});
t.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,8 +1,8 @@
'use strict';
const test = require('tap').test;
const log4js = require('../../lib/log4js');
const sandbox = require('sandboxed-module');
const layouts = require('../../lib/layouts');
function setupLogging(category, options) {
const msgs = [];
@ -26,10 +26,10 @@ function setupLogging(category, options) {
layout: function (type, config) {
this.type = type;
this.config = config;
return log4js.layouts.messagePassThroughLayout;
return layouts.messagePassThroughLayout;
},
basicLayout: log4js.layouts.basicLayout,
messagePassThroughLayout: log4js.layouts.messagePassThroughLayout
basicLayout: layouts.basicLayout,
messagePassThroughLayout: layouts.messagePassThroughLayout
};
const fakeConsole = {
@ -39,22 +39,26 @@ function setupLogging(category, options) {
}
};
const logglyModule = sandbox.require('../../lib/appenders/loggly', {
const log4js = sandbox.require('../../lib/log4js', {
requires: {
loggly: fakeLoggly,
'../layouts': fakeLayouts
'./layouts': fakeLayouts
},
globals: {
console: fakeConsole
}
});
log4js.addAppender(
logglyModule.configure(options),
logglyModule.shutdown,
category);
options = options || {};
options.type = 'loggly';
log4js.configure({
appenders: { loggly: options },
categories: { default: { appenders: ['loggly'], level: 'trace' } }
});
return {
log4js: log4js,
logger: log4js.getLogger(category),
loggly: fakeLoggly,
layouts: fakeLayouts,
@ -63,8 +67,6 @@ function setupLogging(category, options) {
};
}
log4js.clearAppenders();
function setupTaggedLogging() {
return setupLogging('loggly', {
token: 'your-really-long-input-token',
@ -105,9 +107,9 @@ test('log4js logglyAppender', (batch) => {
tags: ['tag1', 'tag2']
});
log4js.shutdown(() => { t.end(); });
setup.log4js.shutdown(() => { t.end(); });
// shutdown will until after the last message has been sent to loggly
// shutdown will wait until after the last message has been sent to loggly
setup.results[0].cb();
});

View File

@ -1,11 +1,11 @@
'use strict';
const test = require('tap').test;
const log4js = require('../../lib/log4js');
const sandbox = require('sandboxed-module');
function setupLogging(category, options) {
const udpSent = {};
const socket = { closed: false };
const fakeDgram = {
createSocket: function () {
@ -18,23 +18,33 @@ function setupLogging(category, options) {
udpSent.offset = 0;
udpSent.buffer = buffer;
callback(undefined, length);
},
close: function (cb) {
socket.closed = true;
cb();
}
};
}
};
const logstashModule = sandbox.require('../../lib/appenders/logstashUDP', {
singleOnly: true,
const log4js = sandbox.require('../../lib/log4js', {
requires: {
dgram: fakeDgram
}
});
log4js.clearAppenders();
log4js.addAppender(logstashModule.configure(options), category);
options = options || {};
options.type = 'logstashUDP';
log4js.configure({
appenders: { logstash: options },
categories: { default: { appenders: ['logstash'], level: 'trace' } }
});
return {
logger: log4js.getLogger(category),
results: udpSent
log4js: log4js,
results: udpSent,
socket: socket
};
}
@ -72,7 +82,7 @@ test('logstashUDP appender', (batch) => {
const keys = Object.keys(fields);
for (let i = 0, length = keys.length; i < length; i += 1) {
t.equal(json[keys[i]], fields[keys[i]]);
t.equal(json[keys[i]], fields[keys[i]]);
}
t.equal(JSON.stringify(json.fields), JSON.stringify(fields));
@ -133,5 +143,21 @@ test('logstashUDP appender', (batch) => {
t.end();
});
batch.test('shutdown should close sockets', (t) => {
const setup = setupLogging('myLogger', {
host: '127.0.0.1',
port: 10001,
type: 'logstashUDP',
category: 'myLogger',
layout: {
type: 'dummy'
}
});
setup.log4js.shutdown(() => {
t.ok(setup.socket.closed);
t.end();
});
});
batch.end();
});

View File

@ -1,7 +1,7 @@
'use strict';
const test = require('tap').test;
const log4js = require('../../lib/log4js');
const layouts = require('../../lib/layouts');
const sandbox = require('sandboxed-module');
function setupLogging(category, options) {
@ -30,10 +30,10 @@ function setupLogging(category, options) {
layout: function (type, config) {
this.type = type;
this.config = config;
return log4js.layouts.messagePassThroughLayout;
return layouts.messagePassThroughLayout;
},
basicLayout: log4js.layouts.basicLayout,
messagePassThroughLayout: log4js.layouts.messagePassThroughLayout
basicLayout: layouts.basicLayout,
messagePassThroughLayout: layouts.messagePassThroughLayout
};
const fakeConsole = {
@ -47,19 +47,21 @@ function setupLogging(category, options) {
}
};
const mailgunModule = sandbox.require('../../lib/appenders/mailgun', {
const log4js = sandbox.require('../../lib/log4js', {
requires: {
'mailgun-js': fakeMailgun,
'../layouts': fakeLayouts
'./layouts': fakeLayouts
},
globals: {
console: fakeConsole
}
});
log4js.addAppender(mailgunModule.configure(options), category);
options = options || {};
options.type = 'mailgun';
log4js.configure({
appenders: { mailgun: options },
categories: { default: { appenders: ['mailgun'], level: 'trace' } }
});
return {
logger: log4js.getLogger(category),
@ -80,8 +82,6 @@ function checkMessages(assert, result) {
}
}
log4js.clearAppenders();
test('log4js mailgunAppender', (batch) => {
batch.test('mailgun setup', (t) => {
const result = setupLogging('mailgun setup', {

View File

@ -6,14 +6,16 @@ const net = require('net');
test('multiprocess appender shutdown (master)', { timeout: 2000 }, (t) => {
log4js.configure({
appenders: [
{
appenders: {
stdout: { type: 'stdout' },
multi: {
type: 'multiprocess',
mode: 'master',
loggerPort: 12345,
appender: { type: 'stdout' }
appender: 'stdout'
}
]
},
categories: { default: { appenders: ['multi'], level: 'debug' } }
});
setTimeout(() => {

View File

@ -2,16 +2,13 @@
const test = require('tap').test;
const sandbox = require('sandboxed-module');
const recording = require('../../lib/appenders/recording');
function makeFakeNet() {
return {
logEvents: [],
data: [],
cbs: {},
createConnectionCalled: 0,
fakeAppender: function (logEvent) {
this.logEvents.push(logEvent);
},
createConnection: function (port, host) {
const fakeNet = this;
this.port = port;
@ -54,27 +51,36 @@ function makeFakeNet() {
}
test('Multiprocess Appender', (batch) => {
batch.beforeEach((done) => {
recording.erase();
done();
});
batch.test('worker', (t) => {
const fakeNet = makeFakeNet();
const appender = sandbox.require(
'../../lib/appenders/multiprocess',
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
).appender({ mode: 'worker', loggerPort: 1234, loggerHost: 'pants' });
);
log4js.configure({
appenders: { worker: { type: 'multiprocess', mode: 'worker', loggerPort: 1234, loggerHost: 'pants' } },
categories: { default: { appenders: ['worker'], level: 'trace' } }
});
// don't need a proper log event for the worker tests
appender('before connect');
const logger = log4js.getLogger();
logger.info('before connect');
fakeNet.cbs.connect();
appender('after connect');
logger.info('after connect');
fakeNet.cbs.close(true);
appender('after error, before connect');
logger.info('after error, before connect');
fakeNet.cbs.connect();
appender('after error, after connect');
appender(new Error('Error test'));
logger.info('after error, after connect');
logger.error(new Error('Error test'));
const net = fakeNet;
t.test('should open a socket to the loggerPort and loggerHost', (assert) => {
@ -84,23 +90,23 @@ test('Multiprocess Appender', (batch) => {
});
t.test('should buffer messages written before socket is connected', (assert) => {
assert.equal(net.data[0], JSON.stringify('before connect'));
assert.include(net.data[0], JSON.stringify('before connect'));
assert.end();
});
t.test('should write log messages to socket as json strings with a terminator string', (assert) => {
assert.equal(net.data[0], JSON.stringify('before connect'));
assert.include(net.data[0], JSON.stringify('before connect'));
assert.equal(net.data[1], '__LOG4JS__');
assert.equal(net.data[2], JSON.stringify('after connect'));
assert.include(net.data[2], JSON.stringify('after connect'));
assert.equal(net.data[3], '__LOG4JS__');
assert.equal(net.encoding, 'utf8');
assert.end();
});
t.test('should attempt to re-open the socket on error', (assert) => {
assert.equal(net.data[4], JSON.stringify('after error, before connect'));
assert.include(net.data[4], JSON.stringify('after error, before connect'));
assert.equal(net.data[5], '__LOG4JS__');
assert.equal(net.data[6], JSON.stringify('after error, after connect'));
assert.include(net.data[6], JSON.stringify('after error, after connect'));
assert.equal(net.data[7], '__LOG4JS__');
assert.equal(net.createConnectionCalled, 2);
assert.end();
@ -108,48 +114,53 @@ test('Multiprocess Appender', (batch) => {
t.test('should serialize an Error correctly', (assert) => {
assert.ok(
JSON.parse(net.data[8]).stack,
`Expected:\n\n${net.data[8]}\n\n to have a 'stack' property`
JSON.parse(net.data[8]).data[0].stack,
`Expected:\n\n${net.data[8]}\n\n to have a 'data[0].stack' property`
);
const actual = JSON.parse(net.data[8]).stack;
const actual = JSON.parse(net.data[8]).data[0].stack;
assert.match(actual, /^Error: Error test/);
assert.end();
});
t.end();
});
batch.test('worker with timeout', (t) => {
const fakeNet = makeFakeNet();
const appender = sandbox.require(
'../../lib/appenders/multiprocess',
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
).appender({ mode: 'worker' });
);
log4js.configure({
appenders: { worker: { type: 'multiprocess', mode: 'worker' } },
categories: { default: { appenders: ['worker'], level: 'trace' } }
});
// don't need a proper log event for the worker tests
appender('before connect');
const logger = log4js.getLogger();
logger.info('before connect');
fakeNet.cbs.connect();
appender('after connect');
logger.info('after connect');
fakeNet.cbs.timeout();
appender('after timeout, before close');
logger.info('after timeout, before close');
fakeNet.cbs.close();
appender('after close, before connect');
logger.info('after close, before connect');
fakeNet.cbs.connect();
appender('after close, after connect');
logger.info('after close, after connect');
const net = fakeNet;
t.test('should attempt to re-open the socket', (assert) => {
// skipping the __LOG4JS__ separators
assert.equal(net.data[0], JSON.stringify('before connect'));
assert.equal(net.data[2], JSON.stringify('after connect'));
assert.equal(net.data[4], JSON.stringify('after timeout, before close'));
assert.equal(net.data[6], JSON.stringify('after close, before connect'));
assert.equal(net.data[8], JSON.stringify('after close, after connect'));
assert.include(net.data[0], JSON.stringify('before connect'));
assert.include(net.data[2], JSON.stringify('after connect'));
assert.include(net.data[4], JSON.stringify('after timeout, before close'));
assert.include(net.data[6], JSON.stringify('after close, before connect'));
assert.include(net.data[8], JSON.stringify('after close, after connect'));
assert.equal(net.createConnectionCalled, 2);
assert.end();
});
@ -159,14 +170,18 @@ test('Multiprocess Appender', (batch) => {
batch.test('worker defaults', (t) => {
const fakeNet = makeFakeNet();
sandbox.require(
'../../lib/appenders/multiprocess',
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
).appender({ mode: 'worker' });
);
log4js.configure({
appenders: { worker: { type: 'multiprocess', mode: 'worker' } },
categories: { default: { appenders: ['worker'], level: 'trace' } }
});
t.test('should open a socket to localhost:5000', (assert) => {
assert.equal(fakeNet.port, 5000);
@ -179,22 +194,29 @@ test('Multiprocess Appender', (batch) => {
batch.test('master', (t) => {
const fakeNet = makeFakeNet();
const appender = sandbox.require(
'../../lib/appenders/multiprocess',
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
net: fakeNet,
'./appenders/recording': recording
}
}
).appender({
mode: 'master',
loggerHost: 'server',
loggerPort: 1234,
actualAppender: fakeNet.fakeAppender.bind(fakeNet)
);
log4js.configure({
appenders: {
recorder: { type: 'recording' },
master: {
type: 'multiprocess',
mode: 'master',
loggerPort: 1234,
loggerHost: 'server',
appender: 'recorder'
}
},
categories: { default: { appenders: ['master'], level: 'trace' } }
});
appender('this should be sent to the actual appender directly');
const net = fakeNet;
t.test('should listen for log messages on loggerPort and loggerHost', (assert) => {
@ -204,7 +226,9 @@ test('Multiprocess Appender', (batch) => {
});
t.test('should return the underlying appender', (assert) => {
assert.equal(net.logEvents[0], 'this should be sent to the actual appender directly');
log4js.getLogger().info('this should be sent to the actual appender directly');
assert.equal(recording.replay()[0].data[0], 'this should be sent to the actual appender directly');
assert.end();
});
@ -237,48 +261,98 @@ test('Multiprocess Appender', (batch) => {
);
net.cbs.data('bad message__LOG4JS__');
const logEvents = recording.replay();
// should parse log messages into log events and send to appender
assert.equal(net.logEvents[1].level.toString(), 'ERROR');
assert.equal(net.logEvents[1].data[0], 'an error message');
assert.equal(net.logEvents[1].remoteAddress, '1.2.3.4');
assert.equal(net.logEvents[1].remotePort, '1234');
assert.equal(logEvents[0].level.toString(), 'ERROR');
assert.equal(logEvents[0].data[0], 'an error message');
assert.equal(logEvents[0].remoteAddress, '1.2.3.4');
assert.equal(logEvents[0].remotePort, '1234');
// should parse log messages split into multiple chunks'
assert.equal(net.logEvents[2].level.toString(), 'DEBUG');
assert.equal(net.logEvents[2].data[0], 'some debug');
assert.equal(net.logEvents[2].remoteAddress, '1.2.3.4');
assert.equal(net.logEvents[2].remotePort, '1234');
assert.equal(logEvents[1].level.toString(), 'DEBUG');
assert.equal(logEvents[1].data[0], 'some debug');
assert.equal(logEvents[1].remoteAddress, '1.2.3.4');
assert.equal(logEvents[1].remotePort, '1234');
// should parse multiple log messages in a single chunk'
assert.equal(net.logEvents[3].data[0], 'some debug');
assert.equal(net.logEvents[4].data[0], 'some debug');
assert.equal(net.logEvents[5].data[0], 'some debug');
assert.equal(logEvents[2].data[0], 'some debug');
assert.equal(logEvents[3].data[0], 'some debug');
assert.equal(logEvents[4].data[0], 'some debug');
// should handle log messages sent as part of end event'
assert.equal(net.logEvents[6].data[0], "that's all folks");
assert.equal(logEvents[5].data[0], "that's all folks");
// should handle unparseable log messages
assert.equal(net.logEvents[7].level.toString(), 'ERROR');
assert.equal(net.logEvents[7].categoryName, 'log4js');
assert.equal(net.logEvents[7].data[0], 'Unable to parse log:');
assert.equal(net.logEvents[7].data[1], 'bad message');
assert.equal(logEvents[6].level.toString(), 'ERROR');
assert.equal(logEvents[6].categoryName, 'log4js');
assert.equal(logEvents[6].data[0], 'Unable to parse log:');
assert.equal(logEvents[6].data[1], 'bad message');
assert.end();
});
t.end();
});
batch.test('master defaults', (t) => {
batch.test('master without actual appender throws error', (t) => {
const fakeNet = makeFakeNet();
sandbox.require(
'../../lib/appenders/multiprocess',
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
).appender({ mode: 'master' });
);
t.throws(() =>
log4js.configure({
appenders: { master: { type: 'multiprocess', mode: 'master' } },
categories: { default: { appenders: ['master'], level: 'trace' } }
}),
new Error('multiprocess master must have an "appender" defined')
);
t.end();
});
batch.test('master with unknown appender throws error', (t) => {
const fakeNet = makeFakeNet();
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
);
t.throws(() =>
log4js.configure({
appenders: { master: { type: 'multiprocess', mode: 'master', appender: 'cheese' } },
categories: { default: { appenders: ['master'], level: 'trace' } }
}),
new Error('multiprocess master appender "cheese" not defined')
);
t.end();
});
batch.test('master defaults', (t) => {
const fakeNet = makeFakeNet();
const log4js = sandbox.require(
'../../lib/log4js',
{
requires: {
net: fakeNet
}
}
);
log4js.configure({
appenders: {
stdout: { type: 'stdout' },
master: { type: 'multiprocess', mode: 'master', appender: 'stdout' }
},
categories: { default: { appenders: ['master'], level: 'trace' } }
});
t.test('should listen for log messages on localhost:5000', (assert) => {
assert.equal(fakeNet.port, 5000);
@ -288,44 +362,5 @@ test('Multiprocess Appender', (batch) => {
t.end();
});
batch.test('configure', (t) => {
const results = {};
const fakeNet = makeFakeNet();
sandbox.require(
'../../lib/appenders/multiprocess',
{
requires: {
net: fakeNet,
'../log4js': {
loadAppender: function (app) {
results.appenderLoaded = app;
},
appenderMakers: {
madeupappender: function (config, options) {
results.config = config;
results.options = options;
}
}
}
}
}
).configure(
{
mode: 'master',
appender: {
type: 'madeupappender',
cheese: 'gouda'
}
},
{ crackers: 'jacobs' }
);
t.equal(results.appenderLoaded, 'madeupappender', 'should load underlying appender for master');
t.equal(results.config.cheese, 'gouda', 'should pass config to underlying appender');
t.equal(results.options.crackers, 'jacobs', 'should pass options to underlying appender');
t.end();
});
batch.end();
});

View File

@ -1,37 +1,57 @@
'use strict';
const test = require('tap').test;
const Level = require('../../lib/levels');
const log4js = require('../../lib/log4js');
const loggerModule = require('../../lib/logger');
const Logger = loggerModule.Logger;
const recording = require('../../lib/appenders/recording');
test('../../lib/logger', (batch) => {
batch.beforeEach((done) => {
recording.reset();
done();
});
batch.test('creating a new log level', (t) => {
Level.forName('DIAG', 6000);
const logger = new Logger();
log4js.configure({
levels: {
DIAG: 6000
},
appenders: {
stdout: { type: 'stdout' }
},
categories: {
default: { appenders: ['stdout'], level: 'trace' }
}
});
const logger = log4js.getLogger();
t.test('should export new log level in levels module', (assert) => {
assert.ok(Level.DIAG);
assert.equal(Level.DIAG.levelStr, 'DIAG');
assert.equal(Level.DIAG.level, 6000);
assert.ok(log4js.levels.DIAG);
assert.equal(log4js.levels.DIAG.levelStr, 'DIAG');
assert.equal(log4js.levels.DIAG.level, 6000);
assert.end();
});
t.type(logger.diag, 'function', 'should create named function on logger prototype');
t.type(logger.isDiagEnabled, 'function', 'should create isLevelEnabled function on logger prototype');
t.type(logger.info, 'function', 'should retain default levels');
t.end();
});
batch.test('creating a new log level with underscores', (t) => {
Level.forName('NEW_LEVEL_OTHER', 6000);
const logger = new Logger();
log4js.configure({
levels: {
NEW_LEVEL_OTHER: 6000
},
appenders: { stdout: { type: 'stdout' } },
categories: { default: { appenders: ['stdout'], level: 'trace' } }
});
const logger = log4js.getLogger();
t.test('should export new log level to levels module', (assert) => {
assert.ok(Level.NEW_LEVEL_OTHER);
assert.equal(Level.NEW_LEVEL_OTHER.levelStr, 'NEW_LEVEL_OTHER');
assert.equal(Level.NEW_LEVEL_OTHER.level, 6000);
assert.ok(log4js.levels.NEW_LEVEL_OTHER);
assert.equal(log4js.levels.NEW_LEVEL_OTHER.levelStr, 'NEW_LEVEL_OTHER');
assert.equal(log4js.levels.NEW_LEVEL_OTHER.level, 6000);
assert.end();
});
@ -47,19 +67,26 @@ test('../../lib/logger', (batch) => {
});
batch.test('creating log events containing newly created log level', (t) => {
const events = [];
const logger = new Logger();
logger.addListener('log', (logEvent) => {
events.push(logEvent);
log4js.configure({
levels: {
LVL1: 6000,
LVL2: 5000
},
appenders: { recorder: { type: 'recording' } },
categories: {
default: { appenders: ['recorder'], level: 'LVL1' }
}
});
const logger = log4js.getLogger();
logger.log(Level.forName('LVL1', 6000), 'Event 1');
logger.log(Level.getLevel('LVL1'), 'Event 2');
logger.log(log4js.levels.getLevel('LVL1', log4js.levels.DEBUG), 'Event 1');
logger.log(log4js.levels.getLevel('LVL1'), 'Event 2');
logger.log('LVL1', 'Event 3');
logger.lvl1('Event 4');
logger.setLevel(Level.forName('LVL2', 7000));
logger.lvl1('Event 5');
logger.lvl2('Event 5');
const events = recording.replay();
t.test('should show log events with new log level', (assert) => {
assert.equal(events[0].level.toString(), 'LVL1');
@ -81,44 +108,126 @@ test('../../lib/logger', (batch) => {
});
batch.test('creating a new log level with incorrect parameters', (t) => {
log4js.levels.forName(9000, 'FAIL_LEVEL_1');
log4js.levels.forName('FAIL_LEVEL_2');
t.throws(() => {
log4js.configure({
levels: {
cheese: 'biscuits'
},
appenders: { stdout: { type: 'stdout' } },
categories: { default: { appenders: ['stdout'], level: 'trace' } }
});
}, new Error(
'Problem with log4js configuration: ' +
"({ levels: { cheese: 'biscuits' },\n appenders: { stdout: { type: 'stdout' } },\n" +
" categories: { default: { appenders: [ 'stdout' ], level: 'trace' } } }) - " +
'level "cheese" must have an integer value'
));
t.throws(() => {
log4js.configure({
levels: {
'#pants': 3
},
appenders: { stdout: { type: 'stdout' } },
categories: { default: { appenders: ['stdout'], level: 'trace' } }
});
}, new Error(
'Problem with log4js configuration: ' +
"({ levels: { '#pants': 3 },\n appenders: { stdout: { type: 'stdout' } },\n" +
" categories: { default: { appenders: [ 'stdout' ], level: 'trace' } } }) - " +
'level name "#pants" is not a valid identifier (must start with a letter, only contain A-Z,a-z,0-9,_)'
));
t.throws(() => {
log4js.configure({
levels: {
'thing#pants': 3
},
appenders: { stdout: { type: 'stdout' } },
categories: { default: { appenders: ['stdout'], level: 'trace' } }
});
}, new Error(
'Problem with log4js configuration: ' +
"({ levels: { 'thing#pants': 3 },\n appenders: { stdout: { type: 'stdout' } },\n" +
" categories: { default: { appenders: [ 'stdout' ], level: 'trace' } } }) - " +
'level name "thing#pants" is not a valid identifier (must start with a letter, only contain A-Z,a-z,0-9,_)'
));
t.throws(() => {
log4js.configure({
levels: {
'1pants': 3
},
appenders: { stdout: { type: 'stdout' } },
categories: { default: { appenders: ['stdout'], level: 'trace' } }
});
}, new Error(
'Problem with log4js configuration: ' +
"({ levels: { '1pants': 3 },\n appenders: { stdout: { type: 'stdout' } },\n" +
" categories: { default: { appenders: [ 'stdout' ], level: 'trace' } } }) - " +
'level name "1pants" is not a valid identifier (must start with a letter, only contain A-Z,a-z,0-9,_)'
));
t.throws(() => {
log4js.configure({
levels: {
2: 3
},
appenders: { stdout: { type: 'stdout' } },
categories: { default: { appenders: ['stdout'], level: 'trace' } }
});
}, new Error(
'Problem with log4js configuration: ' +
"({ levels: { '2': 3 },\n appenders: { stdout: { type: 'stdout' } },\n" +
" categories: { default: { appenders: [ 'stdout' ], level: 'trace' } } }) - " +
'level name "2" is not a valid identifier (must start with a letter, only contain A-Z,a-z,0-9,_)'
));
t.throws(() => {
log4js.configure({
levels: {
'cheese!': 3
},
appenders: { stdout: { type: 'stdout' } },
categories: { default: { appenders: ['stdout'], level: 'trace' } }
});
}, new Error(
'Problem with log4js configuration: ' +
"({ levels: { 'cheese!': 3 },\n appenders: { stdout: { type: 'stdout' } },\n" +
" categories: { default: { appenders: [ 'stdout' ], level: 'trace' } } }) - " +
'level name "cheese!" is not a valid identifier (must start with a letter, only contain A-Z,a-z,0-9,_)'
));
t.test('should fail to create the level', (assert) => {
assert.notOk(Level.FAIL_LEVEL_1);
assert.notOk(Level.FAIL_LEVEL_2);
assert.end();
});
t.end();
});
batch.test('calling log with an undefined log level', (t) => {
const events = [];
const logger = new Logger();
logger.addListener('log', (logEvent) => {
events.push(logEvent);
log4js.configure({
appenders: { recorder: { type: 'recording' } },
categories: { default: { appenders: ['recorder'], level: 'trace' } }
});
logger.log('LEVEL_DOES_NEXT_EXIST', 'Event 1');
logger.log(Level.forName('LEVEL_DOES_NEXT_EXIST'), 'Event 2');
const logger = log4js.getLogger();
logger.log('LEVEL_DOES_NEXT_EXIST', 'Event 1');
logger.log(log4js.levels.getLevel('LEVEL_DOES_NEXT_EXIST'), 'Event 2');
const events = recording.replay();
t.equal(events[0].level.toString(), 'INFO', 'should fall back to INFO');
t.equal(events[1].level.toString(), 'INFO', 'should fall back to INFO');
t.end();
});
batch.test('creating a new level with an existing level name', (t) => {
const events = [];
const logger = new Logger();
logger.addListener('log', (logEvent) => {
events.push(logEvent);
log4js.configure({
levels: {
info: 1234
},
appenders: { stdout: { type: 'stdout' } },
categories: { default: { appenders: ['stdout'], level: 'trace' } }
});
logger.log(log4js.levels.forName('MY_LEVEL', 9000), 'Event 1');
logger.log(log4js.levels.forName('MY_LEVEL', 8000), 'Event 1');
t.equal(events[0].level.level, 9000, 'should override the existing log level');
t.equal(events[1].level.level, 8000, 'should override the existing log level');
t.equal(log4js.levels.INFO.level, 1234, 'should override the existing log level');
t.end();
});
batch.end();

View File

@ -1,184 +1,127 @@
'use strict';
const test = require('tap').test;
const log4js = require('../../lib/log4js');
// const log4js = require('../../lib/log4js');
const sandbox = require('sandboxed-module');
function setupLogging(category, options) {
const msgs = [];
const redisCredentials = {
type: options.type,
host: options.host,
port: options.port,
pass: options.pass,
channel: options.channel,
layout: options.layout
};
const fakeRedis = {
msgs: [],
createClient: function (port, host, optionR) {
this.port = port;
this.host = host;
this.optionR = {};
this.optionR.auth_pass = optionR.pass;
this.optionR = optionR;
return {
on: function (event, callback) {
callback('throw redis error #1');
fakeRedis.errorCb = callback;
},
publish: function (channel, message, callback) {
msgs.push(message);
callback(null, {status: 'sent'});
fakeRedis.msgs.push({ channel: channel, message: message });
fakeRedis.publishCb = callback;
}
};
}
};
const fakeLayouts = {
layout: function (type, config) {
this.type = type;
this.config = config;
return log4js.layouts.messagePassThroughLayout;
},
basicLayout: log4js.layouts.basicLayout,
coloredLayout: log4js.layouts.coloredLayout,
messagePassThroughLayout: log4js.layouts.messagePassThroughLayout
};
const fakeUtil = {
inspect: function (item) {
return JSON.stringify(item);
}
};
const fakeConsole = {
errors: [],
logs: [],
error: function (msg, value) {
this.errors.push({ msg: msg, value: value });
},
log: function (msg, value) {
this.logs.push({ msg: msg, value: value });
error: function (msg) {
this.errors.push(msg);
}
};
const redisModule = sandbox.require('../../lib/appenders/redis', {
const log4js = sandbox.require('../../lib/log4js', {
requires: {
'redis': fakeRedis,
'../layouts': fakeLayouts,
'util': fakeUtil
redis: fakeRedis
},
globals: {
console: fakeConsole
}
});
log4js.addAppender(redisModule.configure(options), category);
log4js.configure({
appenders: { redis: options },
categories: { default: { appenders: ['redis'], level: 'trace' } }
});
return {
logger: log4js.getLogger(category),
redis: fakeRedis,
layouts: fakeLayouts,
console: fakeConsole,
messages: msgs,
credentials: redisCredentials
fakeRedis: fakeRedis,
fakeConsole: fakeConsole
};
}
function checkMessages(assert, result) {
for (let i = 0; i < result.messages.length; i++) {
assert.ok(new RegExp(`Log event #${i + 1}`).test(result.messages[i]));
}
}
log4js.clearAppenders();
test('log4js redisAppender', (batch) => {
batch.test('redis setup', (t) => {
const result = setupLogging('redis setup', {
host: '127.0.0.1',
port: 6739,
host: '123.123.123.123',
port: 1234,
pass: '123456',
channel: 'log',
type: 'redis',
layout: {
type: 'pattern',
pattern: '%d{yyyy-MM-dd hh:mm:ss:SSS}#%p#%m'
pattern: 'cheese %m'
}
});
result.logger.info('Log event #1');
result.fakeRedis.publishCb();
t.test('redis credentials should match', (assert) => {
assert.equal(result.credentials.host, '127.0.0.1');
assert.equal(result.credentials.port, 6739);
assert.equal(result.credentials.pass, '123456');
assert.equal(result.credentials.channel, 'log');
assert.equal(result.credentials.type, 'redis');
assert.equal(result.credentials.layout.type, 'pattern');
assert.equal(result.credentials.layout.pattern, '%d{yyyy-MM-dd hh:mm:ss:SSS}#%p#%m');
assert.equal(result.fakeRedis.host, '123.123.123.123');
assert.equal(result.fakeRedis.port, 1234);
assert.equal(result.fakeRedis.optionR.auth_pass, '123456');
assert.equal(result.fakeRedis.msgs.length, 1, 'should be one message only');
assert.equal(result.fakeRedis.msgs[0].channel, 'log');
assert.equal(result.fakeRedis.msgs[0].message, 'cheese Log event #1');
assert.end();
});
t.end();
});
batch.test('basic usage', (t) => {
const setup = setupLogging('basic usage', {
host: '127.0.0.1',
port: 6739,
pass: '',
channel: 'log',
batch.test('default values', (t) => {
const setup = setupLogging('defaults', {
type: 'redis',
layout: {
type: 'pattern',
pattern: '%d{yyyy-MM-dd hh:mm:ss:SSS}#%p#%m'
}
channel: 'thing'
});
setup.logger.info('Log event #1');
setup.logger.info('just testing');
setup.fakeRedis.publishCb();
t.test('should use localhost', (assert) => {
assert.equal(setup.fakeRedis.host, '127.0.0.1');
assert.equal(setup.fakeRedis.port, 6379);
assert.same(setup.fakeRedis.optionR, {});
assert.end();
});
t.test('should use message pass through layout', (assert) => {
assert.equal(setup.fakeRedis.msgs.length, 1);
assert.equal(setup.fakeRedis.msgs[0].channel, 'thing');
assert.equal(setup.fakeRedis.msgs[0].message, 'just testing');
assert.end();
});
t.equal(setup.messages.length, 1, 'should be one message only');
checkMessages(t, setup);
t.end();
});
batch.test('redis errors', (t) => {
const setup = setupLogging('errors', { type: 'redis', channel: 'testing' });
batch.test('config with layout', (t) => {
const result = setupLogging('config with layout', {
layout: {
type: 'redis'
}
setup.fakeRedis.errorCb('oh no, error on connect');
setup.logger.info('something something');
setup.fakeRedis.publishCb('oh no, error on publish');
t.test('should go to the console', (assert) => {
assert.equal(setup.fakeConsole.errors.length, 2);
assert.equal(setup.fakeConsole.errors[0], 'log4js.redisAppender - 127.0.0.1:6379 Error: \'oh no, error on connect\'');
assert.equal(setup.fakeConsole.errors[1], 'log4js.redisAppender - 127.0.0.1:6379 Error: \'oh no, error on publish\'');
assert.end();
});
t.equal(result.layouts.type, 'redis', 'should configure layout');
t.end();
});
batch.test('separate notification for each event', (t) => {
const setup = setupLogging('separate notification for each event', {
host: '127.0.0.1',
port: 6739,
pass: '',
channel: 'log',
type: 'redis',
layout: {
type: 'pattern',
pattern: '%d{yyyy-MM-dd hh:mm:ss:SSS}#%p#%m'
}
});
setTimeout(() => {
setup.logger.info('Log event #1');
}, 0);
setTimeout(() => {
setup.logger.info('Log event #2');
}, 500);
setTimeout(() => {
setup.logger.info('Log event #3');
}, 1100);
setTimeout(() => {
t.equal(setup.messages.length, 3, 'should be three messages');
checkMessages(t, setup);
t.end();
}, 3000);
});
batch.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

@ -15,12 +15,12 @@ const logger = log4js.getLogger('test-setLevel-asymmetry');
// Define the array of levels as string to iterate over.
const strLevels = ['Trace', 'Debug', 'Info', 'Warn', 'Error', 'Fatal'];
const log4jsLevels = strLevels.map(log4js.levels.toLevel);
const log4jsLevels = strLevels.map(log4js.levels.getLevel);
test('log4js setLevel', (batch) => {
strLevels.forEach((strLevel) => {
batch.test(`is called with a ${strLevel} as string`, (t) => {
const log4jsLevel = log4js.levels.toLevel(strLevel);
const log4jsLevel = log4js.levels.getLevel(strLevel);
t.test('should convert string to level correctly', (assert) => {
logger.setLevel(strLevel);

View File

@ -1,8 +1,8 @@
'use strict';
const test = require('tap').test;
const log4js = require('../../lib/log4js');
const sandbox = require('sandboxed-module');
const realLayouts = require('../../lib/layouts');
function setupLogging(category, options) {
const msgs = [];
@ -32,11 +32,11 @@ function setupLogging(category, options) {
layout: function (type, config) {
this.type = type;
this.config = config;
return log4js.layouts.messagePassThroughLayout;
return realLayouts.messagePassThroughLayout;
},
basicLayout: log4js.layouts.basicLayout,
coloredLayout: log4js.layouts.coloredLayout,
messagePassThroughLayout: log4js.layouts.messagePassThroughLayout
basicLayout: realLayouts.basicLayout,
coloredLayout: realLayouts.coloredLayout,
messagePassThroughLayout: realLayouts.messagePassThroughLayout
};
const fakeConsole = {
@ -50,17 +50,25 @@ function setupLogging(category, options) {
}
};
const slackModule = sandbox.require('../../lib/appenders/slack', {
const log4js = sandbox.require('../../lib/log4js', {
requires: {
'slack-node': fakeSlack,
'../layouts': fakeLayouts
'./layouts': fakeLayouts
},
globals: {
console: fakeConsole
}
});
log4js.addAppender(slackModule.configure(options), category);
options.type = 'slack';
log4js.configure({
appenders: {
slack: options
},
categories: {
default: { appenders: ['slack'], level: 'trace' }
}
});
return {
logger: log4js.getLogger(category),
@ -80,8 +88,6 @@ function checkMessages(assert, result) {
}
}
log4js.clearAppenders();
test('log4js slackAppender', (batch) => {
batch.test('slack setup', (t) => {
const result = setupLogging('slack setup', {

View File

@ -1,10 +1,10 @@
'use strict';
const test = require('tap').test;
const log4js = require('../../lib/log4js');
const realLayouts = require('../../lib/layouts');
const sandbox = require('sandboxed-module');
function setupLogging(category, options) {
function setupLogging(category, options, errorOnSend) {
const msgs = [];
const fakeMailer = {
@ -12,6 +12,10 @@ function setupLogging(category, options) {
return {
config: opts,
sendMail: function (msg, callback) {
if (errorOnSend) {
callback({ message: errorOnSend });
return;
}
msgs.push(msg);
callback(null, true);
},
@ -25,10 +29,10 @@ function setupLogging(category, options) {
layout: function (type, config) {
this.type = type;
this.config = config;
return log4js.layouts.messagePassThroughLayout;
return realLayouts.messagePassThroughLayout;
},
basicLayout: log4js.layouts.basicLayout,
messagePassThroughLayout: log4js.layouts.messagePassThroughLayout
basicLayout: realLayouts.basicLayout,
messagePassThroughLayout: realLayouts.messagePassThroughLayout
};
const fakeConsole = {
@ -38,23 +42,23 @@ function setupLogging(category, options) {
}
};
const fakeTransportPlugin = function () {
};
const smtpModule = sandbox.require('../../lib/appenders/smtp', {
singleOnly: true,
const log4js = sandbox.require('../../lib/log4js', {
requires: {
nodemailer: fakeMailer,
'nodemailer-sendmail-transport': fakeTransportPlugin,
'nodemailer-smtp-transport': fakeTransportPlugin,
'../layouts': fakeLayouts
'./layouts': fakeLayouts
},
globals: {
console: fakeConsole
}
});
log4js.addAppender(smtpModule.configure(options), category);
options.type = 'smtp';
log4js.configure({
appenders: {
smtp: options
},
categories: { default: { appenders: ['smtp'], level: 'trace' } }
});
return {
logger: log4js.getLogger(category),
@ -74,8 +78,6 @@ function checkMessages(assert, result, sender, subject) {
}
}
log4js.clearAppenders();
test('log4js smtpAppender', (batch) => {
batch.test('minimal config', (t) => {
const setup = setupLogging('minimal config', {
@ -189,17 +191,7 @@ test('log4js smtpAppender', (batch) => {
recipients: 'recipient@domain.com',
sendInterval: 0,
SMTP: { port: 25, auth: { user: 'user@domain.com' } }
});
setup.mailer.createTransport = function () {
return {
sendMail: function (msg, cb) {
cb({ message: 'oh noes' });
},
close: function () {
}
};
};
}, 'oh noes');
setup.logger.info('This will break');

View File

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

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

View File

@ -2,16 +2,17 @@
const test = require('tap').test;
const log4js = require('../../lib/log4js');
const levels = require('../../lib/levels');
test('subcategories', (batch) => {
batch.test('loggers created after levels configuration is loaded', (t) => {
log4js.configure({
levels: {
sub1: 'WARN',
'sub1.sub11': 'TRACE',
'sub1.sub11.sub111': 'WARN',
'sub1.sub12': 'INFO'
appenders: { stdout: { type: 'stdout' } },
categories: {
default: { appenders: ['stdout'], level: 'TRACE' },
sub1: { appenders: ['stdout'], level: 'WARN' },
'sub1.sub11': { appenders: ['stdout'], level: 'TRACE' },
'sub1.sub11.sub111': { appenders: ['stdout'], level: 'WARN' },
'sub1.sub12': { appenders: ['stdout'], level: 'INFO' }
}
});
@ -28,15 +29,15 @@ test('subcategories', (batch) => {
};
t.test('check logger levels', (assert) => {
assert.equal(loggers.sub1.level, levels.WARN);
assert.equal(loggers.sub11.level, levels.TRACE);
assert.equal(loggers.sub111.level, levels.WARN);
assert.equal(loggers.sub12.level, levels.INFO);
assert.equal(loggers.sub1.level, log4js.levels.WARN);
assert.equal(loggers.sub11.level, log4js.levels.TRACE);
assert.equal(loggers.sub111.level, log4js.levels.WARN);
assert.equal(loggers.sub12.level, log4js.levels.INFO);
assert.equal(loggers.sub13.level, levels.WARN);
assert.equal(loggers.sub112.level, levels.TRACE);
assert.equal(loggers.sub121.level, levels.INFO);
assert.equal(loggers.sub0.level, levels.TRACE);
assert.equal(loggers.sub13.level, log4js.levels.WARN);
assert.equal(loggers.sub112.level, log4js.levels.TRACE);
assert.equal(loggers.sub121.level, log4js.levels.INFO);
assert.equal(loggers.sub0.level, log4js.levels.TRACE);
assert.end();
});
@ -44,6 +45,13 @@ test('subcategories', (batch) => {
});
batch.test('loggers created before levels configuration is loaded', (t) => {
// reset to defaults
log4js.configure({
appenders: { stdout: { type: 'stdout' } },
categories: { default: { appenders: ['stdout'], level: 'info' } }
});
// these should all get the default log level of INFO
const loggers = {
sub1: log4js.getLogger('sub1'), // WARN
sub11: log4js.getLogger('sub1.sub11'), // TRACE
@ -57,24 +65,27 @@ test('subcategories', (batch) => {
};
log4js.configure({
levels: {
sub1: 'WARN',
'sub1.sub11': 'TRACE',
'sub1.sub11.sub111': 'WARN',
'sub1.sub12': 'INFO'
appenders: { stdout: { type: 'stdout' } },
categories: {
default: { appenders: ['stdout'], level: 'TRACE' },
sub1: { appenders: ['stdout'], level: 'WARN' },
'sub1.sub11': { appenders: ['stdout'], level: 'TRACE' },
'sub1.sub11.sub111': { appenders: ['stdout'], level: 'WARN' },
'sub1.sub12': { appenders: ['stdout'], level: 'INFO' }
}
});
t.test('check logger levels', (assert) => {
assert.equal(loggers.sub1.level, levels.WARN);
assert.equal(loggers.sub11.level, levels.TRACE);
assert.equal(loggers.sub111.level, levels.WARN);
assert.equal(loggers.sub12.level, levels.INFO);
t.test('will not get new levels', (assert) => {
// can't use .equal because by calling log4js.configure we create new instances
assert.same(loggers.sub1.level, log4js.levels.INFO);
assert.same(loggers.sub11.level, log4js.levels.INFO);
assert.same(loggers.sub111.level, log4js.levels.INFO);
assert.same(loggers.sub12.level, log4js.levels.INFO);
assert.equal(loggers.sub13.level, levels.WARN);
assert.equal(loggers.sub112.level, levels.TRACE);
assert.equal(loggers.sub121.level, levels.INFO);
assert.equal(loggers.sub0.level, levels.TRACE);
assert.same(loggers.sub13.level, log4js.levels.INFO);
assert.same(loggers.sub112.level, log4js.levels.INFO);
assert.same(loggers.sub121.level, log4js.levels.INFO);
assert.same(loggers.sub0.level, log4js.levels.INFO);
assert.end();
});
t.end();

View File

@ -1,23 +0,0 @@
{
"appenders": [
{
"type": "categoryFilter",
"exclude": "web",
"appender": {
"type": "file",
"filename": "test/tap/categoryFilter-noweb.log",
"layout": {
"type": "messagePassThrough"
}
}
},
{
"category": "web",
"type": "file",
"filename": "test/tap/categoryFilter-web.log",
"layout": {
"type": "messagePassThrough"
}
}
]
}

View File

@ -1,17 +0,0 @@
{
"appenders": [
{
"category": "tests",
"type": "dateFile",
"filename": "test/tap/date-file-test.log",
"pattern": "-from-MM-dd",
"layout": {
"type": "messagePassThrough"
}
}
],
"levels": {
"tests": "WARN"
}
}

View File

@ -1,41 +0,0 @@
{
"appenders": [
{
"category": "tests",
"type": "logLevelFilter",
"level": "WARN",
"appender": {
"type": "file",
"filename": "test/tap/logLevelFilter-warnings.log",
"layout": {
"type": "messagePassThrough"
}
}
},
{
"category": "tests",
"type": "logLevelFilter",
"level": "TRACE",
"maxLevel": "DEBUG",
"appender": {
"type": "file",
"filename": "test/tap/logLevelFilter-debugs.log",
"layout": {
"type": "messagePassThrough"
}
}
},
{
"category": "tests",
"type": "file",
"filename": "test/tap/logLevelFilter.log",
"layout": {
"type": "messagePassThrough"
}
}
],
"levels": {
"tests": "TRACE"
}
}

11
v2-changes.md Normal file
View File

@ -0,0 +1,11 @@
CHANGES
=======
- no exit listeners defined for appenders by default. users should call log4js.shutdown in their exit listeners.
- context added to loggers (only logstash uses it so far)
- logstash split into two appenders (udp and http)
- no cwd, reload options in config
- configure only by calling configure, no manual adding of appenders, etc
- config format changed a lot, now need to define named appenders and at least one category
- appender format changed, will break any non-core appenders (maybe create adapter?)
- no replacement of console functions