diff --git a/.travis.yml b/.travis.yml index 85fc71b..99dd6e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: node_js node_js: + - "0.12" - "0.10" - "0.8" diff --git a/README.md b/README.md index 71fc14b..e7d4be1 100644 --- a/README.md +++ b/README.md @@ -108,8 +108,9 @@ For FileAppender you can also pass the path to the log directory as an option wh log4js.configure('my_log4js_configuration.json', { cwd: '/absolute/path/to/log/dir' }); ``` If you have already defined an absolute path for one of the FileAppenders in the configuration file, you could add a "absolute": true to the particular FileAppender to override the cwd option passed. Here is an example configuration file: -```json + #### my_log4js_configuration.json #### +```json { "appenders": [ { diff --git a/examples/example.js b/examples/example.js index 25c9544..d304cc4 100644 --- a/examples/example.js +++ b/examples/example.js @@ -35,11 +35,13 @@ logger.setLevel('ERROR'); //console logging methods have been replaced with log4js ones. //so this will get coloured output on console, and appear in cheese.log console.error("AAArgh! Something went wrong", { some: "otherObject", useful_for: "debug purposes" }); +console.log("This should appear as info output"); //these will not appear (logging level beneath error) logger.trace('Entering cheese testing'); logger.debug('Got cheese.'); logger.info('Cheese is Gouda.'); +logger.log('Something funny about cheese.'); logger.warn('Cheese is quite smelly.'); //these end up on the console and in cheese.log logger.error('Cheese %s is too ripe!', "gouda"); diff --git a/lib/appenders/clustered.js b/lib/appenders/clustered.js index b427ab6..7c31248 100755 --- a/lib/appenders/clustered.js +++ b/lib/appenders/clustered.js @@ -87,6 +87,15 @@ function createAppender(config) { // console.log("master : " + cluster.isMaster + " received message: " + JSON.stringify(message.event)); var loggingEvent = deserializeLoggingEvent(message.event); + + // Adding PID metadata + loggingEvent.pid = worker.process.pid; + loggingEvent.cluster = { + master: process.pid, + worker: worker.process.pid, + workerId: worker.id + }; + masterAppender(loggingEvent); } }); diff --git a/lib/appenders/console.js b/lib/appenders/console.js index 7a470a3..20f80b1 100644 --- a/lib/appenders/console.js +++ b/lib/appenders/console.js @@ -2,10 +2,10 @@ var layouts = require('../layouts') , consoleLog = console.log.bind(console); -function consoleAppender (layout) { +function consoleAppender (layout, timezoneOffset) { layout = layout || layouts.colouredLayout; return function(loggingEvent) { - consoleLog(layout(loggingEvent)); + consoleLog(layout(loggingEvent, timezoneOffset)); }; } @@ -14,7 +14,7 @@ function configure(config) { if (config.layout) { layout = layouts.layout(config.layout.type, config.layout); } - return consoleAppender(layout); + return consoleAppender(layout, config.timezoneOffset); } exports.appender = consoleAppender; diff --git a/lib/appenders/dateFile.js b/lib/appenders/dateFile.js index 8b6d594..2b0b232 100644 --- a/lib/appenders/dateFile.js +++ b/lib/appenders/dateFile.js @@ -20,8 +20,9 @@ process.on('exit', function() { * @pattern the format that will be added to the end of filename when rolling, * also used to check when to roll files - defaults to '.yyyy-MM-dd' * @layout layout function for log messages - defaults to basicLayout + * @timezoneOffset optional timezone offset in minutes - defaults to system local */ -function appender(filename, pattern, alwaysIncludePattern, layout) { +function appender(filename, pattern, alwaysIncludePattern, layout, timezoneOffset) { layout = layout || layouts.basicLayout; var logFile = new streams.DateRollingFileStream( @@ -32,7 +33,7 @@ function appender(filename, pattern, alwaysIncludePattern, layout) { openFiles.push(logFile); return function(logEvent) { - logFile.write(layout(logEvent) + eol, "utf8"); + logFile.write(layout(logEvent, timezoneOffset) + eol, "utf8"); }; } @@ -52,7 +53,7 @@ function configure(config, options) { config.filename = path.join(options.cwd, config.filename); } - return appender(config.filename, config.pattern, config.alwaysIncludePattern, layout); + return appender(config.filename, config.pattern, config.alwaysIncludePattern, layout, config.timezoneOffset); } function shutdown(cb) { diff --git a/lib/appenders/file.js b/lib/appenders/file.js index 44bedf7..6a5f813 100644 --- a/lib/appenders/file.js +++ b/lib/appenders/file.js @@ -6,7 +6,8 @@ var layouts = require('../layouts') , streams = require('../streams') , os = require('os') , eol = os.EOL || '\n' -, openFiles = []; +, openFiles = [] +, levels = require('../levels'); //close open files on process exit. process.on('exit', function() { @@ -25,8 +26,10 @@ process.on('exit', function() { * if not provided then logs won't be rotated. * @param numBackups - the number of log files to keep after logSize * has been reached (default 5) + * @param compress - flag that controls log file compression + * @param timezoneOffset - optional timezone offset in minutes (default system local) */ -function fileAppender (file, layout, logSize, numBackups) { +function fileAppender (file, layout, logSize, numBackups, compress, timezoneOffset) { var bytesWritten = 0; file = path.normalize(file); layout = layout || layouts.basicLayout; @@ -40,7 +43,8 @@ function fileAppender (file, layout, logSize, numBackups) { stream = new streams.RollingFileStream( file, fileSize, - numFiles + numFiles, + { "compress": compress } ); } else { stream = fs.createWriteStream( @@ -60,10 +64,11 @@ function fileAppender (file, layout, logSize, numBackups) { // push file to the stack of open handlers openFiles.push(logFile); - + return function(loggingEvent) { - logFile.write(layout(loggingEvent) + eol, "utf8"); + logFile.write(layout(loggingEvent, timezoneOffset) + eol, "utf8"); }; + } function configure(config, options) { @@ -76,7 +81,7 @@ function configure(config, options) { config.filename = path.join(options.cwd, config.filename); } - return fileAppender(config.filename, layout, config.maxLogSize, config.backups); + return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.compress, config.timezoneOffset); } function shutdown(cb) { diff --git a/lib/appenders/fileSync.js b/lib/appenders/fileSync.js index b248f75..2aa6a5b 100755 --- a/lib/appenders/fileSync.js +++ b/lib/appenders/fileSync.js @@ -127,8 +127,10 @@ RollingFileSync.prototype.write = function(chunk, encoding) { * if not provided then logs won't be rotated. * @param numBackups - the number of log files to keep after logSize * has been reached (default 5) + * @param timezoneOffset - optional timezone offset in minutes + * (default system local) */ -function fileAppender (file, layout, logSize, numBackups) { +function fileAppender (file, layout, logSize, numBackups, timezoneOffset) { debug("fileSync appender created"); var bytesWritten = 0; file = path.normalize(file); @@ -166,7 +168,7 @@ function fileAppender (file, layout, logSize, numBackups) { var logFile = openTheStream(file, logSize, numBackups); return function(loggingEvent) { - logFile.write(layout(loggingEvent) + eol); + logFile.write(layout(loggingEvent, timezoneOffset) + eol); }; } @@ -180,7 +182,7 @@ function configure(config, options) { config.filename = path.join(options.cwd, config.filename); } - return fileAppender(config.filename, layout, config.maxLogSize, config.backups); + return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.timezoneOffset); } exports.appender = fileAppender; diff --git a/lib/appenders/gelf.js b/lib/appenders/gelf.js index eca067e..644cbbc 100644 --- a/lib/appenders/gelf.js +++ b/lib/appenders/gelf.js @@ -85,6 +85,8 @@ function gelfAppender (layout, host, port, hostname, facility) { var firstData = data[0]; if (!firstData.GELF) return; // identify with GELF field defined + // Remove the GELF key, some gelf supported logging systems drop the message with it + delete firstData.GELF; Object.keys(firstData).forEach(function(key) { // skip _id field for graylog2, skip keys not starts with UNDERSCORE if (key.match(/^_/) || key !== "_id") { diff --git a/lib/appenders/smtp.js b/lib/appenders/smtp.js index 85accee..1ec1a0f 100644 --- a/lib/appenders/smtp.js +++ b/lib/appenders/smtp.js @@ -1,7 +1,10 @@ "use strict"; var layouts = require("../layouts") , mailer = require("nodemailer") -, os = require('os'); +, os = require('os') +, async = require('async') +, unsentCount = 0 +, shutdownTimeout; /** * SMTP Appender. Sends logging events using SMTP protocol. @@ -11,6 +14,7 @@ var layouts = require("../layouts") * @param config appender configuration data * config.sendInterval time between log emails (in seconds), if 0 * then every event sends an email +* 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) { @@ -21,22 +25,31 @@ function smtpAppender(config, layout) { var logEventBuffer = []; var sendTimer; + shutdownTimeout = ('shutdownTimeout' in config ? config.shutdownTimeout : 5) * 1000; + function sendBuffer() { if (logEventBuffer.length > 0) { - var transport = mailer.createTransport(config.transport, config[config.transport]); + var transport = mailer.createTransport(config.SMTP); var firstEvent = logEventBuffer[0]; var body = ""; + var count = logEventBuffer.length; while (logEventBuffer.length > 0) { - body += layout(logEventBuffer.shift()) + "\n"; + body += layout(logEventBuffer.shift(), config.timezoneOffset) + "\n"; } var msg = { to: config.recipients, subject: config.subject || subjectLayout(firstEvent), - text: body, headers: { "Hostname": os.hostname() } }; + + if (!config.html) { + msg.text = body; + } else { + msg.html = body; + } + if (config.sender) { msg.from = config.sender; } @@ -45,6 +58,7 @@ function smtpAppender(config, layout) { console.error("log4js.smtpAppender - Error happened", error); } transport.close(); + unsentCount -= count; }); } } @@ -59,6 +73,7 @@ function smtpAppender(config, layout) { } return function(loggingEvent) { + unsentCount++; logEventBuffer.push(loggingEvent); if (sendInterval > 0) { scheduleSend(); @@ -76,7 +91,19 @@ function configure(config) { return smtpAppender(config, layout); } +function shutdown(cb) { + if (shutdownTimeout > 0) { + setTimeout(function() { unsentCount = 0; }, shutdownTimeout); + } + async.whilst(function() { + return unsentCount > 0; + }, function(done) { + setTimeout(done, 100); + }, cb); +} + exports.name = "smtp"; exports.appender = smtpAppender; exports.configure = configure; +exports.shutdown = shutdown; diff --git a/lib/connect-logger.js b/lib/connect-logger.js index 1f287c0..4f7a312 100644 --- a/lib/connect-logger.js +++ b/lib/connect-logger.js @@ -1,8 +1,9 @@ "use strict"; var levels = require("./levels"); -var DEFAULT_FORMAT = ':remote-addr - -' + - ' ":method :url HTTP/:http-version"' + - ' :status :content-length ":referrer"' + +var _ = require('underscore'); +var 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. @@ -52,7 +53,7 @@ function getLogger(logger4js, options) { // nologs if (nolog && nolog.test(req.originalUrl)) return next(); if (thislogger.isLevelEnabled(level) || options.level === 'auto') { - + var start = new Date() , statusCode , writeHead = res.writeHead @@ -60,7 +61,7 @@ function getLogger(logger4js, options) { // flag as logging req._logging = true; - + // proxy for statusCode. res.writeHead = function(code, headers){ res.writeHead = writeHead; @@ -77,7 +78,7 @@ function getLogger(logger4js, options) { level = levels.toLevel(options.level, levels.INFO); } }; - + //hook on end request to emit the log entry of the HTTP request. res.on('finish', function() { res.responseTime = new Date() - start; @@ -88,21 +89,68 @@ function getLogger(logger4js, options) { if(res.statusCode >= 400) level = levels.ERROR; } if (thislogger.isLevelEnabled(level)) { + var combined_tokens = assemble_tokens(req, res, options.tokens || []); if (typeof fmt === 'function') { - var line = fmt(req, res, function(str){ return format(str, req, res); }); + var line = fmt(req, res, function(str){ return format(str, combined_tokens); }); if (line) thislogger.log(level, line); } else { - thislogger.log(level, format(fmt, req, res)); + thislogger.log(level, format(fmt, combined_tokens)); } } }); } - + //ensure next gets always called next(); }; } +/** + * Adds custom {token, replacement} objects to defaults, overwriting the defaults if any tokens clash + * + * @param {IncomingMessage} req + * @param {ServerResponse} res + * @param {Array} custom_tokens [{ token: string-or-regexp, replacement: string-or-replace-function }] + * @return {Array} + */ +function assemble_tokens(req, res, custom_tokens) { + var array_unique_tokens = function(array) { + var a = array.concat(); + for(var i=0; i -1 ? vYearLong : vYearShort); - var vHour = addZero(date.getHours()); - var vMinute = addZero(date.getMinutes()); - var vSecond = addZero(date.getSeconds()); - var vMillisecond = padWithZeros(date.getMilliseconds(), 3); - var vTimeZone = offset(date); + var vHour = addZero(date.getUTCHours()); + var vMinute = addZero(date.getUTCMinutes()); + var vSecond = addZero(date.getUTCSeconds()); + var vMillisecond = padWithZeros(date.getUTCMilliseconds(), 3); + var vTimeZone = offset(timezoneOffset); + date.setUTCMinutes(date.getUTCMinutes() + timezoneOffset); var formatted = format .replace(/dd/g, vDay) .replace(/MM/g, vMonth) diff --git a/lib/layouts.js b/lib/layouts.js index 270f336..8e7e8a2 100644 --- a/lib/layouts.js +++ b/lib/layouts.js @@ -71,11 +71,11 @@ function colorize (str, style) { return colorizeStart(style) + str + colorizeEnd(style); } -function timestampLevelAndCategory(loggingEvent, colour) { +function timestampLevelAndCategory(loggingEvent, colour, timezoneOffest) { var output = colorize( formatLogData( '[%s] [%s] %s - ' - , dateFormat.asString(loggingEvent.startTime) + , dateFormat.asString(loggingEvent.startTime, timezoneOffest) , loggingEvent.level , loggingEvent.categoryName ) @@ -93,18 +93,19 @@ function timestampLevelAndCategory(loggingEvent, colour) { * * @author Stephan Strittmatter */ -function basicLayout (loggingEvent) { - return timestampLevelAndCategory(loggingEvent) + formatLogData(loggingEvent.data); +function basicLayout (loggingEvent, timezoneOffset) { + return timestampLevelAndCategory(loggingEvent, undefined, timezoneOffset) + formatLogData(loggingEvent.data); } /** * colouredLayout - taken from masylum's fork. * same as basicLayout, but with colours. */ -function colouredLayout (loggingEvent) { +function colouredLayout (loggingEvent, timezoneOffset) { return timestampLevelAndCategory( loggingEvent, - colours[loggingEvent.level.toString()] + colours[loggingEvent.level.toString()], + timezoneOffset ) + formatLogData(loggingEvent.data); } @@ -139,13 +140,14 @@ function messagePassThroughLayout (loggingEvent) { * Takes a pattern string, array of tokens and returns a layout function. * @param {String} Log format pattern String * @param {object} map object of different tokens + * @param {number} timezone offset in minutes * @return {Function} * @author Stephan Strittmatter * @author Jan Schmidle */ -function patternLayout (pattern, tokens) { +function patternLayout (pattern, tokens, timezoneOffset) { var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n"; - var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([\[\]cdhmnprzx%])(\{([^\}]+)\})?|([^%]+)/; + var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([\[\]cdhmnprzxy%])(\{([^\}]+)\})?|([^%]+)/; pattern = pattern || TTCC_CONVERSION_PATTERN; @@ -177,7 +179,7 @@ function patternLayout (pattern, tokens) { } } // Format the date - return dateFormat.asString(format, loggingEvent.startTime); + return dateFormat.asString(format, loggingEvent.startTime, timezoneOffset); } function hostname() { @@ -197,7 +199,7 @@ function patternLayout (pattern, tokens) { } function startTime(loggingEvent) { - return "" + loggingEvent.startTime.toLocaleTimeString(); + return dateFormat.asString('hh:mm:ss', loggingEvent.startTime, timezoneOffset); } function startColour(loggingEvent) { @@ -212,8 +214,25 @@ function patternLayout (pattern, tokens) { return '%'; } - function pid() { - return process.pid; + function pid(loggingEvent) { + if (loggingEvent && loggingEvent.pid) { + return loggingEvent.pid; + } else { + return process.pid; + } + } + + function clusterInfo(loggingEvent, specifier) { + if (loggingEvent.cluster && specifier) { + return specifier + .replace('%m', loggingEvent.cluster.master) + .replace('%w', loggingEvent.cluster.worker) + .replace('%i', loggingEvent.cluster.workerId); + } else if (loggingEvent.cluster) { + return loggingEvent.cluster.worker+'@'+loggingEvent.cluster.master; + } else { + return pid(); + } } function userDefined(loggingEvent, specifier) { @@ -237,6 +256,7 @@ function patternLayout (pattern, tokens) { 'r': startTime, '[': startColour, ']': endColour, + 'y': clusterInfo, 'z': pid, '%': percent, 'x': userDefined @@ -295,9 +315,7 @@ function patternLayout (pattern, tokens) { } else { // Create a raw replacement string based on the conversion // character and specifier - var replacement = - replaceToken(conversionCharacter, loggingEvent, specifier) || - matchedString; + var replacement = replaceToken(conversionCharacter, loggingEvent, specifier); // Format the replacement according to any padding or // truncation specified diff --git a/lib/levels.js b/lib/levels.js index 0637099..54a8cf3 100644 --- a/lib/levels.js +++ b/lib/levels.js @@ -63,6 +63,7 @@ module.exports = { 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 }; diff --git a/lib/log4js.js b/lib/log4js.js index 33a5d05..18fd665 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -65,6 +65,8 @@ var events = require('events') replaceConsole: false }; +require('./appenders/console'); + function hasLogger(logger) { return loggers.hasOwnProperty(logger); } @@ -92,6 +94,22 @@ function getBufferedLogger(categoryName) { return logger; } +function normalizeCategory (category) { + return category + '.'; +} + +function doesLevelEntryContainsLogger (levelCategory, loggerCategory) { + var normalizedLevelCategory = normalizeCategory(levelCategory); + var normalizedLoggerCategory = normalizeCategory(loggerCategory); + return normalizedLoggerCategory.substring(0, normalizedLevelCategory.length) == normalizedLevelCategory; +} + +function doesAppenderContainsLogger (appenderCategory, loggerCategory) { + var normalizedAppenderCategory = normalizeCategory(appenderCategory); + var normalizedLoggerCategory = normalizeCategory(loggerCategory); + return normalizedLoggerCategory.substring(0, normalizedAppenderCategory.length) == normalizedAppenderCategory; +} + /** * Get a logger instance. Instance is cached on categoryName level. @@ -99,32 +117,51 @@ function getBufferedLogger(categoryName) { * @return {Logger} instance of logger for the category * @static */ -function getLogger (categoryName) { +function getLogger (loggerCategoryName) { // Use default logger if categoryName is not specified or invalid - if (typeof categoryName !== "string") { - categoryName = Logger.DEFAULT_CATEGORY; + if (typeof loggerCategoryName !== "string") { + loggerCategoryName = Logger.DEFAULT_CATEGORY; } - var appenderList; - if (!hasLogger(categoryName)) { + if (!hasLogger(loggerCategoryName)) { + + var level = undefined; + + // If there's a "levels" entry in the configuration + if (levels.config) { + // Goes through the categories in the levels configuration entry, starting by the "higher" ones. + var keys = Object.keys(levels.config).sort(); + for (var idx = 0; idx < keys.length; idx++) { + var levelCategory = keys[idx]; + if (doesLevelEntryContainsLogger(levelCategory, loggerCategoryName)) { + // level for the logger + level = levels.config[levelCategory]; + } + } + } + // Create the logger for this name if it doesn't already exist - loggers[categoryName] = new Logger(categoryName); - if (appenders[categoryName]) { - appenderList = appenders[categoryName]; - appenderList.forEach(function(appender) { - loggers[categoryName].addListener("log", appender); - }); + loggers[loggerCategoryName] = new Logger(loggerCategoryName, level); + + var appenderList; + for(var appenderCategory in appenders) { + if (doesAppenderContainsLogger(appenderCategory, loggerCategoryName)) { + appenderList = appenders[appenderCategory]; + appenderList.forEach(function(appender) { + loggers[loggerCategoryName].addListener("log", appender); + }); + } } if (appenders[ALL_CATEGORIES]) { appenderList = appenders[ALL_CATEGORIES]; appenderList.forEach(function(appender) { - loggers[categoryName].addListener("log", appender); + loggers[loggerCategoryName].addListener("log", appender); }); } } - return loggers[categoryName]; + return loggers[loggerCategoryName]; } /** @@ -141,13 +178,19 @@ function addAppender () { args = args[0]; } - args.forEach(function(category) { - addAppenderToCategory(appender, category); + args.forEach(function(appenderCategory) { + addAppenderToCategory(appender, appenderCategory); - if (category === ALL_CATEGORIES) { + if (appenderCategory === ALL_CATEGORIES) { addAppenderToAllLoggers(appender); - } else if (hasLogger(category)) { - loggers[category].addListener("log", appender); + } else { + + for(var loggerCategory in loggers) { + if (doesAppenderContainsLogger(appenderCategory,loggerCategory)) { + loggers[loggerCategory].addListener("log", appender); + } + } + } }); } @@ -198,14 +241,19 @@ function configureAppenders(appenderList, options) { } } -function configureLevels(levels) { - if (levels) { - for (var category in levels) { - if (levels.hasOwnProperty(category)) { - if(category === ALL_CATEGORIES) { - setGlobalLogLevel(levels[category]); +function configureLevels(_levels) { + levels.config = _levels; // Keep it so we can create loggers later using this cfg + if (_levels) { + var keys = Object.keys(levels.config).sort(); + for (var idx in keys) { + var category = keys[idx]; + if(category === ALL_CATEGORIES) { + setGlobalLogLevel(_levels[category]); + } + for(var loggerCategory in loggers) { + if (doesLevelEntryContainsLogger(category, loggerCategory)) { + loggers[loggerCategory].setLevel(_levels[category]); } - getLogger(category).setLevel(levels[category]); } } } @@ -236,8 +284,8 @@ function loadConfigurationFile(filename) { function configureOnceOff(config, options) { if (config) { try { - configureAppenders(config.appenders, options); configureLevels(config.levels); + configureAppenders(config.appenders, options); if (config.replaceConsole) { replaceConsole(); @@ -253,12 +301,12 @@ function configureOnceOff(config, options) { } } -function reloadConfiguration() { +function reloadConfiguration(options) { var mtime = getMTime(configState.filename); if (!mtime) return; - + if (configState.lastMTime && (mtime.getTime() > configState.lastMTime.getTime())) { - configureOnceOff(loadConfigurationFile(configState.filename)); + configureOnceOff(loadConfigurationFile(configState.filename), options); } configState.lastMTime = mtime; } @@ -280,7 +328,7 @@ function initReloadConfiguration(filename, options) { } configState.filename = filename; configState.lastMTime = getMTime(filename); - configState.timerId = setInterval(reloadConfiguration, options.reloadSecs*1000); + configState.timerId = setInterval(reloadConfiguration, options.reloadSecs*1000, options); } function configure(configurationFileOrObject, options) { diff --git a/lib/logger.js b/lib/logger.js index 3615a18..49907ab 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -51,7 +51,7 @@ Logger.prototype.removeLevel = function() { Logger.prototype.log = function() { var args = Array.prototype.slice.call(arguments) - , logLevel = levels.toLevel(args.shift()) + , logLevel = levels.toLevel(args.shift(), levels.INFO) , loggingEvent; if (this.isLevelEnabled(logLevel)) { loggingEvent = new LoggingEvent(this.category, logLevel, args, this); @@ -63,7 +63,7 @@ Logger.prototype.isLevelEnabled = function(otherLevel) { return this.level.isLessThanOrEqualTo(otherLevel); }; -['Trace','Debug','Info','Warn','Error','Fatal'].forEach( +['Trace','Debug','Info','Warn','Error','Fatal', 'Mark'].forEach( function(levelString) { var level = levels.toLevel(levelString); Logger.prototype['is'+levelString+'Enabled'] = function() { diff --git a/lib/streams/BaseRollingFileStream.js b/lib/streams/BaseRollingFileStream.js index 572794c..9c441ad 100644 --- a/lib/streams/BaseRollingFileStream.js +++ b/lib/streams/BaseRollingFileStream.js @@ -16,7 +16,11 @@ module.exports = BaseRollingFileStream; function BaseRollingFileStream(filename, options) { debug("In BaseRollingFileStream"); this.filename = filename; - this.options = options || { encoding: 'utf8', mode: parseInt('0644', 8), flags: 'a' }; + this.options = options || {}; + this.options.encoding = this.options.encoding || 'utf8'; + this.options.mode = this.options.mode || parseInt('0644', 8); + this.options.flags = this.options.flags || 'a'; + this.currentSize = 0; function currentFileSize(file) { diff --git a/lib/streams/RollingFileStream.js b/lib/streams/RollingFileStream.js index c9d35dd..1b0a0cf 100644 --- a/lib/streams/RollingFileStream.js +++ b/lib/streams/RollingFileStream.js @@ -3,6 +3,8 @@ var BaseRollingFileStream = require('./BaseRollingFileStream') , debug = require('../debug')('RollingFileStream') , util = require('util') , path = require('path') +, child_process = require('child_process') +, zlib = require("zlib") , fs = require('fs') , async = require('async'); @@ -25,7 +27,7 @@ function RollingFileStream (filename, size, backups, options) { util.inherits(RollingFileStream, BaseRollingFileStream); RollingFileStream.prototype.shouldRoll = function() { - debug("should roll with current size %d, and max size %d", this.currentSize, this.size); + debug("should roll with current size " + this.currentSize + " and max size " + this.size); return this.currentSize >= this.size; }; @@ -38,6 +40,7 @@ RollingFileStream.prototype.roll = function(filename, callback) { } function index(filename_) { + debug('Calculating index of '+filename_); return parseInt(filename_.substring((path.basename(filename) + '.').length), 10) || 0; } @@ -51,16 +54,42 @@ RollingFileStream.prototype.roll = function(filename, callback) { } } + function compress (filename, cb) { + + var gzip = zlib.createGzip(); + var inp = fs.createReadStream(filename); + var out = fs.createWriteStream(filename+".gz"); + inp.pipe(gzip).pipe(out); + fs.unlink(filename, cb); + + } + function increaseFileIndex (fileToRename, cb) { var idx = index(fileToRename); debug('Index of ' + fileToRename + ' is ' + idx); if (idx < that.backups) { + + var ext = path.extname(fileToRename); + var destination = filename + '.' + (idx+1); + if (that.options.compress && /^gz$/.test(ext.substring(1))) { + destination+=ext; + } //on windows, you can get a EEXIST error if you rename a file to an existing file //so, we'll try to delete the file we're renaming to first - fs.unlink(filename + '.' + (idx+1), function (err) { + fs.unlink(destination, function (err) { //ignore err: if we could not delete, it's most likely that it doesn't exist - debug('Renaming ' + fileToRename + ' -> ' + filename + '.' + (idx+1)); - fs.rename(path.join(path.dirname(filename), fileToRename), filename + '.' + (idx + 1), cb); + debug('Renaming ' + fileToRename + ' -> ' + destination); + fs.rename(path.join(path.dirname(filename), fileToRename), destination, function(err) { + if (err) { + cb(err); + } else { + if (that.options.compress && ext!=".gz") { + compress(destination, cb); + } else { + cb(); + } + } + }); }); } else { cb(); diff --git a/package.json b/package.json index 3fb6f5a..b0fa65c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.6.21", + "version": "0.6.25", "description": "Port of Log4js to work with node.", "keywords": [ "logging", @@ -31,7 +31,8 @@ "dependencies": { "async": "~0.2.0", "readable-stream": "~1.0.2", - "semver": "~1.1.4" + "semver": "~4.3.3", + "underscore": "1.8.2" }, "devDependencies": { "vows": "0.7.0", diff --git a/test/connect-logger-test.js b/test/connect-logger-test.js index 96d0409..dd1e717 100644 --- a/test/connect-logger-test.js +++ b/test/connect-logger-test.js @@ -10,7 +10,7 @@ function MockLogger() { var that = this; this.messages = []; - + this.log = function(level, message, exception) { that.messages.push({ level: level, message: message }); }; @@ -18,7 +18,7 @@ function MockLogger() { this.isLevelEnabled = function(level) { return level.isGreaterThanOrEqualTo(that.level); }; - + this.level = levels.TRACE; } @@ -40,7 +40,7 @@ function MockRequest(remoteAddr, method, originalUrl, headers) { function MockResponse() { var r = this; - this.end = function(chunk, encoding) { + this.end = function(chunk, encoding) { r.emit('finish'); }; @@ -66,7 +66,7 @@ vows.describe('log4js connect logger').addBatch({ var clm = require('../lib/connect-logger'); return clm; }, - + 'should return a "connect logger" factory' : function(clm) { assert.isObject(clm); }, @@ -77,12 +77,12 @@ vows.describe('log4js connect logger').addBatch({ var cl = clm.connectLogger(ml); return cl; }, - + 'should return a "connect logger"': function(cl) { assert.isFunction(cl); } }, - + 'log events' : { topic: function(clm) { var ml = new MockLogger(); @@ -113,7 +113,7 @@ vows.describe('log4js connect logger').addBatch({ request(cl, 'GET', 'http://url', 200); return ml.messages; }, - + 'check message': function(messages) { assert.isArray(messages); assert.isEmpty(messages); @@ -130,7 +130,7 @@ vows.describe('log4js connect logger').addBatch({ setTimeout(function() { cb(null, ml.messages); },10); }, - + 'check message': function(messages) { assert.isArray(messages); assert.equal(messages.length, 1); @@ -168,7 +168,7 @@ vows.describe('log4js connect logger').addBatch({ request(cl, 'GET', 'http://meh', 500); setTimeout(function() { cb(null, ml.messages); - },10); + },10); }, 'should use INFO for 2xx': function(messages) { @@ -198,7 +198,7 @@ vows.describe('log4js connect logger').addBatch({ request(cl, 'GET', 'http://blah', 200); setTimeout(function() { cb(null, ml.messages); - },10); + },10); }, 'should call the format function': function(messages) { @@ -213,8 +213,8 @@ vows.describe('log4js connect logger').addBatch({ ml.level = levels.INFO; var cl = clm.connectLogger(ml, ':req[Content-Type]'); request( - cl, - 'GET', 'http://blah', 200, + cl, + 'GET', 'http://blah', 200, { 'Content-Type': 'application/json' } ); setTimeout(function() { @@ -246,7 +246,50 @@ vows.describe('log4js connect logger').addBatch({ 'should output the response header': function(messages) { assert.equal(messages[0].message, 'application/cheese'); } - } - + }, + + 'log events with custom token' : { + topic: function(clm) { + var ml = new MockLogger(); + var cb = this.callback; + ml.level = levels.INFO; + var cl = clm.connectLogger(ml, { level: levels.INFO, format: ':method :url :custom_string', tokens: [{ + token: ':custom_string', replacement: 'fooBAR' + }] } ); + request(cl, 'GET', 'http://url', 200); + setTimeout(function() { + cb(null, ml.messages); + },10); + }, + + 'check message': function(messages) { + assert.isArray(messages); + assert.equal(messages.length, 1); + assert.ok(levels.INFO.isEqualTo(messages[0].level)); + assert.equal(messages[0].message, 'GET http://url fooBAR'); + } + }, + + 'log events with custom override token' : { + topic: function(clm) { + var ml = new MockLogger(); + var cb = this.callback; + ml.level = levels.INFO; + var cl = clm.connectLogger(ml, { level: levels.INFO, format: ':method :url :date', tokens: [{ + token: ':date', replacement: "20150310" + }] } ); + request(cl, 'GET', 'http://url', 200); + setTimeout(function() { + cb(null, ml.messages); + },10); + }, + + 'check message': function(messages) { + assert.isArray(messages); + assert.equal(messages.length, 1); + assert.ok(levels.INFO.isEqualTo(messages[0].level)); + assert.equal(messages[0].message, 'GET http://url 20150310'); + } + } } }).export(module); diff --git a/test/date_format-test.js b/test/date_format-test.js index 6085843..04adb08 100644 --- a/test/date_format-test.js +++ b/test/date_format-test.js @@ -3,11 +3,13 @@ var vows = require('vows') , assert = require('assert') , dateFormat = require('../lib/date_format'); +function createFixedDate() { + return new Date(2010, 0, 11, 14, 31, 30, 5); +} + vows.describe('date_format').addBatch({ 'Date extensions': { - topic: function() { - return new Date(2010, 0, 11, 14, 31, 30, 5); - }, + topic: createFixedDate, 'should format a date as string using a pattern': function(date) { assert.equal( dateFormat.asString(dateFormat.DATETIME_FORMAT, date), @@ -20,13 +22,16 @@ vows.describe('date_format').addBatch({ '2010-01-11 14:31:30.005' ); }, - 'should provide a ISO8601 with timezone offset format': function(date) { + 'should provide a ISO8601 with timezone offset format': function() { + var date = createFixedDate(); + date.setMinutes(date.getMinutes() - date.getTimezoneOffset() - 660); date.getTimezoneOffset = function() { return -660; }; assert.equal( dateFormat.asString(dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT, date), "2010-01-11T14:31:30+1100" ); - + date = createFixedDate(); + date.setMinutes(date.getMinutes() - date.getTimezoneOffset() + 120); date.getTimezoneOffset = function() { return 120; }; assert.equal( dateFormat.asString(dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT, date), @@ -40,7 +45,9 @@ vows.describe('date_format').addBatch({ '14:31:30.005' ); }, - 'should provide a custom format': function(date) { + 'should provide a custom format': function() { + var date = createFixedDate(); + date.setMinutes(date.getMinutes() - date.getTimezoneOffset() + 120); date.getTimezoneOffset = function() { return 120; }; assert.equal( dateFormat.asString("O.SSS.ss.mm.hh.dd.MM.yy", date), diff --git a/test/fileAppender-test.js b/test/fileAppender-test.js index 9b4bd0f..b313400 100644 --- a/test/fileAppender-test.js +++ b/test/fileAppender-test.js @@ -5,6 +5,7 @@ var vows = require('vows') , sandbox = require('sandboxed-module') , log4js = require('../lib/log4js') , assert = require('assert') +, zlib = require('zlib') , EOL = require('os').EOL || '\n'; log4js.clearAppenders(); @@ -104,6 +105,70 @@ vows.describe('log4js fileAppender').addBatch({ ); } }, + 'fileAppender subcategories': { + topic: function() { + var that = this; + + log4js.clearAppenders(); + + function addAppender(cat) { + var 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; + } + + var file_sub1 = addAppender([ 'sub1']); + + var file_sub1_sub12$sub1_sub13 = addAppender([ 'sub1.sub12', 'sub1.sub13' ]); + + var file_sub1_sub12 = addAppender([ 'sub1.sub12' ]); + + + var logger_sub1_sub12_sub123 = log4js.getLogger('sub1.sub12.sub123'); + + var logger_sub1_sub13_sub133 = log4js.getLogger('sub1.sub13.sub133'); + + var logger_sub1_sub14 = log4js.getLogger('sub1.sub14'); + + var 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(function() { + that.callback(null, { + 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() + }); + }, 3000); + }, + 'check file contents': function (err, fileContents) { + + // 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}$/); + 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}$/); + 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}$/); + assert.ok(!fileContents.file_sub1_sub12.match(/sub14|sub2|sub13/)); + + } + }, 'with a max file size and no backups': { topic: function() { var testFile = path.join(__dirname, '/fa-maxFileSize-test.log') @@ -214,6 +279,79 @@ vows.describe('log4js fileAppender').addBatch({ } } } + }, + 'with a max file size and 2 compressed backups': { + topic: function() { + var testFile = path.join(__dirname, '/fa-maxFileSize-with-backups-compressed-test.log') + , logger = log4js.getLogger('max-file-size-backups'); + remove(testFile); + remove(testFile+'.1.gz'); + remove(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, true), + 'max-file-size-backups' + ); + logger.info("This is the first log message."); + logger.info("This is the second log message."); + logger.info("This is the third log message."); + logger.info("This is the fourth log message."); + var that = this; + //give the system a chance to open the stream + setTimeout(function() { + fs.readdir(__dirname, function(err, files) { + if (files) { + that.callback(null, files.sort()); + } else { + that.callback(err, files); + } + }); + }, 1000); + }, + 'the log files': { + topic: function(files) { + var logFiles = files.filter( + function(file) { return file.indexOf('fa-maxFileSize-with-backups-compressed-test.log') > -1; } + ); + return logFiles; + }, + 'should be 3': function (files) { + assert.equal(files.length, 3); + }, + 'should be named in sequence': function (files) { + assert.deepEqual(files, [ + 'fa-maxFileSize-with-backups-compressed-test.log', + 'fa-maxFileSize-with-backups-compressed-test.log.1.gz', + 'fa-maxFileSize-with-backups-compressed-test.log.2.gz' + ]); + }, + 'and the contents of the first file': { + topic: function(logFiles) { + fs.readFile(path.join(__dirname, logFiles[0]), "utf8", this.callback); + }, + 'should be the last log message': function(contents) { + assert.include(contents, 'This is the fourth log message.'); + } + }, + 'and the contents of the second file': { + topic: function(logFiles) { + zlib.gunzip(fs.readFileSync(path.join(__dirname, logFiles[1])), this.callback); + }, + 'should be the third log message': function(contents) { + assert.include(contents.toString('utf8'), 'This is the third log message.'); + } + }, + 'and the contents of the third file': { + topic: function(logFiles) { + zlib.gunzip(fs.readFileSync(path.join(__dirname, logFiles[2])), this.callback); + }, + 'should be the second log message': function(contents) { + assert.include(contents.toString('utf8'), 'This is the second log message.'); + } + } + } } }).addBatch({ 'configure' : { diff --git a/test/gelfAppender-test.js b/test/gelfAppender-test.js index 6b33b72..76fb5ea 100644 --- a/test/gelfAppender-test.js +++ b/test/gelfAppender-test.js @@ -244,6 +244,7 @@ vows.describe('log4js gelfAppender').addBatch({ }, 'should pick up the options': function(message) { assert.equal(message.host, 'cheese'); + assert.isUndefined(message.GELF); // make sure flag was removed assert.equal(message._facility, 'nonsense'); assert.equal(message._every1, 'Hello every one'); // the default value assert.equal(message._every2, 'Overwritten!'); // the overwritten value diff --git a/test/layouts-test.js b/test/layouts-test.js index db6ba56..2b5baf9 100644 --- a/test/layouts-test.js +++ b/test/layouts-test.js @@ -179,7 +179,7 @@ vows.describe('log4js layouts').addBatch({ topic: function() { var event = { data: ['this is a test'], - startTime: new Date(2010, 11, 5, 14, 18, 30, 45), + startTime: new Date('2010-12-05T14:18:30.045Z'), //new Date(2010, 11, 5, 14, 18, 30, 45), categoryName: "multiple.levels.of.tests", level: { toString: function() { return "DEBUG"; } @@ -282,14 +282,14 @@ vows.describe('log4js layouts').addBatch({ test(args, '%x{testFunction}', 'testFunctionToken'); }, '%x{doesNotExist} should output the string stored in tokens': function(args) { - test(args, '%x{doesNotExist}', '%x{doesNotExist}'); + test(args, '%x{doesNotExist}', 'null'); }, '%x{fnThatUsesLogEvent} should be able to use the logEvent': function(args) { test(args, '%x{fnThatUsesLogEvent}', 'DEBUG'); }, '%x should output the string stored in tokens': function(args) { - test(args, '%x', '%x'); - }, + test(args, '%x', 'null'); + } }, 'layout makers': { topic: require('../lib/layouts'), diff --git a/test/levels-test.js b/test/levels-test.js index 99dd1fc..9a0fdfc 100644 --- a/test/levels-test.js +++ b/test/levels-test.js @@ -43,6 +43,7 @@ vows.describe('levels').addBatch({ assert.isNotNull(levels.WARN); assert.isNotNull(levels.ERROR); assert.isNotNull(levels.FATAL); + assert.isNotNull(levels.MARK); assert.isNotNull(levels.OFF); }, 'ALL': { @@ -56,7 +57,8 @@ vows.describe('levels').addBatch({ levels.INFO, levels.WARN, levels.ERROR, - levels.FATAL, + levels.FATAL, + levels.MARK, levels.OFF ] ); @@ -70,6 +72,7 @@ vows.describe('levels').addBatch({ levels.WARN, levels.ERROR, levels.FATAL, + levels.MARK, levels.OFF ] ); @@ -84,6 +87,7 @@ vows.describe('levels').addBatch({ levels.WARN, levels.ERROR, levels.FATAL, + levels.MARK, levels.OFF ] ); @@ -99,6 +103,7 @@ vows.describe('levels').addBatch({ levels.WARN, levels.ERROR, levels.FATAL, + levels.MARK, levels.OFF ] ); @@ -113,6 +118,7 @@ vows.describe('levels').addBatch({ levels.WARN, levels.ERROR, levels.FATAL, + levels.MARK, levels.OFF ] ); @@ -127,6 +133,7 @@ vows.describe('levels').addBatch({ levels.WARN, levels.ERROR, levels.FATAL, + levels.MARK, levels.OFF ] ); @@ -141,6 +148,7 @@ vows.describe('levels').addBatch({ levels.WARN, levels.ERROR, levels.FATAL, + levels.MARK, levels.OFF ] ); @@ -154,6 +162,7 @@ vows.describe('levels').addBatch({ levels.WARN, levels.ERROR, levels.FATAL, + levels.MARK, levels.OFF ] ); @@ -168,6 +177,7 @@ vows.describe('levels').addBatch({ levels.WARN, levels.ERROR, levels.FATAL, + levels.MARK, levels.OFF ] ); @@ -180,6 +190,7 @@ vows.describe('levels').addBatch({ levels.WARN, levels.ERROR, levels.FATAL, + levels.MARK, levels.OFF ]); assertThat(info).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]); @@ -190,6 +201,7 @@ vows.describe('levels').addBatch({ levels.WARN, levels.ERROR, levels.FATAL, + levels.MARK, levels.OFF ]); }, @@ -202,6 +214,7 @@ vows.describe('levels').addBatch({ levels.WARN, levels.ERROR, levels.FATAL, + levels.MARK, levels.OFF ]); } @@ -209,7 +222,7 @@ vows.describe('levels').addBatch({ 'WARN': { topic: levels.WARN, 'should be less than ERROR': function(warn) { - assertThat(warn).isLessThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]); + assertThat(warn).isLessThanOrEqualTo([levels.ERROR, levels.FATAL, levels.MARK, levels.OFF]); assertThat(warn).isNotLessThanOrEqualTo([ levels.ALL, levels.TRACE, @@ -224,7 +237,7 @@ vows.describe('levels').addBatch({ levels.DEBUG, levels.INFO ]); - assertThat(warn).isNotGreaterThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]); + assertThat(warn).isNotGreaterThanOrEqualTo([levels.ERROR, levels.FATAL, levels.MARK, levels.OFF]); }, 'should only be equal to WARN': function(trace) { assertThat(trace).isEqualTo([levels.toLevel("WARN")]); @@ -242,7 +255,7 @@ vows.describe('levels').addBatch({ 'ERROR': { topic: levels.ERROR, 'should be less than FATAL': function(error) { - assertThat(error).isLessThanOrEqualTo([levels.FATAL, levels.OFF]); + assertThat(error).isLessThanOrEqualTo([levels.FATAL, levels.MARK, levels.OFF]); assertThat(error).isNotLessThanOrEqualTo([ levels.ALL, levels.TRACE, @@ -259,7 +272,7 @@ vows.describe('levels').addBatch({ levels.INFO, levels.WARN ]); - assertThat(error).isNotGreaterThanOrEqualTo([levels.FATAL, levels.OFF]); + assertThat(error).isNotGreaterThanOrEqualTo([levels.FATAL, levels.MARK, levels.OFF]); }, 'should only be equal to ERROR': function(trace) { assertThat(trace).isEqualTo([levels.toLevel("ERROR")]); @@ -270,6 +283,7 @@ vows.describe('levels').addBatch({ levels.INFO, levels.WARN, levels.FATAL, + levels.MARK, levels.OFF ]); } @@ -277,7 +291,7 @@ vows.describe('levels').addBatch({ 'FATAL': { topic: levels.FATAL, 'should be less than OFF': function(fatal) { - assertThat(fatal).isLessThanOrEqualTo([levels.OFF]); + assertThat(fatal).isLessThanOrEqualTo([levels.MARK, levels.OFF]); assertThat(fatal).isNotLessThanOrEqualTo([ levels.ALL, levels.TRACE, @@ -295,8 +309,8 @@ vows.describe('levels').addBatch({ levels.INFO, levels.WARN, levels.ERROR - ]); - assertThat(fatal).isNotGreaterThanOrEqualTo([levels.OFF]); + ]); + assertThat(fatal).isNotGreaterThanOrEqualTo([levels.MARK, levels.OFF]); }, 'should only be equal to FATAL': function(fatal) { assertThat(fatal).isEqualTo([levels.toLevel("FATAL")]); @@ -306,7 +320,48 @@ vows.describe('levels').addBatch({ levels.DEBUG, levels.INFO, levels.WARN, - levels.ERROR, + levels.ERROR, + levels.MARK, + levels.OFF + ]); + } + }, + 'MARK': { + topic: levels.MARK, + 'should be less than OFF': function(mark) { + assertThat(mark).isLessThanOrEqualTo([levels.OFF]); + assertThat(mark).isNotLessThanOrEqualTo([ + levels.ALL, + levels.TRACE, + levels.DEBUG, + levels.INFO, + levels.WARN, + levels.FATAL, + levels.ERROR + ]); + }, + 'should be greater than FATAL': function(mark) { + assertThat(mark).isGreaterThanOrEqualTo([ + levels.ALL, + levels.TRACE, + levels.DEBUG, + levels.INFO, + levels.WARN, + levels.ERROR, + levels.FATAL + ]); + assertThat(mark).isNotGreaterThanOrEqualTo([levels.OFF]); + }, + 'should only be equal to MARK': function(mark) { + assertThat(mark).isEqualTo([levels.toLevel("MARK")]); + assertThat(mark).isNotEqualTo([ + levels.ALL, + levels.TRACE, + levels.DEBUG, + levels.INFO, + levels.WARN, + levels.ERROR, + levels.FATAL, levels.OFF ]); } @@ -321,7 +376,8 @@ vows.describe('levels').addBatch({ levels.INFO, levels.WARN, levels.ERROR, - levels.FATAL + levels.FATAL, + levels.MARK ]); }, 'should be greater than everything': function(off) { @@ -332,7 +388,8 @@ vows.describe('levels').addBatch({ levels.INFO, levels.WARN, levels.ERROR, - levels.FATAL + levels.FATAL, + levels.MARK ]); }, 'should only be equal to OFF': function(off) { @@ -344,7 +401,8 @@ vows.describe('levels').addBatch({ levels.INFO, levels.WARN, levels.ERROR, - levels.FATAL + levels.FATAL, + levels.MARK ]); } } @@ -353,14 +411,14 @@ vows.describe('levels').addBatch({ topic: levels.INFO, 'should handle string arguments': function(info) { assertThat(info).isGreaterThanOrEqualTo(["all", "trace", "debug"]); - assertThat(info).isNotGreaterThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']); + assertThat(info).isNotGreaterThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'MARK', 'off']); } }, 'isLessThanOrEqualTo': { topic: levels.INFO, 'should handle string arguments': function(info) { assertThat(info).isNotLessThanOrEqualTo(["all", "trace", "debug"]); - assertThat(info).isLessThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']); + assertThat(info).isLessThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'MARK', 'off']); } }, 'isEqualTo': { diff --git a/test/smtpAppender-test.js b/test/smtpAppender-test.js index ab38d93..f681c75 100644 --- a/test/smtpAppender-test.js +++ b/test/smtpAppender-test.js @@ -74,7 +74,6 @@ vows.describe('log4js smtpAppender').addBatch({ topic: function() { var setup = setupLogging('minimal config', { recipients: 'recipient@domain.com', - transport: "SMTP", SMTP: { port: 25, auth: { @@ -98,7 +97,6 @@ vows.describe('log4js smtpAppender').addBatch({ recipients: 'recipient@domain.com', sender: 'sender@domain.com', subject: 'This is subject', - transport: "SMTP", SMTP: { port: 25, auth: { @@ -134,7 +132,6 @@ vows.describe('log4js smtpAppender').addBatch({ var self = this; var setup = setupLogging('separate email for each event', { recipients: 'recipient@domain.com', - transport: "SMTP", SMTP: { port: 25, auth: { @@ -168,7 +165,6 @@ vows.describe('log4js smtpAppender').addBatch({ var setup = setupLogging('multiple events in one email', { recipients: 'recipient@domain.com', sendInterval: 1, - transport: "SMTP", SMTP: { port: 25, auth: { @@ -206,7 +202,6 @@ vows.describe('log4js smtpAppender').addBatch({ var setup = setupLogging('error when sending email', { recipients: 'recipient@domain.com', sendInterval: 0, - transport: 'SMTP', SMTP: { port: 25, auth: { user: 'user@domain.com' } } }); diff --git a/test/subcategories-test.js b/test/subcategories-test.js new file mode 100644 index 0000000..32ff9b2 --- /dev/null +++ b/test/subcategories-test.js @@ -0,0 +1,86 @@ +"use strict"; +var assert = require('assert') +, vows = require('vows') +, sandbox = require('sandboxed-module') +, log4js = require('../lib/log4js') +, levels = require('../lib/levels'); + +vows.describe('subcategories').addBatch({ + 'loggers created after levels configuration is loaded': { + topic: function() { + + log4js.configure({ + "levels": { + "sub1": "WARN", + "sub1.sub11": "TRACE", + "sub1.sub11.sub111": "WARN", + "sub1.sub12": "INFO" + } + }, { reloadSecs: 30 }) + + return { + "sub1": log4js.getLogger('sub1'), // WARN + "sub11": log4js.getLogger('sub1.sub11'), // TRACE + "sub111": log4js.getLogger('sub1.sub11.sub111'), // WARN + "sub12": log4js.getLogger('sub1.sub12'), // INFO + + "sub13": log4js.getLogger('sub1.sub13'), // Inherits sub1: WARN + "sub112": log4js.getLogger('sub1.sub11.sub112'), // Inherits sub1.sub11: TRACE + "sub121": log4js.getLogger('sub1.sub12.sub121'), // Inherits sub12: INFO + "sub0": log4js.getLogger('sub0') // Not defined, not inherited: TRACE + }; + }, + 'check logger levels': function(loggers) { + 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.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); + } + }, + 'loggers created before levels configuration is loaded': { + topic: function() { + + var loggers = { + "sub1": log4js.getLogger('sub1'), // WARN + "sub11": log4js.getLogger('sub1.sub11'), // TRACE + "sub111": log4js.getLogger('sub1.sub11.sub111'), // WARN + "sub12": log4js.getLogger('sub1.sub12'), // INFO + + "sub13": log4js.getLogger('sub1.sub13'), // Inherits sub1: WARN + "sub112": log4js.getLogger('sub1.sub11.sub112'), // Inherits sub1.sub11: TRACE + "sub121": log4js.getLogger('sub1.sub12.sub121'), // Inherits sub12: INFO + "sub0": log4js.getLogger('sub0') // Not defined, not inherited: TRACE + }; + + + log4js.configure({ + "levels": { + "sub1": "WARN", + "sub1.sub11": "TRACE", + "sub1.sub11.sub111": "WARN", + "sub1.sub12": "INFO" + } + }, { reloadSecs: 30 }) + + return loggers; + + + }, + 'check logger levels': function(loggers) { + 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.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); + } + } +}).exportTo(module);