From 6fa998408a2272b6c7891350105063f16f804232 Mon Sep 17 00:00:00 2001 From: Luis Malheiro Date: Tue, 10 Dec 2013 23:55:08 +0100 Subject: [PATCH 01/37] Adds subcategories to the appenders and loggers. Adds property "level" at the file appender to limit the levels that a file appender accepts. Creates a MARK category that always write to the log. That's useful to write things like '---- STARTED ----'. --- lib/appenders/file.js | 28 +++++++++---- lib/levels.js | 1 + lib/log4js.js | 96 ++++++++++++++++++++++++++++++++----------- lib/logger.js | 2 +- 4 files changed, 94 insertions(+), 33 deletions(-) diff --git a/lib/appenders/file.js b/lib/appenders/file.js index 8a7d113..d7ce702 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() { @@ -26,7 +27,7 @@ process.on('exit', function() { * @param numBackups - the number of log files to keep after logSize * has been reached (default 5) */ -function fileAppender (file, layout, logSize, numBackups) { +function fileAppender (file, layout, logSize, numBackups, level) { var bytesWritten = 0; file = path.normalize(file); layout = layout || layouts.basicLayout; @@ -60,10 +61,23 @@ 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"); - }; + + if (level) { + + var obj = levels.toLevel(level, levels.TRACE); + return function(loggingEvent) { + if (obj.isLessThanOrEqualTo(loggingEvent.level)) + logFile.write(layout(loggingEvent) + eol, "utf8"); + }; + + } else { + + return function(loggingEvent) { + logFile.write(layout(loggingEvent) + eol, "utf8"); + }; + + } + } function configure(config, options) { @@ -76,7 +90,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.level); } function shutdown(cb) { diff --git a/lib/levels.js b/lib/levels.js index 0637099..45ebf64 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(Number.MAX_VALUE-1, "MARK"), OFF: new Level(Number.MAX_VALUE, "OFF"), toLevel: toLevel }; diff --git a/lib/log4js.js b/lib/log4js.js index 2e1f25c..cb799d1 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -92,6 +92,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 +115,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 +176,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); + } + } + } }); } @@ -193,14 +234,19 @@ function configureAppenders(appenderList, options) { } } -function configureLevels(levels) { - if (levels) { - for (var category in levels) { - if (levels.hasOwnProperty(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]; + for(var loggerCategory in loggers) { + if (doesLevelEntryContainsLogger(category, loggerCategory)) { + loggers[loggerCategory].setLevel(_levels[category]); + } if(category === ALL_CATEGORIES) { setGlobalLogLevel(levels[category]); - } - getLogger(category).setLevel(levels[category]); + } } } } @@ -231,8 +277,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(); diff --git a/lib/logger.js b/lib/logger.js index 3615a18..67bb35d 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -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() { From 036293db4193556d6999cba022a5a79b83697342 Mon Sep 17 00:00:00 2001 From: Luis Malheiro Date: Wed, 27 Aug 2014 16:08:17 +0200 Subject: [PATCH 02/37] Log compression. --- lib/appenders/file.js | 7 +++--- lib/streams/BaseRollingFileStream.js | 6 ++++- lib/streams/RollingFileStream.js | 37 +++++++++++++++++++++++++--- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/lib/appenders/file.js b/lib/appenders/file.js index d7ce702..b79d4f6 100644 --- a/lib/appenders/file.js +++ b/lib/appenders/file.js @@ -27,7 +27,7 @@ process.on('exit', function() { * @param numBackups - the number of log files to keep after logSize * has been reached (default 5) */ -function fileAppender (file, layout, logSize, numBackups, level) { +function fileAppender (file, layout, logSize, numBackups, level, compress) { var bytesWritten = 0; file = path.normalize(file); layout = layout || layouts.basicLayout; @@ -41,7 +41,8 @@ function fileAppender (file, layout, logSize, numBackups, level) { stream = new streams.RollingFileStream( file, fileSize, - numFiles + numFiles, + { "compress": compress } ); } else { stream = fs.createWriteStream( @@ -90,7 +91,7 @@ function configure(config, options) { config.filename = path.join(options.cwd, config.filename); } - return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.level); + return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.level, config.compress); } function shutdown(cb) { 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 64a0725..64058f3 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(); From 39a73586edd13e235659f67ff06bd27c5538d97a Mon Sep 17 00:00:00 2001 From: Luis Malheiro Date: Mon, 1 Sep 2014 14:37:25 +0200 Subject: [PATCH 03/37] Fixed bug that failed test 'set level on all categories' --- lib/log4js.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/log4js.js b/lib/log4js.js index cb799d1..b0cc341 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -240,13 +240,13 @@ function configureLevels(_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]); } - if(category === ALL_CATEGORIES) { - setGlobalLogLevel(levels[category]); - } } } } From 02ea4831ea548bb5f963db7c59a00e936e408d77 Mon Sep 17 00:00:00 2001 From: Luis Malheiro Date: Mon, 1 Sep 2014 18:05:36 +0200 Subject: [PATCH 04/37] Test for the 'compress' option at the file appender. --- test/fileAppender-test.js | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/fileAppender-test.js b/test/fileAppender-test.js index 9b4bd0f..95b0a66 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(); @@ -214,6 +215,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, null, 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); + } + }); + }, 200); + }, + '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' : { From ebbbea198dda5e09d438486714b2e47ca7d4d4ff Mon Sep 17 00:00:00 2001 From: Luis Malheiro Date: Tue, 2 Sep 2014 14:33:51 +0200 Subject: [PATCH 05/37] Added test to check loggers using sub-categories. --- test/subcategories-test.js | 86 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 test/subcategories-test.js 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); From 492c45d055311b20bb740000440061baff3a20fb Mon Sep 17 00:00:00 2001 From: Luis Malheiro Date: Mon, 8 Sep 2014 11:14:00 +0200 Subject: [PATCH 06/37] Test for appender using subcategories. --- test/fileAppender-test.js | 64 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/test/fileAppender-test.js b/test/fileAppender-test.js index 95b0a66..093eab3 100644 --- a/test/fileAppender-test.js +++ b/test/fileAppender-test.js @@ -105,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() + }); + }, 1000); + }, + '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') From 17c9b29ca5ab1fceb1460a3c222abb7112713899 Mon Sep 17 00:00:00 2001 From: Luis Malheiro Date: Mon, 8 Sep 2014 11:33:22 +0200 Subject: [PATCH 07/37] Removed property 'level' from the file appender, because that functionality is provided by appender logLevelFilter. --- lib/appenders/file.js | 22 +++++----------------- test/fileAppender-test.js | 6 +++--- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/lib/appenders/file.js b/lib/appenders/file.js index b79d4f6..57aaa9c 100644 --- a/lib/appenders/file.js +++ b/lib/appenders/file.js @@ -27,7 +27,7 @@ process.on('exit', function() { * @param numBackups - the number of log files to keep after logSize * has been reached (default 5) */ -function fileAppender (file, layout, logSize, numBackups, level, compress) { +function fileAppender (file, layout, logSize, numBackups, compress) { var bytesWritten = 0; file = path.normalize(file); layout = layout || layouts.basicLayout; @@ -63,21 +63,9 @@ function fileAppender (file, layout, logSize, numBackups, level, compress) { // push file to the stack of open handlers openFiles.push(logFile); - if (level) { - - var obj = levels.toLevel(level, levels.TRACE); - return function(loggingEvent) { - if (obj.isLessThanOrEqualTo(loggingEvent.level)) - logFile.write(layout(loggingEvent) + eol, "utf8"); - }; - - } else { - - return function(loggingEvent) { - logFile.write(layout(loggingEvent) + eol, "utf8"); - }; - - } + return function(loggingEvent) { + logFile.write(layout(loggingEvent) + eol, "utf8"); + }; } @@ -91,7 +79,7 @@ function configure(config, options) { config.filename = path.join(options.cwd, config.filename); } - return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.level, config.compress); + return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.compress); } function shutdown(cb) { diff --git a/test/fileAppender-test.js b/test/fileAppender-test.js index 093eab3..b313400 100644 --- a/test/fileAppender-test.js +++ b/test/fileAppender-test.js @@ -149,7 +149,7 @@ vows.describe('log4js fileAppender').addBatch({ file_sub1_sub12$sub1_sub13: fs.readFileSync(file_sub1_sub12$sub1_sub13).toString(), file_sub1_sub12: fs.readFileSync(file_sub1_sub12).toString() }); - }, 1000); + }, 3000); }, 'check file contents': function (err, fileContents) { @@ -291,7 +291,7 @@ vows.describe('log4js fileAppender').addBatch({ //log file of 50 bytes maximum, 2 backups log4js.clearAppenders(); log4js.addAppender( - require('../lib/appenders/file').appender(testFile, log4js.layouts.basicLayout, 50, 2, null, true), + require('../lib/appenders/file').appender(testFile, log4js.layouts.basicLayout, 50, 2, true), 'max-file-size-backups' ); logger.info("This is the first log message."); @@ -308,7 +308,7 @@ vows.describe('log4js fileAppender').addBatch({ that.callback(err, files); } }); - }, 200); + }, 1000); }, 'the log files': { topic: function(files) { From 1e999f36d769d0456dac81e7b2278ca361d99c71 Mon Sep 17 00:00:00 2001 From: Luis Malheiro Date: Mon, 8 Sep 2014 12:17:50 +0200 Subject: [PATCH 08/37] Fix and test for MARK level. --- lib/levels.js | 2 +- test/levels-test.js | 86 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 73 insertions(+), 15 deletions(-) diff --git a/lib/levels.js b/lib/levels.js index 45ebf64..54a8cf3 100644 --- a/lib/levels.js +++ b/lib/levels.js @@ -63,7 +63,7 @@ module.exports = { WARN: new Level(30000, "WARN"), ERROR: new Level(40000, "ERROR"), FATAL: new Level(50000, "FATAL"), - MARK: new Level(Number.MAX_VALUE-1, "MARK"), + MARK: new Level(9007199254740992, "MARK"), // 2^53 OFF: new Level(Number.MAX_VALUE, "OFF"), toLevel: toLevel }; 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': { From e07adf2ca4f8663849fe007d4229c5586e1b7ef3 Mon Sep 17 00:00:00 2001 From: Imanuel Ulbricht Date: Fri, 28 Nov 2014 17:49:46 +0100 Subject: [PATCH 09/37] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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": [ { From b694fd1d8d00518f4d6484957a7ccf43b8a1200c Mon Sep 17 00:00:00 2001 From: Christophe Bol Date: Mon, 1 Dec 2014 12:29:45 +0100 Subject: [PATCH 10/37] added cluster identifier support --- lib/appenders/clustered.js | 9 +++++++++ lib/layouts.js | 24 +++++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) 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/layouts.js b/lib/layouts.js index 270f336..be5681c 100644 --- a/lib/layouts.js +++ b/lib/layouts.js @@ -145,7 +145,7 @@ function messagePassThroughLayout (loggingEvent) { */ function patternLayout (pattern, tokens) { 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; @@ -212,8 +212,25 @@ function patternLayout (pattern, tokens) { return '%'; } - function pid() { - return process.pid; + function pid(loggingEvent) { + if (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 +254,7 @@ function patternLayout (pattern, tokens) { 'r': startTime, '[': startColour, ']': endColour, + 'y': clusterInfo, 'z': pid, '%': percent, 'x': userDefined From 3300dfae600bf2c7bdc690dd974ce0f413b38128 Mon Sep 17 00:00:00 2001 From: Christophe Bol Date: Mon, 1 Dec 2014 15:34:09 +0100 Subject: [PATCH 11/37] fixed error when logging from the clustered master --- lib/layouts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/layouts.js b/lib/layouts.js index be5681c..1a1f094 100644 --- a/lib/layouts.js +++ b/lib/layouts.js @@ -213,7 +213,7 @@ function patternLayout (pattern, tokens) { } function pid(loggingEvent) { - if (loggingEvent.pid) { + if (loggingEvent && loggingEvent.pid) { return loggingEvent.pid; } else { return process.pid; From ec72c03f813052b0aa69f3e2eb6036373b732634 Mon Sep 17 00:00:00 2001 From: Martin Bramwell Date: Tue, 9 Dec 2014 02:59:58 -0500 Subject: [PATCH 12/37] Force bundling of appenders/console Changes to be committed: modified: lib/log4js.js --- lib/log4js.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/log4js.js b/lib/log4js.js index de4f974..ee829b2 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); } From 1629e01df938de9074c850b62b238921ce3f00e8 Mon Sep 17 00:00:00 2001 From: sc2bigjoe Date: Wed, 17 Dec 2014 11:28:50 -0500 Subject: [PATCH 13/37] Update smtp.js added the ability for smtp appender to send message as html instead of plaintext. in your log4js.config file simply include "html": "true", to write out as html, otherwise it will send plaintext --- lib/appenders/smtp.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/appenders/smtp.js b/lib/appenders/smtp.js index 85accee..fa3e775 100644 --- a/lib/appenders/smtp.js +++ b/lib/appenders/smtp.js @@ -34,9 +34,15 @@ function smtpAppender(config, layout) { 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; } From 6b029e98fc709756c9033cca8080029d0aa33a71 Mon Sep 17 00:00:00 2001 From: Patrick Hogan Date: Sun, 21 Dec 2014 14:49:25 -0800 Subject: [PATCH 14/37] Allow for blank tokens due to dynamic data Metadata for users such as name, email, etc are not always present for users. For example, I am running express and I want to log the %x{company}%x{username} so that when I look at my logs I can immediately understand which user this affects. Or for example, I could log %x{payingOrTial} the type of user. This works well when the user is logged in. However, my logger encompasses everything. I log when the server boots up. I log during the login screen where a user is not yet logged in. In these circumstances there is no way to retrieve this metadata. So for example ``` "username": function () { var session = require('continuation-local-storage').getNamespace('api.callinize'); if(!session) session = require('continuation-local-storage').getNamespace('dashboard.callinize'); var username = session && session.get('user') && session.get('user').username; if(!username) return ""; return " " + username + " "; } ``` I try to get the metadata. If I get no metdata I return a blank string. Unfortunately, in the current implementation, due to the OR operator, even if I have a replacement of "" || matchedString, ``` replaceToken(conversionCharacter, loggingEvent, specifier) || matchedString; ``` the blank string equals false and puts the token in the log instead of the blank string. This makes the log lines get long with information that is not relevant. The better thing to do is simply allow for blank strings. This lets the user have control over their logs and also allows for more metadata to go in the logs, without having to pick only metadata that is always present. --- lib/layouts.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/layouts.js b/lib/layouts.js index 270f336..94f273d 100644 --- a/lib/layouts.js +++ b/lib/layouts.js @@ -295,9 +295,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 From f987990339cb3f1cdab850d94a5a4953ca540507 Mon Sep 17 00:00:00 2001 From: Patrick Hogan Date: Tue, 23 Dec 2014 19:42:32 -0800 Subject: [PATCH 15/37] added null tests --- test/layouts-test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/layouts-test.js b/test/layouts-test.js index db6ba56..01c777e 100644 --- a/test/layouts-test.js +++ b/test/layouts-test.js @@ -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'), From 1f1442cb7cbae0583e3740317f17724e0bb806ca Mon Sep 17 00:00:00 2001 From: Fuxian Ding Date: Sat, 27 Dec 2014 12:48:24 +0800 Subject: [PATCH 16/37] add options to reload function if `cwd` is included in option, reload will not work --- lib/log4js.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/log4js.js b/lib/log4js.js index 2e1f25c..59c9ead 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -248,12 +248,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; } @@ -275,7 +275,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) { From 35067af5504ff10254ae0b086d856f7a39bb0c40 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 10 Jan 2015 15:40:03 +1100 Subject: [PATCH 17/37] 0.6.22 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3fb6f5a..0a2231e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.6.21", + "version": "0.6.22", "description": "Port of Log4js to work with node.", "keywords": [ "logging", From adfad9ad2078b5513d549fb6a262e665a8d2c060 Mon Sep 17 00:00:00 2001 From: hasegawa-jun Date: Wed, 4 Mar 2015 09:13:30 +0900 Subject: [PATCH 18/37] Implemented shutdown function for SMTP appender --- lib/appenders/smtp.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/appenders/smtp.js b/lib/appenders/smtp.js index fa3e775..d907213 100644 --- a/lib/appenders/smtp.js +++ b/lib/appenders/smtp.js @@ -1,7 +1,8 @@ "use strict"; var layouts = require("../layouts") , mailer = require("nodemailer") -, os = require('os'); +, os = require('os') +, unsentCount = 0; /** * SMTP Appender. Sends logging events using SMTP protocol. @@ -27,6 +28,7 @@ function smtpAppender(config, layout) { var transport = mailer.createTransport(config.transport, config[config.transport]); var firstEvent = logEventBuffer[0]; var body = ""; + var count = logEventBuffer.length; while (logEventBuffer.length > 0) { body += layout(logEventBuffer.shift()) + "\n"; } @@ -51,6 +53,7 @@ function smtpAppender(config, layout) { console.error("log4js.smtpAppender - Error happened", error); } transport.close(); + unsentCount -= count; }); } } @@ -65,6 +68,7 @@ function smtpAppender(config, layout) { } return function(loggingEvent) { + unsentCount++; logEventBuffer.push(loggingEvent); if (sendInterval > 0) { scheduleSend(); @@ -82,7 +86,16 @@ function configure(config) { return smtpAppender(config, layout); } +function shutdown(cb) { + async.until(function() { + return unsentCount === 0; + }, function(done) { + setTimeout(done, 100); + }, cb); +} + exports.name = "smtp"; exports.appender = smtpAppender; exports.configure = configure; +exports.shutdown = shutdown; From ba80dc1588e08015b058e423ccc74e54dba253d3 Mon Sep 17 00:00:00 2001 From: Christo Fogelberg Date: Sun, 8 Mar 2015 19:35:54 +0000 Subject: [PATCH 19/37] Trailing whitespace Sublime removed --- lib/connect-logger.js | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/connect-logger.js b/lib/connect-logger.js index 1f287c0..16eef50 100644 --- a/lib/connect-logger.js +++ b/lib/connect-logger.js @@ -1,8 +1,8 @@ "use strict"; var levels = require("./levels"); -var DEFAULT_FORMAT = ':remote-addr - -' + - ' ":method :url HTTP/:http-version"' + - ' :status :content-length ":referrer"' + +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 +52,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 +60,7 @@ function getLogger(logger4js, options) { // flag as logging req._logging = true; - + // proxy for statusCode. res.writeHead = function(code, headers){ res.writeHead = writeHead; @@ -77,7 +77,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; @@ -97,7 +97,7 @@ function getLogger(logger4js, options) { } }); } - + //ensure next gets always called next(); }; @@ -123,20 +123,20 @@ function format(str, req, res) { .replace(':referrer', req.headers.referer || req.headers.referrer || '') .replace(':http-version', req.httpVersionMajor + '.' + req.httpVersionMinor) .replace( - ':remote-addr', req.ip || req._remoteAddress || ( - req.socket && + ':remote-addr', req.ip || req._remoteAddress || ( + req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress)) )) .replace(':user-agent', req.headers['user-agent'] || '') .replace( - ':content-length', - (res._headers && res._headers['content-length']) || - (res.__headers && res.__headers['Content-Length']) || + ':content-length', + (res._headers && res._headers['content-length']) || + (res.__headers && res.__headers['Content-Length']) || '-' ) .replace(/:req\[([^\]]+)\]/g, function(_, field){ return req.headers[field.toLowerCase()]; }) .replace(/:res\[([^\]]+)\]/g, function(_, field){ - return res._headers ? + return res._headers ? (res._headers[field.toLowerCase()] || res.__headers[field]) : (res.__headers && res.__headers[field]); }); @@ -155,9 +155,9 @@ function format(str, req, res) { * 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 + * 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, + * 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 @@ -175,15 +175,15 @@ function createNoLogCondition(nolog) { if (nolog) { if (nolog instanceof RegExp) { regexp = nolog; - } - + } + if (typeof nolog === 'string') { regexp = new RegExp(nolog); } - + if (Array.isArray(nolog)) { var regexpsAsStrings = nolog.map( - function convertToStrings(o) { + function convertToStrings(o) { return o.source ? o.source : o; } ); From 24268422cfa58bcfe6550f9e1837b1a8e034b5b3 Mon Sep 17 00:00:00 2001 From: Christo Fogelberg Date: Tue, 10 Mar 2015 06:13:49 +0000 Subject: [PATCH 20/37] lib/connect-logger.js - format takes tokens array instead of req, res --- lib/connect-logger.js | 71 +++++++++++++++++++++++++------------------ package.json | 3 +- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/lib/connect-logger.js b/lib/connect-logger.js index 16eef50..8162b50 100644 --- a/lib/connect-logger.js +++ b/lib/connect-logger.js @@ -1,5 +1,6 @@ "use strict"; var levels = require("./levels"); +var _ = require('underscore'); var DEFAULT_FORMAT = ':remote-addr - -' + ' ":method :url HTTP/:http-version"' + ' :status :content-length ":referrer"' + @@ -88,11 +89,12 @@ function getLogger(logger4js, options) { if(res.statusCode >= 400) level = levels.ERROR; } if (thislogger.isLevelEnabled(level)) { + var default_tokens = assemble_tokens(req, res); 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, default_tokens); }); if (line) thislogger.log(level, line); } else { - thislogger.log(level, format(fmt, req, res)); + thislogger.log(level, format(fmt, default_tokens)); } } }); @@ -103,6 +105,40 @@ function getLogger(logger4js, options) { }; } +/** + * 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) { + var default_tokens = []; + default_tokens.push({ token: ':url', replacement: req.originalUrl }); + default_tokens.push({ token: ':method', replacement: req.method }); + default_tokens.push({ token: ':status', replacement: res.__statusCode || res.statusCode }); + default_tokens.push({ token: ':response-time', replacement: res.responseTime }); + default_tokens.push({ token: ':date', replacement: new Date().toUTCString() }); + default_tokens.push({ token: ':referrer', replacement: req.headers.referer || req.headers.referrer || '' }); + default_tokens.push({ token: ':http-version', replacement: req.httpVersionMajor + '.' + req.httpVersionMinor }); + default_tokens.push({ token: ':remote-addr', replacement: req.ip || req._remoteAddress || + (req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress))) }); + default_tokens.push({ token: ':user-agent', replacement: req.headers['user-agent'] }); + default_tokens.push({ token: ':content-length', replacement: (res._headers && res._headers['content-length']) || + (res.__headers && res.__headers['Content-Length']) || '-' }); + default_tokens.push({ token: /:req\[([^\]]+)\]/g, replacement: function(_, field) { + return req.headers[field.toLowerCase()]; + } }); + default_tokens.push({ token: /:res\[([^\]]+)\]/g, replacement: function(_, field) { + return res._headers ? + (res._headers[field.toLowerCase()] || res.__headers[field]) + : (res.__headers && res.__headers[field]); + } }); + + return default_tokens; +}; + /** * Return formatted log line. * @@ -113,33 +149,10 @@ function getLogger(logger4js, options) { * @api private */ -function format(str, req, res) { - return str - .replace(':url', req.originalUrl) - .replace(':method', req.method) - .replace(':status', res.__statusCode || res.statusCode) - .replace(':response-time', res.responseTime) - .replace(':date', new Date().toUTCString()) - .replace(':referrer', req.headers.referer || req.headers.referrer || '') - .replace(':http-version', req.httpVersionMajor + '.' + req.httpVersionMinor) - .replace( - ':remote-addr', req.ip || req._remoteAddress || ( - req.socket && - (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress)) - )) - .replace(':user-agent', req.headers['user-agent'] || '') - .replace( - ':content-length', - (res._headers && res._headers['content-length']) || - (res.__headers && res.__headers['Content-Length']) || - '-' - ) - .replace(/:req\[([^\]]+)\]/g, function(_, field){ return req.headers[field.toLowerCase()]; }) - .replace(/:res\[([^\]]+)\]/g, function(_, field){ - return res._headers ? - (res._headers[field.toLowerCase()] || res.__headers[field]) - : (res.__headers && res.__headers[field]); - }); +function format(str, tokens) { + return _.reduce(tokens, function(current_string, token) { + return current_string.replace(token.token, token.replacement); + }, str); } /** diff --git a/package.json b/package.json index 0a2231e..bf7ba8d 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "dependencies": { "async": "~0.2.0", "readable-stream": "~1.0.2", - "semver": "~1.1.4" + "semver": "~1.1.4", + "underscore": "1.8.2" }, "devDependencies": { "vows": "0.7.0", From 25c543f8ae94e43e471874b573d1d4fd762c7187 Mon Sep 17 00:00:00 2001 From: Christo Fogelberg Date: Tue, 10 Mar 2015 06:18:02 +0000 Subject: [PATCH 21/37] lib/connect-logger.js - allow options.tokens --- lib/connect-logger.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/connect-logger.js b/lib/connect-logger.js index 8162b50..a53f7e0 100644 --- a/lib/connect-logger.js +++ b/lib/connect-logger.js @@ -89,12 +89,12 @@ function getLogger(logger4js, options) { if(res.statusCode >= 400) level = levels.ERROR; } if (thislogger.isLevelEnabled(level)) { - var default_tokens = assemble_tokens(req, res); + var combined_tokens = assemble_tokens(req, res, options.tokens || []); if (typeof fmt === 'function') { - var line = fmt(req, res, function(str){ return format(str, default_tokens); }); + var line = fmt(req, res, function(str){ return format(str, combined_tokens); }); if (line) thislogger.log(level, line); } else { - thislogger.log(level, format(fmt, default_tokens)); + thislogger.log(level, format(fmt, combined_tokens)); } } }); @@ -113,7 +113,19 @@ function getLogger(logger4js, options) { * @param {Array} custom_tokens [{ token: string-or-regexp, replacement: string-or-replace-function }] * @return {Array} */ -function assemble_tokens(req, res) { +function assemble_tokens(req, res, custom_tokens) { + var array_unique_tokens = function(array) { + var a = array.concat(); + for(var i=0; i Date: Tue, 10 Mar 2015 06:24:57 +0000 Subject: [PATCH 22/37] test/connect-logger-test.js - trailing whitespace Sublime removed --- test/connect-logger-test.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/connect-logger-test.js b/test/connect-logger-test.js index 96d0409..1f50416 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,7 @@ vows.describe('log4js connect logger').addBatch({ 'should output the response header': function(messages) { assert.equal(messages[0].message, 'application/cheese'); } - } - + } + } }).export(module); From 41504a755d0b6cd0627ae9e80871b4c6242e4cf3 Mon Sep 17 00:00:00 2001 From: Christo Fogelberg Date: Tue, 10 Mar 2015 06:37:53 +0000 Subject: [PATCH 23/37] test/connect-logger-test.js - tests for custom tokens --- test/connect-logger-test.js | 45 ++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/test/connect-logger-test.js b/test/connect-logger-test.js index 1f50416..dd1e717 100644 --- a/test/connect-logger-test.js +++ b/test/connect-logger-test.js @@ -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); From 4dfe14a0c253122a251bd62e01f4430949027f95 Mon Sep 17 00:00:00 2001 From: hasegawa-jun Date: Tue, 17 Mar 2015 09:25:22 +0900 Subject: [PATCH 24/37] added shutdownTimeout option --- lib/appenders/smtp.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/appenders/smtp.js b/lib/appenders/smtp.js index d907213..441d228 100644 --- a/lib/appenders/smtp.js +++ b/lib/appenders/smtp.js @@ -2,7 +2,8 @@ var layouts = require("../layouts") , mailer = require("nodemailer") , os = require('os') -, unsentCount = 0; +, unsentCount = 0 +, shutdownTimeout; /** * SMTP Appender. Sends logging events using SMTP protocol. @@ -12,6 +13,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) { @@ -22,6 +24,8 @@ function smtpAppender(config, layout) { var logEventBuffer = []; var sendTimer; + shutdownTimeout = ('shutdownTimeout' in config ? config.shutdownTimeout : 5) * 1000; + function sendBuffer() { if (logEventBuffer.length > 0) { @@ -87,8 +91,11 @@ function configure(config) { } function shutdown(cb) { - async.until(function() { - return unsentCount === 0; + if (shutdownTimeout > 0) { + setTimeout(function() { unsentCount = 0; }, shutdownTimeout); + } + async.whilst(function() { + return unsentCount > 0; }, function(done) { setTimeout(done, 100); }, cb); From af69eddd1cb3c03d47e77644abc1dfd02a0e2990 Mon Sep 17 00:00:00 2001 From: Quentin Brandon Date: Fri, 20 Mar 2015 11:51:23 +0900 Subject: [PATCH 25/37] Add optional timezoneOffset config for appenders Example: log4js.configure({ appenders: [{type: 'console', timezoneOffset: -540}], replaceConsole: true }); The expected value is the equivalent of (new Date).getTimezoneOffset() In this example, -540 is the value for JST. This allows machines members of world-wide-spread cluster to all report log time-stamps using the same timezone (or adapt the timezone to a local different from the system) --- lib/appenders/console.js | 6 +++--- lib/appenders/dateFile.js | 7 ++++--- lib/appenders/file.js | 8 +++++--- lib/appenders/fileSync.js | 8 +++++--- lib/appenders/smtp.js | 2 +- lib/date_format.js | 33 +++++++++++++++++++-------------- lib/layouts.js | 13 +++++++------ 7 files changed, 44 insertions(+), 33 deletions(-) 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 e4cab1a..a693976 100644 --- a/lib/appenders/file.js +++ b/lib/appenders/file.js @@ -26,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, compress) { +function fileAppender (file, layout, logSize, numBackups, compress, timezoneOffset) { var bytesWritten = 0; file = path.normalize(file); layout = layout || layouts.basicLayout; @@ -64,7 +66,7 @@ function fileAppender (file, layout, logSize, numBackups, compress) { openFiles.push(logFile); return function(loggingEvent) { - logFile.write(layout(loggingEvent) + eol, "utf8"); + logFile.write(layout(loggingEvent, timezoneOffset) + eol, "utf8"); }; } @@ -79,7 +81,7 @@ function configure(config, options) { config.filename = path.join(options.cwd, config.filename); } - return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.compress); + 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/smtp.js b/lib/appenders/smtp.js index fa3e775..a5d77b8 100644 --- a/lib/appenders/smtp.js +++ b/lib/appenders/smtp.js @@ -28,7 +28,7 @@ function smtpAppender(config, layout) { var firstEvent = logEventBuffer[0]; var body = ""; while (logEventBuffer.length > 0) { - body += layout(logEventBuffer.shift()) + "\n"; + body += layout(logEventBuffer.shift(), config.timezoneOffset) + "\n"; } var msg = { diff --git a/lib/date_format.js b/lib/date_format.js index d75ce20..3b34f42 100644 --- a/lib/date_format.js +++ b/lib/date_format.js @@ -21,9 +21,9 @@ function addZero(vNumber) { * Thanks to http://www.svendtofte.com/code/date_format/ * @private */ -function offset(date) { +function offset(timezoneOffset) { // Difference to Greenwich time (GMT) in hours - var os = Math.abs(date.getTimezoneOffset()); + var os = Math.abs(timezoneOffset); var h = String(Math.floor(os/60)); var m = String(os%60); if (h.length == 1) { @@ -32,26 +32,31 @@ function offset(date) { if (m.length == 1) { m = "0" + m; } - return date.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m; + return timezoneOffset < 0 ? "+"+h+m : "-"+h+m; } -exports.asString = function(/*format,*/ date) { +exports.asString = function(/*format,*/ date, timezoneOffset) { var format = exports.ISO8601_FORMAT; if (typeof(date) === "string") { format = arguments[0]; date = arguments[1]; + timezoneOffset = arguments[2]; } - - var vDay = addZero(date.getDate()); - var vMonth = addZero(date.getMonth()+1); - var vYearLong = addZero(date.getFullYear()); - var vYearShort = addZero(date.getFullYear().toString().substring(2,4)); + // make the date independent of the system timezone by working with UTC + if (timezoneOffset === undefined) { + timezoneOffset = date.getTimezoneOffset(); + } + date.setUTCMinutes(date.getUTCMinutes() - timezoneOffset); + var vDay = addZero(date.getUTCDate()); + var vMonth = addZero(date.getUTCMonth()+1); + var vYearLong = addZero(date.getUTCFullYear()); + var vYearShort = addZero(date.getUTCFullYear().toString().substring(2,4)); var vYear = (format.indexOf("yyyy") > -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); var formatted = format .replace(/dd/g, vDay) .replace(/MM/g, vMonth) diff --git a/lib/layouts.js b/lib/layouts.js index c7dd1ea..86deb9b 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); } From 435f4f320f8ac7bae9fa5c015f465cf5b2867fc9 Mon Sep 17 00:00:00 2001 From: Quentin Brandon Date: Fri, 20 Mar 2015 18:35:53 +0900 Subject: [PATCH 26/37] Bugfixes: get the unit tests passing again --- lib/date_format.js | 3 ++- lib/layouts.js | 7 ++++--- test/date_format-test.js | 19 +++++++++++++------ test/layouts-test.js | 2 +- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/date_format.js b/lib/date_format.js index 3b34f42..6bed145 100644 --- a/lib/date_format.js +++ b/lib/date_format.js @@ -44,7 +44,7 @@ exports.asString = function(/*format,*/ date, timezoneOffset) { } // make the date independent of the system timezone by working with UTC if (timezoneOffset === undefined) { - timezoneOffset = date.getTimezoneOffset(); + timezoneOffset = date.getTimezoneOffset(); } date.setUTCMinutes(date.getUTCMinutes() - timezoneOffset); var vDay = addZero(date.getUTCDate()); @@ -57,6 +57,7 @@ exports.asString = function(/*format,*/ date, timezoneOffset) { 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 86deb9b..8e7e8a2 100644 --- a/lib/layouts.js +++ b/lib/layouts.js @@ -140,11 +140,12 @@ 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]+)?([\[\]cdhmnprzxy%])(\{([^\}]+)\})?|([^%]+)/; @@ -178,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() { @@ -198,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) { 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/layouts-test.js b/test/layouts-test.js index 01c777e..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"; } From 57fc9e7aa0dad055dc19a3867125899b8e968528 Mon Sep 17 00:00:00 2001 From: alawatthe Date: Sat, 11 Apr 2015 12:03:14 +0200 Subject: [PATCH 27/37] The smtp appender now works with the current version of nodemailer --- lib/appenders/smtp.js | 2 +- test/smtpAppender-test.js | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/appenders/smtp.js b/lib/appenders/smtp.js index fa3e775..c709d54 100644 --- a/lib/appenders/smtp.js +++ b/lib/appenders/smtp.js @@ -24,7 +24,7 @@ function smtpAppender(config, layout) { 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 = ""; while (logEventBuffer.length > 0) { 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' } } }); From 085a88d6fc1946d4cd789821a09b14e8e0f96322 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 17 Apr 2015 08:05:47 +1000 Subject: [PATCH 28/37] upgraded semver package version (github issue #291) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bf7ba8d..111f774 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "dependencies": { "async": "~0.2.0", "readable-stream": "~1.0.2", - "semver": "~1.1.4", + "semver": "~4.3.3", "underscore": "1.8.2" }, "devDependencies": { From 1cdd37c4882e2c725c264294f516c672170e26bc Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 17 Apr 2015 08:21:00 +1000 Subject: [PATCH 29/37] 0.6.23 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 111f774..4123302 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.6.22", + "version": "0.6.23", "description": "Port of Log4js to work with node.", "keywords": [ "logging", From adeb7142438de480e96baf0b17fd438a131bc3a1 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 17 Apr 2015 08:31:12 +1000 Subject: [PATCH 30/37] made calling logger.log safe if level is not provided as first argument (github issue #279) --- examples/example.js | 2 ++ lib/logger.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) 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/logger.js b/lib/logger.js index 67bb35d..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); From ebfcf94db36c3abde79a3587427a5a44397b441f Mon Sep 17 00:00:00 2001 From: hasegawa-jun Date: Fri, 17 Apr 2015 10:08:00 +0900 Subject: [PATCH 31/37] make sure to call require() --- lib/appenders/smtp.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/appenders/smtp.js b/lib/appenders/smtp.js index 6fa4b66..1ec1a0f 100644 --- a/lib/appenders/smtp.js +++ b/lib/appenders/smtp.js @@ -2,6 +2,7 @@ var layouts = require("../layouts") , mailer = require("nodemailer") , os = require('os') +, async = require('async') , unsentCount = 0 , shutdownTimeout; From 9fe32d06e30680b31a558fd96ed3bfe048da3051 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 17 Apr 2015 11:36:22 +1000 Subject: [PATCH 32/37] 0.6.24 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4123302..5d04828 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.6.23", + "version": "0.6.24", "description": "Port of Log4js to work with node.", "keywords": [ "logging", From 3cf1d697e8f8f15abd2bad966f62c202b9f620b1 Mon Sep 17 00:00:00 2001 From: Skylar Lowery Date: Fri, 24 Apr 2015 11:14:17 -0600 Subject: [PATCH 33/37] Remove GELF flag when capturing custom fields * Logstash (which supports GELF) drops messages with this key * There's no particular need to keep this key --- lib/appenders/gelf.js | 2 ++ test/gelfAppender-test.js | 1 + 2 files changed, 3 insertions(+) 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/test/gelfAppender-test.js b/test/gelfAppender-test.js index 6b33b72..4238d6f 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.equal(message.GELF, void(0)); // 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 From 1454ae5350fe921e95185a2041b4069586d20332 Mon Sep 17 00:00:00 2001 From: Skylar Lowery Date: Mon, 27 Apr 2015 11:00:44 -0600 Subject: [PATCH 34/37] Use isUndefined test method vs equals undefined *doh --- test/gelfAppender-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gelfAppender-test.js b/test/gelfAppender-test.js index 4238d6f..76fb5ea 100644 --- a/test/gelfAppender-test.js +++ b/test/gelfAppender-test.js @@ -244,7 +244,7 @@ vows.describe('log4js gelfAppender').addBatch({ }, 'should pick up the options': function(message) { assert.equal(message.host, 'cheese'); - assert.equal(message.GELF, void(0)); // make sure flag was removed + 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 From 8dff114b4935a051e09115c89bec87aac095db15 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Wed, 13 May 2015 08:03:35 +1000 Subject: [PATCH 35/37] 0.6.25 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d04828..b0fa65c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.6.24", + "version": "0.6.25", "description": "Port of Log4js to work with node.", "keywords": [ "logging", From 0c74fbbf7d93432f70a4765e2cd12d7a73616375 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Wed, 13 May 2015 08:06:16 +1000 Subject: [PATCH 36/37] added node 0.12 to travis config --- .travis.yml | 1 + 1 file changed, 1 insertion(+) 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" From 3bf2bade32b46748adc56c5574faa6c79ceffd2d Mon Sep 17 00:00:00 2001 From: Francisco Dans Date: Mon, 1 Jun 2015 15:30:46 +0200 Subject: [PATCH 37/37] x-forwarded-for --- lib/connect-logger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connect-logger.js b/lib/connect-logger.js index a53f7e0..4f7a312 100644 --- a/lib/connect-logger.js +++ b/lib/connect-logger.js @@ -134,7 +134,7 @@ function assemble_tokens(req, res, custom_tokens) { default_tokens.push({ token: ':date', replacement: new Date().toUTCString() }); default_tokens.push({ token: ':referrer', replacement: req.headers.referer || req.headers.referrer || '' }); default_tokens.push({ token: ':http-version', replacement: req.httpVersionMajor + '.' + req.httpVersionMinor }); - default_tokens.push({ token: ':remote-addr', replacement: req.ip || req._remoteAddress || + default_tokens.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))) }); default_tokens.push({ token: ':user-agent', replacement: req.headers['user-agent'] }); default_tokens.push({ token: ':content-length', replacement: (res._headers && res._headers['content-length']) ||