diff --git a/lib/appenders/categoryFilter.js b/lib/appenders/categoryFilter.js index c4ab9d7..263970b 100644 --- a/lib/appenders/categoryFilter.js +++ b/lib/appenders/categoryFilter.js @@ -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; diff --git a/lib/appenders/clustered.js b/lib/appenders/clustered.js index 350209f..ef777c6 100755 --- a/lib/appenders/clustered.js +++ b/lib/appenders/clustered.js @@ -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]; diff --git a/lib/appenders/console.js b/lib/appenders/console.js index 6b2e691..25211f6 100644 --- a/lib/appenders/console.js +++ b/lib/appenders/console.js @@ -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; diff --git a/lib/appenders/dateFile.js b/lib/appenders/dateFile.js index 3c69ae5..9d2c385 100644 --- a/lib/appenders/dateFile.js +++ b/lib/appenders/dateFile.js @@ -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; diff --git a/lib/appenders/file.js b/lib/appenders/file.js index 9c2f2bb..d414ae6 100644 --- a/lib/appenders/file.js +++ b/lib/appenders/file.js @@ -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; diff --git a/lib/appenders/fileSync.js b/lib/appenders/fileSync.js index dab551c..36254a9 100755 --- a/lib/appenders/fileSync.js +++ b/lib/appenders/fileSync.js @@ -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; diff --git a/lib/appenders/gelf.js b/lib/appenders/gelf.js index eb809ed..fe52502 100644 --- a/lib/appenders/gelf.js +++ b/lib/appenders/gelf.js @@ -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; diff --git a/lib/appenders/hipchat.js b/lib/appenders/hipchat.js index 8c3a3be..a071310 100644 --- a/lib/appenders/hipchat.js +++ b/lib/appenders/hipchat.js @@ -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; diff --git a/lib/appenders/logFaces-HTTP.js b/lib/appenders/logFaces-HTTP.js new file mode 100644 index 0000000..41dbac5 --- /dev/null +++ b/lib/appenders/logFaces-HTTP.js @@ -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; diff --git a/lib/appenders/logFaces-UDP.js b/lib/appenders/logFaces-UDP.js new file mode 100644 index 0000000..a2d3b71 --- /dev/null +++ b/lib/appenders/logFaces-UDP.js @@ -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; diff --git a/lib/appenders/logFacesAppender.js b/lib/appenders/logFacesAppender.js deleted file mode 100644 index ba7bda6..0000000 --- a/lib/appenders/logFacesAppender.js +++ /dev/null @@ -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; diff --git a/lib/appenders/logLevelFilter.js b/lib/appenders/logLevelFilter.js index ea0d420..f91e758 100644 --- a/lib/appenders/logLevelFilter.js +++ b/lib/appenders/logLevelFilter.js @@ -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; diff --git a/lib/appenders/loggly.js b/lib/appenders/loggly.js index 77afd06..84c41d1 100644 --- a/lib/appenders/loggly.js +++ b/lib/appenders/loggly.js @@ -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; diff --git a/lib/appenders/logstashUDP.js b/lib/appenders/logstashUDP.js index 7805e09..4d6ce8a 100644 --- a/lib/appenders/logstashUDP.js +++ b/lib/appenders/logstashUDP.js @@ -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; diff --git a/lib/appenders/mailgun.js b/lib/appenders/mailgun.js index 11341ff..41ee19d 100644 --- a/lib/appenders/mailgun.js +++ b/lib/appenders/mailgun.js @@ -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; diff --git a/lib/appenders/multiprocess.js b/lib/appenders/multiprocess.js index d56a714..14475f0 100644 --- a/lib/appenders/multiprocess.js +++ b/lib/appenders/multiprocess.js @@ -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; diff --git a/lib/appenders/recording.js b/lib/appenders/recording.js new file mode 100644 index 0000000..78992a4 --- /dev/null +++ b/lib/appenders/recording.js @@ -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 +}; diff --git a/lib/appenders/redis.js b/lib/appenders/redis.js index 33a8d66..66036ef 100644 --- a/lib/appenders/redis.js +++ b/lib/appenders/redis.js @@ -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; diff --git a/lib/appenders/slack.js b/lib/appenders/slack.js index ae366cd..694f4f5 100644 --- a/lib/appenders/slack.js +++ b/lib/appenders/slack.js @@ -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; diff --git a/lib/appenders/smtp.js b/lib/appenders/smtp.js index dca9a3f..075743f 100644 --- a/lib/appenders/smtp.js +++ b/lib/appenders/smtp.js @@ -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; diff --git a/lib/appenders/stderr.js b/lib/appenders/stderr.js index 8944468..2c5a689 100644 --- a/lib/appenders/stderr.js +++ b/lib/appenders/stderr.js @@ -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; diff --git a/lib/appenders/stdout.js b/lib/appenders/stdout.js index 124ac97..80b9605 100644 --- a/lib/appenders/stdout.js +++ b/lib/appenders/stdout.js @@ -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; diff --git a/lib/configuration.js b/lib/configuration.js new file mode 100644 index 0000000..c348c4b --- /dev/null +++ b/lib/configuration.js @@ -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; diff --git a/lib/connect-logger.js b/lib/connect-logger.js index 2c60d3c..433c1b5 100755 --- a/lib/connect-logger.js +++ b/lib/connect-logger.js @@ -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 }; +}; diff --git a/lib/levels.js b/lib/levels.js index 2d981ac..443065e 100644 --- a/lib/levels.js +++ b/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; }; diff --git a/lib/log4js.js b/lib/log4js.js index ae6c8ca..5c812dd 100644 --- a/lib/log4js.js +++ b/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). * *
- * 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);
diff --git a/lib/logger.js b/lib/logger.js
index 1da0cae..20251a0 100644
--- a/lib/logger.js
+++ b/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;
+};
diff --git a/package.json b/package.json
index 2a88636..124cd5d 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/test/tap/categoryFilter-test.js b/test/tap/categoryFilter-test.js
index 4cd1043..cabd7f2 100644
--- a/test/tap/categoryFilter-test.js
+++ b/test/tap/categoryFilter-test.js
@@ -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();
});
diff --git a/test/tap/clusteredAppender-test.js b/test/tap/clusteredAppender-test.js
index 83b1e5e..6ebbaff 100644
--- a/test/tap/clusteredAppender-test.js
+++ b/test/tap/clusteredAppender-test.js
@@ -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) => {
diff --git a/test/tap/configuration-test.js b/test/tap/configuration-test.js
index 9c84ebf..0e44907 100644
--- a/test/tap/configuration-test.js
+++ b/test/tap/configuration-test.js
@@ -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);
diff --git a/test/tap/configuration-validation-test.js b/test/tap/configuration-validation-test.js
new file mode 100644
index 0000000..4514b14
--- /dev/null
+++ b/test/tap/configuration-validation-test.js
@@ -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();
+});
diff --git a/test/tap/configureNoLevels-test.js b/test/tap/configureNoLevels-test.js
deleted file mode 100644
index 0c5988b..0000000
--- a/test/tap/configureNoLevels-test.js
+++ /dev/null
@@ -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()
-// 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();
-});
diff --git a/test/tap/connect-logger-test.js b/test/tap/connect-logger-test.js
index 5c61b99..6c79d07 100644
--- a/test/tap/connect-logger-test.js
+++ b/test/tap/connect-logger-test.js
@@ -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');
diff --git a/test/tap/connect-nolog-test.js b/test/tap/connect-nolog-test.js
index 8d3370d..5404c34 100644
--- a/test/tap/connect-nolog-test.js
+++ b/test/tap/connect-nolog-test.js
@@ -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();
diff --git a/test/tap/consoleAppender-test.js b/test/tap/consoleAppender-test.js
index 6fe32cd..499f2d3 100644
--- a/test/tap/consoleAppender-test.js
+++ b/test/tap/consoleAppender-test.js
@@ -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();
diff --git a/test/tap/dateFileAppender-test.js b/test/tap/dateFileAppender-test.js
index 1dfb268..8ebfb10 100644
--- a/test/tap/dateFileAppender-test.js
+++ b/test/tap/dateFileAppender-test.js
@@ -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');
diff --git a/test/tap/file-sighup-test.js b/test/tap/file-sighup-test.js
index 5ed6afa..f863851 100644
--- a/test/tap/file-sighup-test.js
+++ b/test/tap/file-sighup-test.js
@@ -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);
diff --git a/test/tap/fileAppender-test.js b/test/tap/fileAppender-test.js
index afad844..b734724 100644
--- a/test/tap/fileAppender-test.js
+++ b/test/tap/fileAppender-test.js
@@ -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) => {
diff --git a/test/tap/fileSyncAppender-test.js b/test/tap/fileSyncAppender-test.js
index 4874862..fc5629a 100644
--- a/test/tap/fileSyncAppender-test.js
+++ b/test/tap/fileSyncAppender-test.js
@@ -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');
diff --git a/test/tap/gelfAppender-test.js b/test/tap/gelfAppender-test.js
index fc822be..12402e5 100644
--- a/test/tap/gelfAppender-test.js
+++ b/test/tap/gelfAppender-test.js
@@ -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;
diff --git a/test/tap/global-log-level-test.js b/test/tap/global-log-level-test.js
deleted file mode 100644
index 14beb61..0000000
--- a/test/tap/global-log-level-test.js
+++ /dev/null
@@ -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();
-});
diff --git a/test/tap/hipchatAppender-test.js b/test/tap/hipchatAppender-test.js
index 032bde7..2d58687 100644
--- a/test/tap/hipchatAppender-test.js
+++ b/test/tap/hipchatAppender-test.js
@@ -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');
diff --git a/test/tap/levels-test.js b/test/tap/levels-test.js
index 5447191..aeacd1e 100644
--- a/test/tap/levels-test.js
+++ b/test/tap/levels-test.js
@@ -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();
});
diff --git a/test/tap/log-abspath-test.js b/test/tap/log-abspath-test.js
deleted file mode 100644
index aa274ac..0000000
--- a/test/tap/log-abspath-test.js
+++ /dev/null
@@ -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();
-});
diff --git a/test/tap/logFaces-HTTP-test.js b/test/tap/logFaces-HTTP-test.js
new file mode 100644
index 0000000..05df419
--- /dev/null
+++ b/test/tap/logFaces-HTTP-test.js
@@ -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();
+});
diff --git a/test/tap/logFaces-UDP-test.js b/test/tap/logFaces-UDP-test.js
new file mode 100644
index 0000000..a5f0fa8
--- /dev/null
+++ b/test/tap/logFaces-UDP-test.js
@@ -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();
+});
diff --git a/test/tap/logFacesAppender-test.js b/test/tap/logFacesAppender-test.js
deleted file mode 100644
index fe1a632..0000000
--- a/test/tap/logFacesAppender-test.js
+++ /dev/null
@@ -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();
-});
diff --git a/test/tap/logLevelFilter-test.js b/test/tap/logLevelFilter-test.js
index 9a09aef..68fdab3 100644
--- a/test/tap/logLevelFilter-test.js
+++ b/test/tap/logLevelFilter-test.js
@@ -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');
diff --git a/test/tap/logger-test.js b/test/tap/logger-test.js
index 6edf229..1e72a59 100644
--- a/test/tap/logger-test.js
+++ b/test/tap/logger-test.js
@@ -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();
});
diff --git a/test/tap/logging-test.js b/test/tap/logging-test.js
index 42321aa..f9d6122 100644
--- a/test/tap/logging-test.js
+++ b/test/tap/logging-test.js
@@ -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();
});
diff --git a/test/tap/logglyAppender-test.js b/test/tap/logglyAppender-test.js
index 8fb25ad..400a4b8 100644
--- a/test/tap/logglyAppender-test.js
+++ b/test/tap/logglyAppender-test.js
@@ -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();
});
diff --git a/test/tap/logstashUDP-test.js b/test/tap/logstashUDP-test.js
index 5bacc4e..b50b1be 100644
--- a/test/tap/logstashUDP-test.js
+++ b/test/tap/logstashUDP-test.js
@@ -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();
});
diff --git a/test/tap/mailgunAppender-test.js b/test/tap/mailgunAppender-test.js
index 3408a38..248bd4a 100644
--- a/test/tap/mailgunAppender-test.js
+++ b/test/tap/mailgunAppender-test.js
@@ -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', {
diff --git a/test/tap/multiprocess-shutdown-test.js b/test/tap/multiprocess-shutdown-test.js
index 1bde591..660e1b4 100644
--- a/test/tap/multiprocess-shutdown-test.js
+++ b/test/tap/multiprocess-shutdown-test.js
@@ -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(() => {
diff --git a/test/tap/multiprocess-test.js b/test/tap/multiprocess-test.js
index 0b0c61c..0d6f1ed 100644
--- a/test/tap/multiprocess-test.js
+++ b/test/tap/multiprocess-test.js
@@ -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();
});
diff --git a/test/tap/newLevel-test.js b/test/tap/newLevel-test.js
index b817cfa..e190bb7 100644
--- a/test/tap/newLevel-test.js
+++ b/test/tap/newLevel-test.js
@@ -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();
diff --git a/test/tap/redisAppender-test.js b/test/tap/redisAppender-test.js
index 788a613..7f67d7a 100644
--- a/test/tap/redisAppender-test.js
+++ b/test/tap/redisAppender-test.js
@@ -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();
});
diff --git a/test/tap/reload-shutdown-test.js b/test/tap/reload-shutdown-test.js
deleted file mode 100644
index 7b3175f..0000000
--- a/test/tap/reload-shutdown-test.js
+++ /dev/null
@@ -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();
- });
-});
diff --git a/test/tap/reloadConfiguration-test.js b/test/tap/reloadConfiguration-test.js
deleted file mode 100644
index 6ce338c..0000000
--- a/test/tap/reloadConfiguration-test.js
+++ /dev/null
@@ -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();
-});
diff --git a/test/tap/setLevel-asymmetry-test.js b/test/tap/setLevel-asymmetry-test.js
index c3d5222..5b1f633 100644
--- a/test/tap/setLevel-asymmetry-test.js
+++ b/test/tap/setLevel-asymmetry-test.js
@@ -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);
diff --git a/test/tap/slackAppender-test.js b/test/tap/slackAppender-test.js
index acc1bbb..75048d6 100644
--- a/test/tap/slackAppender-test.js
+++ b/test/tap/slackAppender-test.js
@@ -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', {
diff --git a/test/tap/smtpAppender-test.js b/test/tap/smtpAppender-test.js
index fef1361..497d076 100644
--- a/test/tap/smtpAppender-test.js
+++ b/test/tap/smtpAppender-test.js
@@ -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');
diff --git a/test/tap/stderrAppender-test.js b/test/tap/stderrAppender-test.js
index 9fd4871..d311aa9 100644
--- a/test/tap/stderrAppender-test.js
+++ b/test/tap/stderrAppender-test.js
@@ -20,7 +20,7 @@ test('stderr appender', (t) => {
}
}
}
- ).appender(layouts.messagePassThroughLayout);
+ ).configure({ type: 'stderr', layout: { type: 'messagePassThrough' } }, layouts);
appender({ data: ['biscuits'] });
t.plan(2);
diff --git a/test/tap/stdoutAppender-test.js b/test/tap/stdoutAppender-test.js
index 9ae5baf..a3b0ce4 100644
--- a/test/tap/stdoutAppender-test.js
+++ b/test/tap/stdoutAppender-test.js
@@ -20,7 +20,7 @@ test('stdout appender', (t) => {
}
}
}
- ).appender(layouts.messagePassThroughLayout);
+ ).configure({ type: 'stdout', layout: { type: 'messagePassThrough' } }, layouts);
appender({ data: ['cheese'] });
t.plan(2);
diff --git a/test/tap/subcategories-test.js b/test/tap/subcategories-test.js
index f803c69..08295d4 100644
--- a/test/tap/subcategories-test.js
+++ b/test/tap/subcategories-test.js
@@ -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();
diff --git a/test/tap/with-categoryFilter.json b/test/tap/with-categoryFilter.json
deleted file mode 100644
index f1efa4a..0000000
--- a/test/tap/with-categoryFilter.json
+++ /dev/null
@@ -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"
- }
- }
- ]
-}
diff --git a/test/tap/with-dateFile.json b/test/tap/with-dateFile.json
deleted file mode 100644
index 4691278..0000000
--- a/test/tap/with-dateFile.json
+++ /dev/null
@@ -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"
- }
-}
diff --git a/test/tap/with-logLevelFilter.json b/test/tap/with-logLevelFilter.json
deleted file mode 100644
index 0995d35..0000000
--- a/test/tap/with-logLevelFilter.json
+++ /dev/null
@@ -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"
- }
-}
diff --git a/v2-changes.md b/v2-changes.md
new file mode 100644
index 0000000..9759369
--- /dev/null
+++ b/v2-changes.md
@@ -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