mirror of
https://github.com/log4js-node/log4js-node.git
synced 2025-12-08 19:26:01 +00:00
commit
406084ab3a
@ -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;
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
89
lib/appenders/logFaces-HTTP.js
Normal file
89
lib/appenders/logFaces-HTTP.js
Normal 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;
|
||||
90
lib/appenders/logFaces-UDP.js
Normal file
90
lib/appenders/logFaces-UDP.js
Normal 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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
28
lib/appenders/recording.js
Normal file
28
lib/appenders/recording.js
Normal 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
|
||||
};
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
179
lib/configuration.js
Normal 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;
|
||||
@ -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 };
|
||||
};
|
||||
|
||||
140
lib/levels.js
140
lib/levels.js
@ -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;
|
||||
};
|
||||
|
||||
503
lib/log4js.js
503
lib/log4js.js
@ -1,23 +1,13 @@
|
||||
/* eslint no-prototype-builtins:1,no-restricted-syntax:[1, "ForInStatement"],no-plusplus:0 */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @fileoverview log4js is a library to log in JavaScript in similar manner
|
||||
* than in log4j for Java. The API should be nearly the same.
|
||||
* than in log4j for Java (but not really).
|
||||
*
|
||||
* <h3>Example:</h3>
|
||||
* <pre>
|
||||
* let logging = require('log4js');
|
||||
* //add an appender that logs all messages to stdout.
|
||||
* logging.addAppender(logging.consoleAppender());
|
||||
* //add an appender that logs 'some-category' to a file
|
||||
* logging.addAppender(logging.fileAppender('file.log'), 'some-category');
|
||||
* //get a logger
|
||||
* let log = logging.getLogger('some-category');
|
||||
* log.setLevel(logging.levels.TRACE); //set the Level
|
||||
*
|
||||
* ...
|
||||
* const logging = require('log4js');
|
||||
* const log = logging.getLogger('some-category');
|
||||
*
|
||||
* //call the log
|
||||
* log.trace('trace me' );
|
||||
@ -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);
|
||||
|
||||
182
lib/logger.js
182
lib/logger.js
@ -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;
|
||||
};
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -3,100 +3,7 @@
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
|
||||
function makeTestAppender() {
|
||||
return {
|
||||
configure: function (config, options) {
|
||||
this.configureCalled = true;
|
||||
this.config = config;
|
||||
this.options = options;
|
||||
return this.appender();
|
||||
},
|
||||
appender: function () {
|
||||
const self = this;
|
||||
return function (logEvt) {
|
||||
self.logEvt = logEvt;
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test('log4js configure', (batch) => {
|
||||
batch.test('when appenders specified by type', (t) => {
|
||||
const testAppender = makeTestAppender();
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: {
|
||||
'./appenders/cheese': testAppender
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: [
|
||||
{ type: 'cheese', flavour: 'gouda' }
|
||||
]
|
||||
},
|
||||
{ pants: 'yes' }
|
||||
);
|
||||
t.ok(testAppender.configureCalled, 'should load appender');
|
||||
t.equal(testAppender.config.flavour, 'gouda', 'should pass config to appender');
|
||||
t.equal(testAppender.options.pants, 'yes', 'should pass log4js options to appender');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when core appender loaded via loadAppender', (t) => {
|
||||
const testAppender = makeTestAppender();
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: { './appenders/cheese': testAppender }
|
||||
}
|
||||
);
|
||||
|
||||
log4js.loadAppender('cheese');
|
||||
|
||||
t.ok(log4js.appenders.cheese, 'should load appender from ../../lib/appenders');
|
||||
t.type(log4js.appenderMakers.cheese, 'function', 'should add appender configure function to appenderMakers');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when appender in node_modules loaded via loadAppender', (t) => {
|
||||
const testAppender = makeTestAppender();
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: { 'some/other/external': testAppender }
|
||||
}
|
||||
);
|
||||
|
||||
log4js.loadAppender('some/other/external');
|
||||
t.ok(log4js.appenders['some/other/external'], 'should load appender via require');
|
||||
t.type(
|
||||
log4js.appenderMakers['some/other/external'], 'function',
|
||||
'should add appender configure function to appenderMakers'
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when appender object loaded via loadAppender', (t) => {
|
||||
const testAppender = makeTestAppender();
|
||||
const log4js = sandbox.require('../../lib/log4js');
|
||||
|
||||
log4js.loadAppender('some/other/external', testAppender);
|
||||
|
||||
t.ok(log4js.appenders['some/other/external'], 'should load appender with provided object');
|
||||
t.type(
|
||||
log4js.appenderMakers['some/other/external'], 'function',
|
||||
'should add appender configure function to appenderMakers'
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when configuration file loaded via LOG4JS_CONFIG env variable', (t) => {
|
||||
process.env.LOG4JS_CONFIG = 'some/path/to/mylog4js.json';
|
||||
let fileRead = 0;
|
||||
@ -106,8 +13,10 @@ test('log4js configure', (batch) => {
|
||||
|
||||
const fakeFS = {
|
||||
config: {
|
||||
appenders: [{ type: 'console', layout: { type: 'messagePassThrough' } }],
|
||||
levels: { 'a-test': 'INFO' }
|
||||
appenders: {
|
||||
console: { type: 'console', layout: { type: 'messagePassThrough' } }
|
||||
},
|
||||
categories: { default: { appenders: ['console'], level: 'INFO' } }
|
||||
},
|
||||
readdirSync: function (dir) {
|
||||
return require('fs').readdirSync(dir);
|
||||
|
||||
300
test/tap/configuration-validation-test.js
Normal file
300
test/tap/configuration-validation-test.js
Normal file
@ -0,0 +1,300 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const Configuration = require('../../lib/configuration');
|
||||
const util = require('util');
|
||||
const sandbox = require('sandboxed-module');
|
||||
|
||||
function testAppender(label) {
|
||||
return {
|
||||
configure: function (config, layouts, findAppender) {
|
||||
return {
|
||||
configureCalled: true,
|
||||
type: config.type,
|
||||
label: label,
|
||||
config: config,
|
||||
layouts: layouts,
|
||||
findAppender: findAppender
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test('log4js configuration validation', (batch) => {
|
||||
batch.test('should give error if config is just plain silly', (t) => {
|
||||
[null, undefined, '', []].forEach((config) => {
|
||||
const expectedError = new Error(
|
||||
`Problem with log4js configuration: (${util.inspect(config)}) - must be an object.`
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration(config),
|
||||
expectedError
|
||||
);
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if config is an empty object', (t) => {
|
||||
const expectedError = new Error(
|
||||
'Problem with log4js configuration: ({}) - must have a property "appenders" of type object.'
|
||||
);
|
||||
t.throws(() => new Configuration({}), expectedError);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if config has no appenders', (t) => {
|
||||
const expectedError = new Error(
|
||||
'Problem with log4js configuration: ({ categories: {} }) - must have a property "appenders" of type object.'
|
||||
);
|
||||
t.throws(() => new Configuration({ categories: {} }), expectedError);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if config has no categories', (t) => {
|
||||
const expectedError = new Error(
|
||||
'Problem with log4js configuration: ({ appenders: {} }) - must have a property "categories" of type object.'
|
||||
);
|
||||
t.throws(() => new Configuration({ appenders: {} }), expectedError);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if appenders is not an object', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ({ appenders: [], categories: [] })' +
|
||||
' - must have a property "appenders" of type object.'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: [], categories: [] }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if appenders are not all valid', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ({ appenders: { thing: \'cheese\' }, categories: {} })' +
|
||||
' - appender "thing" is not valid (must be an object with property "type")'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: { thing: 'cheese' }, categories: {} }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should require at least one appender', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ({ appenders: {}, categories: {} })' +
|
||||
' - must define at least one appender.'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: {}, categories: {} }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if categories are not all valid', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n categories: { thing: \'cheese\' } })' +
|
||||
' - category "thing" is not valid (must be an object with properties "appenders" and "level")'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: { stdout: { type: 'stdout' } }, categories: { thing: 'cheese' } }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if default category not defined', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n' +
|
||||
' categories: { thing: { appenders: [ \'stdout\' ], level: \'ERROR\' } } })' +
|
||||
' - must define a "default" category.'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: ['stdout'], level: 'ERROR' } } }
|
||||
),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should require at least one category', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ({ appenders: { stdout: { type: \'stdout\' } }, categories: {} })' +
|
||||
' - must define at least one category.'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({ appenders: { stdout: { type: 'stdout' } }, categories: {} }),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if category.appenders is not an array', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n' +
|
||||
' categories: { thing: { appenders: {}, level: \'ERROR\' } } })' +
|
||||
' - category "thing" is not valid (appenders must be an array of appender names)'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: {}, level: 'ERROR' } }
|
||||
}),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if category.appenders is empty', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n' +
|
||||
' categories: { thing: { appenders: [], level: \'ERROR\' } } })' +
|
||||
' - category "thing" is not valid (appenders must contain at least one appender name)'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: [], level: 'ERROR' } }
|
||||
}),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if categories do not refer to valid appenders', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n' +
|
||||
' categories: { thing: { appenders: [ \'cheese\' ], level: \'ERROR\' } } })' +
|
||||
' - category "thing" is not valid (appender "cheese" is not defined)'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { thing: { appenders: ['cheese'], level: 'ERROR' } }
|
||||
}),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if category level is not valid', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { stdout: { type: \'stdout\' } },\n' +
|
||||
' categories: { default: { appenders: [ \'stdout\' ], level: \'Biscuits\' } } })' +
|
||||
' - category "default" is not valid (level "Biscuits" not recognised; ' +
|
||||
'valid levels are ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, MARK, OFF)'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { stdout: { type: 'stdout' } },
|
||||
categories: { default: { appenders: ['stdout'], level: 'Biscuits' } }
|
||||
}),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should give error if appender type cannot be found', (t) => {
|
||||
const error = new Error(
|
||||
'Problem with log4js configuration: ' +
|
||||
'({ appenders: { thing: { type: \'cheese\' } },\n' +
|
||||
' categories: { default: { appenders: [ \'thing\' ], level: \'ERROR\' } } })' +
|
||||
' - appender "thing" is not valid (type "cheese" could not be found)'
|
||||
);
|
||||
t.throws(
|
||||
() => new Configuration({
|
||||
appenders: { thing: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
}),
|
||||
error
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should create appender instances', (t) => {
|
||||
const SandboxedConfiguration = sandbox.require(
|
||||
'../../lib/configuration',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: {
|
||||
cheese: testAppender('cheesy')
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = new SandboxedConfiguration({
|
||||
appenders: { thing: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
});
|
||||
|
||||
const thing = config.appenders.get('thing');
|
||||
t.ok(thing.configureCalled);
|
||||
t.equal(thing.type, 'cheese');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should load appenders from core first', (t) => {
|
||||
const SandboxedConfiguration = sandbox.require(
|
||||
'../../lib/configuration',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: {
|
||||
'./appenders/cheese': testAppender('correct'),
|
||||
cheese: testAppender('wrong')
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = new SandboxedConfiguration({
|
||||
appenders: { thing: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
});
|
||||
|
||||
const thing = config.appenders.get('thing');
|
||||
t.ok(thing.configureCalled);
|
||||
t.equal(thing.type, 'cheese');
|
||||
t.equal(thing.label, 'correct');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('should pass config, layout, findAppender to appenders', (t) => {
|
||||
const SandboxedConfiguration = sandbox.require(
|
||||
'../../lib/configuration',
|
||||
{
|
||||
singleOnly: true,
|
||||
requires: {
|
||||
cheese: testAppender('cheesy')
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = new SandboxedConfiguration({
|
||||
appenders: { thing: { type: 'cheese', foo: 'bar' }, thing2: { type: 'cheese' } },
|
||||
categories: { default: { appenders: ['thing'], level: 'ERROR' } }
|
||||
});
|
||||
|
||||
const thing = config.appenders.get('thing');
|
||||
t.ok(thing.configureCalled);
|
||||
t.equal(thing.type, 'cheese');
|
||||
t.equal(thing.config.foo, 'bar');
|
||||
t.type(thing.layouts, 'object');
|
||||
t.type(thing.layouts.basicLayout, 'function');
|
||||
t.type(thing.findAppender, 'function');
|
||||
t.type(thing.findAppender('thing2'), 'object');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.end();
|
||||
});
|
||||
@ -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();
|
||||
});
|
||||
@ -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');
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
});
|
||||
@ -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');
|
||||
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
|
||||
@ -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();
|
||||
});
|
||||
102
test/tap/logFaces-HTTP-test.js
Normal file
102
test/tap/logFaces-HTTP-test.js
Normal 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();
|
||||
});
|
||||
94
test/tap/logFaces-UDP-test.js
Normal file
94
test/tap/logFaces-UDP-test.js
Normal 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();
|
||||
});
|
||||
@ -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();
|
||||
});
|
||||
@ -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');
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
|
||||
@ -2,82 +2,16 @@
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
|
||||
function setupConsoleTest() {
|
||||
const fakeConsole = {};
|
||||
const logEvents = [];
|
||||
|
||||
['trace', 'debug', 'log', 'info', 'warn', 'error'].forEach((fn) => {
|
||||
fakeConsole[fn] = function () {
|
||||
throw new Error('this should not be called.');
|
||||
};
|
||||
});
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
globals: {
|
||||
console: fakeConsole
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.clearAppenders();
|
||||
log4js.addAppender((evt) => {
|
||||
logEvents.push(evt);
|
||||
});
|
||||
|
||||
return { log4js: log4js, logEvents: logEvents, fakeConsole: fakeConsole };
|
||||
}
|
||||
const recording = require('../../lib/appenders/recording');
|
||||
|
||||
test('log4js', (batch) => {
|
||||
batch.test('getBufferedLogger', (t) => {
|
||||
const log4js = require('../../lib/log4js');
|
||||
log4js.clearAppenders();
|
||||
const logger = log4js.getBufferedLogger('tests');
|
||||
|
||||
t.test('should take a category and return a logger', (assert) => {
|
||||
assert.equal(logger.target.category, 'tests');
|
||||
assert.type(logger.flush, 'function');
|
||||
assert.type(logger.trace, 'function');
|
||||
assert.type(logger.debug, 'function');
|
||||
assert.type(logger.info, 'function');
|
||||
assert.type(logger.warn, 'function');
|
||||
assert.type(logger.error, 'function');
|
||||
assert.type(logger.fatal, 'function');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('cache events', (assert) => {
|
||||
const events = [];
|
||||
logger.target.setLevel('TRACE');
|
||||
logger.target.addListener('log', (logEvent) => {
|
||||
events.push(logEvent);
|
||||
});
|
||||
logger.debug('Debug event');
|
||||
logger.trace('Trace event 1');
|
||||
logger.trace('Trace event 2');
|
||||
logger.warn('Warning event');
|
||||
logger.error('Aargh!', new Error('Pants are on fire!'));
|
||||
logger.error(
|
||||
'Simulated CouchDB problem',
|
||||
{ err: 127, cause: 'incendiary underwear' }
|
||||
);
|
||||
|
||||
assert.equal(events.length, 0, 'should not emit log events if .flush() is not called.');
|
||||
logger.flush();
|
||||
assert.equal(events.length, 6, 'should emit log events when .flush() is called.');
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
batch.test('getLogger', (t) => {
|
||||
const log4js = require('../../lib/log4js');
|
||||
log4js.clearAppenders();
|
||||
log4js.configure({
|
||||
appenders: { recorder: { type: 'recording' } },
|
||||
categories: { default: { appenders: ['recorder'], level: 'DEBUG' } }
|
||||
});
|
||||
const logger = log4js.getLogger('tests');
|
||||
logger.setLevel('DEBUG');
|
||||
|
||||
t.test('should take a category and return a logger', (assert) => {
|
||||
assert.equal(logger.category, 'tests');
|
||||
@ -91,10 +25,8 @@ test('log4js', (batch) => {
|
||||
});
|
||||
|
||||
t.test('log events', (assert) => {
|
||||
const events = [];
|
||||
logger.addListener('log', (logEvent) => {
|
||||
events.push(logEvent);
|
||||
});
|
||||
recording.reset();
|
||||
|
||||
logger.debug('Debug event');
|
||||
logger.trace('Trace event 1');
|
||||
logger.trace('Trace event 2');
|
||||
@ -102,6 +34,8 @@ test('log4js', (batch) => {
|
||||
logger.error('Aargh!', new Error('Pants are on fire!'));
|
||||
logger.error('Simulated CouchDB problem', { err: 127, cause: 'incendiary underwear' });
|
||||
|
||||
const events = recording.replay();
|
||||
|
||||
assert.equal(events[0].level.toString(), 'DEBUG');
|
||||
assert.equal(events[0].data[0], 'Debug event');
|
||||
assert.type(events[0].startTime, 'Date');
|
||||
@ -128,15 +62,16 @@ test('log4js', (batch) => {
|
||||
requires: {
|
||||
'./appenders/file': {
|
||||
name: 'file',
|
||||
appender: function () {
|
||||
},
|
||||
configure: function () {
|
||||
return function () {
|
||||
function thing() {
|
||||
return null;
|
||||
}
|
||||
|
||||
thing.shutdown = function (cb) {
|
||||
events.appenderShutdownCalled = true;
|
||||
cb();
|
||||
};
|
||||
},
|
||||
shutdown: function (cb) {
|
||||
events.appenderShutdownCalled = true;
|
||||
cb();
|
||||
return thing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,108 +79,24 @@ test('log4js', (batch) => {
|
||||
);
|
||||
|
||||
const config = {
|
||||
appenders: [
|
||||
{
|
||||
appenders: {
|
||||
file: {
|
||||
type: 'file',
|
||||
filename: 'cheesy-wotsits.log',
|
||||
maxLogSize: 1024,
|
||||
backups: 3
|
||||
}
|
||||
]
|
||||
},
|
||||
categories: { default: { appenders: ['file'], level: 'DEBUG' } }
|
||||
};
|
||||
|
||||
log4js.configure(config);
|
||||
log4js.shutdown(() => {
|
||||
// Re-enable log writing so other tests that use logger are not
|
||||
// affected.
|
||||
require('../../lib/logger').enableAllLogWrites();
|
||||
t.ok(events.appenderShutdownCalled, 'should invoke appender shutdowns');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
// 'invalid configuration': {
|
||||
// 'should throw an exception': function () {
|
||||
// assert.throws(() => {
|
||||
// // todo: here is weird, it's not ideal test
|
||||
// require('../../lib/log4js').configure({ type: 'invalid' });
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
|
||||
batch.test('configuration when passed as object', (t) => {
|
||||
let appenderConfig;
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'./appenders/file': {
|
||||
name: 'file',
|
||||
appender: function () {
|
||||
},
|
||||
configure: function (configuration) {
|
||||
appenderConfig = configuration;
|
||||
return function () {
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = {
|
||||
appenders: [
|
||||
{
|
||||
type: 'file',
|
||||
filename: 'cheesy-wotsits.log',
|
||||
maxLogSize: 1024,
|
||||
backups: 3
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
log4js.configure(config);
|
||||
t.equal(appenderConfig.filename, 'cheesy-wotsits.log', 'should be passed to appender config');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('configuration that causes an error', (t) => {
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'./appenders/file': {
|
||||
name: 'file',
|
||||
appender: function () {
|
||||
},
|
||||
configure: function () {
|
||||
throw new Error('oh noes');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const config = {
|
||||
appenders: [
|
||||
{
|
||||
type: 'file',
|
||||
filename: 'cheesy-wotsits.log',
|
||||
maxLogSize: 1024,
|
||||
backups: 3
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
try {
|
||||
log4js.configure(config);
|
||||
} catch (e) {
|
||||
t.ok(e.message.includes('log4js configuration problem for'));
|
||||
t.end();
|
||||
}
|
||||
});
|
||||
|
||||
batch.test('configuration when passed as filename', (t) => {
|
||||
let appenderConfig;
|
||||
let configFilename;
|
||||
@ -261,12 +112,13 @@ test('log4js', (batch) => {
|
||||
readFileSync: function (filename) {
|
||||
configFilename = filename;
|
||||
return JSON.stringify({
|
||||
appenders: [
|
||||
{
|
||||
appenders: {
|
||||
file: {
|
||||
type: 'file',
|
||||
filename: 'whatever.log'
|
||||
}
|
||||
]
|
||||
},
|
||||
categories: { default: { appenders: ['file'], level: 'DEBUG' } }
|
||||
});
|
||||
},
|
||||
readdirSync: function () {
|
||||
@ -274,9 +126,6 @@ test('log4js', (batch) => {
|
||||
}
|
||||
},
|
||||
'./appenders/file': {
|
||||
name: 'file',
|
||||
appender: function () {
|
||||
},
|
||||
configure: function (configuration) {
|
||||
appenderConfig = configuration;
|
||||
return function () {
|
||||
@ -295,15 +144,11 @@ test('log4js', (batch) => {
|
||||
|
||||
batch.test('with no appenders defined', (t) => {
|
||||
const fakeStdoutAppender = {
|
||||
name: 'stdout',
|
||||
appender: function () {
|
||||
configure: function () {
|
||||
return function (evt) {
|
||||
t.equal(evt.data[0], 'This is a test', 'should default to the stdout appender');
|
||||
t.end();
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeStdoutAppender.appender();
|
||||
}
|
||||
};
|
||||
|
||||
@ -321,288 +166,18 @@ test('log4js', (batch) => {
|
||||
// assert is back at the top, in the fake stdout appender
|
||||
});
|
||||
|
||||
batch.test('addAppender', (t) => {
|
||||
const log4js = require('../../lib/log4js');
|
||||
log4js.clearAppenders();
|
||||
|
||||
t.test('without a category', (assert) => {
|
||||
let appenderEvent;
|
||||
|
||||
const appender = function (evt) {
|
||||
appenderEvent = evt;
|
||||
};
|
||||
|
||||
const logger = log4js.getLogger('tests');
|
||||
|
||||
log4js.addAppender(appender);
|
||||
logger.debug('This is a test');
|
||||
|
||||
assert.equal(
|
||||
appenderEvent.data[0],
|
||||
'This is a test',
|
||||
'should register the function as a listener for all loggers'
|
||||
);
|
||||
assert.equal(appenderEvent.categoryName, 'tests');
|
||||
assert.equal(appenderEvent.level.toString(), 'DEBUG');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('if an appender for a category is defined', (assert) => {
|
||||
let otherEvent;
|
||||
let appenderEvent;
|
||||
|
||||
log4js.addAppender((evt) => {
|
||||
appenderEvent = evt;
|
||||
});
|
||||
log4js.addAppender((evt) => {
|
||||
otherEvent = evt;
|
||||
}, 'cheese');
|
||||
|
||||
const cheeseLogger = log4js.getLogger('cheese');
|
||||
cheeseLogger.debug('This is a test');
|
||||
|
||||
assert.same(appenderEvent, otherEvent, 'should register for that category');
|
||||
assert.equal(otherEvent.data[0], 'This is a test');
|
||||
assert.equal(otherEvent.categoryName, 'cheese');
|
||||
|
||||
otherEvent = undefined;
|
||||
appenderEvent = undefined;
|
||||
log4js.getLogger('pants').debug('this should not be propagated to otherEvent');
|
||||
assert.notOk(otherEvent);
|
||||
assert.equal(appenderEvent.data[0], 'this should not be propagated to otherEvent');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('with a category', (assert) => {
|
||||
let appenderEvent;
|
||||
|
||||
const appender = function (evt) {
|
||||
appenderEvent = evt;
|
||||
};
|
||||
|
||||
const logger = log4js.getLogger('tests');
|
||||
|
||||
log4js.addAppender(appender, 'tests');
|
||||
logger.debug('this is a category test');
|
||||
assert.equal(
|
||||
appenderEvent.data[0],
|
||||
'this is a category test',
|
||||
'should only register the function as a listener for that category'
|
||||
);
|
||||
|
||||
appenderEvent = undefined;
|
||||
log4js.getLogger('some other category').debug('Cheese');
|
||||
assert.notOk(appenderEvent);
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('with multiple categories', (assert) => {
|
||||
let appenderEvent;
|
||||
|
||||
const appender = function (evt) {
|
||||
appenderEvent = evt;
|
||||
};
|
||||
|
||||
const logger = log4js.getLogger('tests');
|
||||
|
||||
log4js.addAppender(appender, 'tests', 'biscuits');
|
||||
|
||||
logger.debug('this is a test');
|
||||
assert.equal(
|
||||
appenderEvent.data[0],
|
||||
'this is a test',
|
||||
'should register the function as a listener for all the categories'
|
||||
);
|
||||
|
||||
appenderEvent = undefined;
|
||||
const otherLogger = log4js.getLogger('biscuits');
|
||||
otherLogger.debug('mmm... garibaldis');
|
||||
assert.equal(appenderEvent.data[0], 'mmm... garibaldis');
|
||||
|
||||
appenderEvent = undefined;
|
||||
|
||||
log4js.getLogger('something else').debug('pants');
|
||||
assert.notOk(appenderEvent);
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('should register the function when the list of categories is an array', (assert) => {
|
||||
let appenderEvent;
|
||||
|
||||
const appender = function (evt) {
|
||||
appenderEvent = evt;
|
||||
};
|
||||
|
||||
log4js.addAppender(appender, ['tests', 'pants']);
|
||||
|
||||
log4js.getLogger('tests').debug('this is a test');
|
||||
assert.equal(appenderEvent.data[0], 'this is a test');
|
||||
|
||||
appenderEvent = undefined;
|
||||
|
||||
log4js.getLogger('pants').debug('big pants');
|
||||
assert.equal(appenderEvent.data[0], 'big pants');
|
||||
|
||||
appenderEvent = undefined;
|
||||
|
||||
log4js.getLogger('something else').debug('pants');
|
||||
assert.notOk(appenderEvent);
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('default setup', (t) => {
|
||||
const appenderEvents = [];
|
||||
|
||||
const fakeStdout = {
|
||||
name: 'stdout',
|
||||
appender: function () {
|
||||
return function (evt) {
|
||||
appenderEvents.push(evt);
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeStdout.appender();
|
||||
}
|
||||
};
|
||||
|
||||
const globalConsole = {
|
||||
log: function () {
|
||||
}
|
||||
};
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'./appenders/stdout': fakeStdout
|
||||
},
|
||||
globals: {
|
||||
console: globalConsole
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const logger = log4js.getLogger('a-test');
|
||||
|
||||
logger.debug('this is a test');
|
||||
globalConsole.log('this should not be logged');
|
||||
|
||||
t.equal(appenderEvents[0].data[0], 'this is a test', 'should configure a stdout appender');
|
||||
t.equal(appenderEvents.length, 1, 'should not replace console.log with log4js version');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('console', (t) => {
|
||||
const setup = setupConsoleTest();
|
||||
|
||||
t.test('when replaceConsole called', (assert) => {
|
||||
setup.log4js.replaceConsole();
|
||||
|
||||
setup.fakeConsole.log('Some debug message someone put in a module');
|
||||
setup.fakeConsole.debug('Some debug');
|
||||
setup.fakeConsole.error('An error');
|
||||
setup.fakeConsole.info('some info');
|
||||
setup.fakeConsole.warn('a warning');
|
||||
|
||||
setup.fakeConsole.log('cheese (%s) and biscuits (%s)', 'gouda', 'garibaldis');
|
||||
setup.fakeConsole.log({ lumpy: 'tapioca' });
|
||||
setup.fakeConsole.log('count %d', 123);
|
||||
setup.fakeConsole.log('stringify %j', { lumpy: 'tapioca' });
|
||||
|
||||
const logEvents = setup.logEvents;
|
||||
assert.equal(logEvents.length, 9);
|
||||
assert.equal(logEvents[0].data[0], 'Some debug message someone put in a module');
|
||||
assert.equal(logEvents[0].level.toString(), 'INFO');
|
||||
assert.equal(logEvents[1].data[0], 'Some debug');
|
||||
assert.equal(logEvents[1].level.toString(), 'DEBUG');
|
||||
assert.equal(logEvents[2].data[0], 'An error');
|
||||
assert.equal(logEvents[2].level.toString(), 'ERROR');
|
||||
assert.equal(logEvents[3].data[0], 'some info');
|
||||
assert.equal(logEvents[3].level.toString(), 'INFO');
|
||||
assert.equal(logEvents[4].data[0], 'a warning');
|
||||
assert.equal(logEvents[4].level.toString(), 'WARN');
|
||||
assert.equal(logEvents[5].data[0], 'cheese (%s) and biscuits (%s)');
|
||||
assert.equal(logEvents[5].data[1], 'gouda');
|
||||
assert.equal(logEvents[5].data[2], 'garibaldis');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.test('when turned off', (assert) => {
|
||||
setup.log4js.restoreConsole();
|
||||
try {
|
||||
setup.fakeConsole.log('This should cause the error described in the setup');
|
||||
} catch (e) {
|
||||
assert.type(e, 'Error', 'should call the original console methods');
|
||||
assert.equal(e.message, 'this should not be called.');
|
||||
assert.end();
|
||||
}
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('console configuration', (t) => {
|
||||
const setup = setupConsoleTest();
|
||||
|
||||
t.test('when disabled', (assert) => {
|
||||
setup.log4js.replaceConsole();
|
||||
setup.log4js.configure({ replaceConsole: false });
|
||||
try {
|
||||
setup.fakeConsole.log('This should cause the error described in the setup');
|
||||
} catch (e) {
|
||||
assert.type(e, 'Error');
|
||||
assert.equal(e.message, 'this should not be called.');
|
||||
assert.end();
|
||||
}
|
||||
});
|
||||
|
||||
t.test('when enabled', (assert) => {
|
||||
setup.log4js.restoreConsole();
|
||||
setup.log4js.configure({ replaceConsole: true });
|
||||
// log4js.configure clears all appenders
|
||||
setup.log4js.addAppender((evt) => {
|
||||
setup.logEvents.push(evt);
|
||||
});
|
||||
|
||||
setup.fakeConsole.debug('Some debug');
|
||||
|
||||
const logEvents = setup.logEvents;
|
||||
assert.equal(logEvents.length, 1);
|
||||
assert.equal(logEvents[0].level.toString(), 'DEBUG');
|
||||
assert.equal(logEvents[0].data[0], 'Some debug');
|
||||
assert.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('configuration persistence', (t) => {
|
||||
let logEvent;
|
||||
const firstLog4js = require('../../lib/log4js');
|
||||
|
||||
firstLog4js.clearAppenders();
|
||||
firstLog4js.addAppender((evt) => {
|
||||
logEvent = evt;
|
||||
firstLog4js.configure({
|
||||
appenders: { recorder: { type: 'recording' } },
|
||||
categories: { default: { appenders: ['recorder'], level: 'DEBUG' } }
|
||||
});
|
||||
recording.reset();
|
||||
|
||||
const secondLog4js = require('../../lib/log4js');
|
||||
secondLog4js.getLogger().info('This should go to the appender defined in firstLog4js');
|
||||
|
||||
t.equal(logEvent.data[0], 'This should go to the appender defined in firstLog4js');
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('getDefaultLogger', (t) => {
|
||||
const logger = require('../../lib/log4js').getDefaultLogger();
|
||||
|
||||
t.test('should return a logger', (assert) => {
|
||||
assert.ok(logger.info);
|
||||
assert.ok(logger.debug);
|
||||
assert.ok(logger.error);
|
||||
assert.end();
|
||||
});
|
||||
t.equal(recording.replay()[0].data[0], 'This should go to the appender defined in firstLog4js');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
@ -1,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();
|
||||
});
|
||||
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -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', {
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const path = require('path');
|
||||
const sandbox = require('sandboxed-module');
|
||||
|
||||
test('Reload configuration shutdown hook', (t) => {
|
||||
let timerId;
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
globals: {
|
||||
clearInterval: function (id) {
|
||||
timerId = id;
|
||||
},
|
||||
setInterval: function () {
|
||||
return '1234';
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure(
|
||||
path.join(__dirname, 'test-config.json'),
|
||||
{ reloadSecs: 30 }
|
||||
);
|
||||
|
||||
t.plan(1);
|
||||
log4js.shutdown(() => {
|
||||
t.equal(timerId, '1234', 'Shutdown should clear the reload timer');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
@ -1,350 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('tap').test;
|
||||
const sandbox = require('sandboxed-module');
|
||||
|
||||
function setupConsoleTest() {
|
||||
const fakeConsole = {};
|
||||
const logEvents = [];
|
||||
|
||||
['trace', 'debug', 'log', 'info', 'warn', 'error'].forEach((fn) => {
|
||||
fakeConsole[fn] = function () {
|
||||
throw new Error('this should not be called.');
|
||||
};
|
||||
});
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
globals: {
|
||||
console: fakeConsole
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.clearAppenders();
|
||||
log4js.addAppender((evt) => {
|
||||
logEvents.push(evt);
|
||||
});
|
||||
|
||||
return { log4js: log4js, logEvents: logEvents, fakeConsole: fakeConsole };
|
||||
}
|
||||
|
||||
test('reload configuration', (batch) => {
|
||||
batch.test('with config file changing', (t) => {
|
||||
const pathsChecked = [];
|
||||
const logEvents = [];
|
||||
const modulePath = 'path/to/log4js.json';
|
||||
|
||||
const fakeFS = {
|
||||
lastMtime: Date.now(),
|
||||
config: {
|
||||
appenders: [
|
||||
{ type: 'console', layout: { type: 'messagePassThrough' } }
|
||||
],
|
||||
levels: { 'a-test': 'INFO' }
|
||||
},
|
||||
readFileSync: function (file, encoding) {
|
||||
t.equal(file, modulePath);
|
||||
t.equal(encoding, 'utf8');
|
||||
return JSON.stringify(fakeFS.config);
|
||||
},
|
||||
statSync: function (path) {
|
||||
pathsChecked.push(path);
|
||||
if (path === modulePath) {
|
||||
fakeFS.lastMtime += 1;
|
||||
return { mtime: new Date(fakeFS.lastMtime) };
|
||||
}
|
||||
throw new Error('no such file');
|
||||
}
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
name: 'console',
|
||||
appender: function () {
|
||||
return function (evt) {
|
||||
logEvents.push(evt);
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeConsole.appender();
|
||||
}
|
||||
};
|
||||
|
||||
let setIntervalCallback;
|
||||
|
||||
const fakeSetInterval = function (cb) {
|
||||
setIntervalCallback = cb;
|
||||
};
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
fs: fakeFS,
|
||||
'./appenders/console': fakeConsole
|
||||
},
|
||||
globals: {
|
||||
console: fakeConsole,
|
||||
setInterval: fakeSetInterval,
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure('path/to/log4js.json', { reloadSecs: 30 });
|
||||
const logger = log4js.getLogger('a-test');
|
||||
logger.info('info1');
|
||||
logger.debug('debug2 - should be ignored');
|
||||
fakeFS.config.levels['a-test'] = 'DEBUG';
|
||||
setIntervalCallback();
|
||||
logger.info('info3');
|
||||
logger.debug('debug4');
|
||||
|
||||
t.test('should configure log4js from first log4js.json found', (assert) => {
|
||||
assert.equal(logEvents[0].data[0], 'info1');
|
||||
assert.equal(logEvents[1].data[0], 'info3');
|
||||
assert.equal(logEvents[2].data[0], 'debug4');
|
||||
assert.equal(logEvents.length, 3);
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('with config file staying the same', (t) => {
|
||||
const pathsChecked = [];
|
||||
let fileRead = 0;
|
||||
const logEvents = [];
|
||||
const modulePath = require('path').normalize(`${__dirname}/../../lib/log4js.json`);
|
||||
const mtime = new Date();
|
||||
|
||||
const fakeFS = {
|
||||
config: {
|
||||
appenders: [
|
||||
{ type: 'console', layout: { type: 'messagePassThrough' } }
|
||||
],
|
||||
levels: { 'a-test': 'INFO' }
|
||||
},
|
||||
readFileSync: function (file, encoding) {
|
||||
fileRead += 1;
|
||||
t.type(file, 'string');
|
||||
t.equal(file, modulePath);
|
||||
t.equal(encoding, 'utf8');
|
||||
return JSON.stringify(fakeFS.config);
|
||||
},
|
||||
statSync: function (path) {
|
||||
pathsChecked.push(path);
|
||||
if (path === modulePath) {
|
||||
return { mtime: mtime };
|
||||
}
|
||||
throw new Error('no such file');
|
||||
}
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
name: 'console',
|
||||
appender: function () {
|
||||
return function (evt) {
|
||||
logEvents.push(evt);
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeConsole.appender();
|
||||
}
|
||||
};
|
||||
|
||||
let setIntervalCallback;
|
||||
|
||||
const fakeSetInterval = function (cb) {
|
||||
setIntervalCallback = cb;
|
||||
};
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
fs: fakeFS,
|
||||
'./appenders/console': fakeConsole
|
||||
},
|
||||
globals: {
|
||||
console: fakeConsole,
|
||||
setInterval: fakeSetInterval,
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure(modulePath, { reloadSecs: 3 });
|
||||
const logger = log4js.getLogger('a-test');
|
||||
logger.info('info1');
|
||||
logger.debug('debug2 - should be ignored');
|
||||
setIntervalCallback();
|
||||
logger.info('info3');
|
||||
logger.debug('debug4');
|
||||
|
||||
t.equal(fileRead, 1, 'should only read the configuration file once');
|
||||
t.test('should configure log4js from first log4js.json found', (assert) => {
|
||||
assert.equal(logEvents.length, 2);
|
||||
assert.equal(logEvents[0].data[0], 'info1');
|
||||
assert.equal(logEvents[1].data[0], 'info3');
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when config file is removed', (t) => {
|
||||
let fileRead = 0;
|
||||
const logEvents = [];
|
||||
const modulePath = require('path').normalize(`${__dirname}/../../lib/log4js.json`);
|
||||
|
||||
const fakeFS = {
|
||||
config: {
|
||||
appenders: [
|
||||
{ type: 'console', layout: { type: 'messagePassThrough' } }
|
||||
],
|
||||
levels: { 'a-test': 'INFO' }
|
||||
},
|
||||
readFileSync: function (file, encoding) {
|
||||
fileRead += 1;
|
||||
t.type(file, 'string');
|
||||
t.equal(file, modulePath);
|
||||
t.equal(encoding, 'utf8');
|
||||
return JSON.stringify(fakeFS.config);
|
||||
},
|
||||
statSync: function () {
|
||||
this.statSync = function () {
|
||||
throw new Error('no such file');
|
||||
};
|
||||
return { mtime: new Date() };
|
||||
}
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
name: 'console',
|
||||
appender: function () {
|
||||
return function (evt) {
|
||||
logEvents.push(evt);
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeConsole.appender();
|
||||
}
|
||||
};
|
||||
|
||||
let setIntervalCallback;
|
||||
|
||||
const fakeSetInterval = function (cb) {
|
||||
setIntervalCallback = cb;
|
||||
};
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
fs: fakeFS,
|
||||
'./appenders/console': fakeConsole
|
||||
},
|
||||
globals: {
|
||||
console: fakeConsole,
|
||||
setInterval: fakeSetInterval,
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure(modulePath, { reloadSecs: 3 });
|
||||
const logger = log4js.getLogger('a-test');
|
||||
logger.info('info1');
|
||||
logger.debug('debug2 - should be ignored');
|
||||
setIntervalCallback();
|
||||
logger.info('info3');
|
||||
logger.debug('debug4');
|
||||
|
||||
t.equal(fileRead, 1, 'should only read the configuration file once');
|
||||
t.test('should not clear configuration when config file not found', (assert) => {
|
||||
assert.equal(logEvents.length, 3);
|
||||
assert.equal(logEvents[0].data[0], 'info1');
|
||||
assert.equal(logEvents[1].level.toString(), 'WARN');
|
||||
assert.include(logEvents[1].data[0], 'Failed to load configuration file');
|
||||
assert.equal(logEvents[2].data[0], 'info3');
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when passed an object', (t) => {
|
||||
const setup = setupConsoleTest();
|
||||
setup.log4js.configure({}, { reloadSecs: 30 });
|
||||
const events = setup.logEvents;
|
||||
|
||||
t.test('should log a warning', (assert) => {
|
||||
assert.equal(events[0].level.toString(), 'WARN');
|
||||
assert.equal(
|
||||
events[0].data[0],
|
||||
'Ignoring configuration reload parameter for "object" configuration.'
|
||||
);
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.test('when called twice with reload options', (t) => {
|
||||
const modulePath = require('path').normalize(`${__dirname}/../../lib/log4js.json`);
|
||||
|
||||
const fakeFS = {
|
||||
readFileSync: function () {
|
||||
return JSON.stringify({});
|
||||
},
|
||||
statSync: function () {
|
||||
return { mtime: new Date() };
|
||||
}
|
||||
};
|
||||
|
||||
const fakeConsole = {
|
||||
name: 'console',
|
||||
appender: function () {
|
||||
return function () {
|
||||
};
|
||||
},
|
||||
configure: function () {
|
||||
return fakeConsole.appender();
|
||||
}
|
||||
};
|
||||
|
||||
let setIntervalCallback; // eslint-disable-line
|
||||
let intervalCleared = false;
|
||||
let clearedId;
|
||||
|
||||
const fakeSetInterval = function (cb) {
|
||||
setIntervalCallback = cb;
|
||||
return 1234;
|
||||
};
|
||||
|
||||
const log4js = sandbox.require(
|
||||
'../../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
fs: fakeFS,
|
||||
'./appenders/console': fakeConsole
|
||||
},
|
||||
globals: {
|
||||
console: fakeConsole,
|
||||
setInterval: fakeSetInterval,
|
||||
clearInterval: function (interval) {
|
||||
intervalCleared = true;
|
||||
clearedId = interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js.configure(modulePath, { reloadSecs: 3 });
|
||||
log4js.configure(modulePath, { reloadSecs: 15 });
|
||||
|
||||
t.test('should clear the previous interval', (assert) => {
|
||||
assert.ok(intervalCleared);
|
||||
assert.equal(clearedId, 1234);
|
||||
assert.end();
|
||||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
batch.end();
|
||||
});
|
||||
@ -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);
|
||||
|
||||
@ -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', {
|
||||
|
||||
@ -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');
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ test('stderr appender', (t) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
).appender(layouts.messagePassThroughLayout);
|
||||
).configure({ type: 'stderr', layout: { type: 'messagePassThrough' } }, layouts);
|
||||
|
||||
appender({ data: ['biscuits'] });
|
||||
t.plan(2);
|
||||
|
||||
@ -20,7 +20,7 @@ test('stdout appender', (t) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
).appender(layouts.messagePassThroughLayout);
|
||||
).configure({ type: 'stdout', layout: { type: 'messagePassThrough' } }, layouts);
|
||||
|
||||
appender({ data: ['cheese'] });
|
||||
t.plan(2);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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
11
v2-changes.md
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user