Merge pull request #20 from danbell/master

Added ability to reload configuration file periodically.
This commit is contained in:
Gareth Jones
2011-07-25 18:16:36 -07:00
3 changed files with 281 additions and 132 deletions

View File

@@ -52,7 +52,23 @@ Output:
You can either configure the appenders and log levels manually (as above), or provide a You can either configure the appenders and log levels manually (as above), or provide a
configuration file (`log4js.configure('path/to/file.json')`) explicitly, or just let log4js look for a file called `log4js.json` (it looks in the current directory first, then the require paths, and finally looks for the default config included in the same directory as the `log4js.js` file). configuration file (`log4js.configure('path/to/file.json')`) explicitly, or just let log4js look for a file called `log4js.json` (it looks in the current directory first, then the require paths, and finally looks for the default config included in the same directory as the `log4js.js` file).
An example file can be found in `test/log4js.json`. An example config file with log rolling is in `test/with-log-rolling.json` An example file can be found in `test/log4js.json`. An example config file with log rolling is in `test/with-log-rolling.json`.
By default, the configuration file is checked for changes every 60 seconds, and if changed, reloaded. This allows changes to logging levels
to occur without restarting the application.
To turn off configuration file change checking, configure with:
var log4js = require('log4js');
log4js.configure(undefined, {}); // load 'log4js.json' from NODE_PATH
Or:
log4js.configure('my_log4js_configuration.json', {});
To specify a different period:
log4js.configure(undefined, { reloadSecs: 300 }); // load 'log4js.json' from NODE_PATH
You can also pass an object to the configure function, which has the same properties as the json versions. You can also pass an object to the configure function, which has the same properties as the json versions.
## connect/express logger ## connect/express logger

View File

@@ -67,25 +67,25 @@ function getLogger (categoryName) {
// Use default logger if categoryName is not specified or invalid // Use default logger if categoryName is not specified or invalid
if (!(typeof categoryName == "string")) { if (!(typeof categoryName == "string")) {
categoryName = DEFAULT_CATEGORY; categoryName = DEFAULT_CATEGORY;
} }
var appenderList; var appenderList;
if (!loggers[categoryName]) { if (!loggers[categoryName]) {
// Create the logger for this name if it doesn't already exist // Create the logger for this name if it doesn't already exist
loggers[categoryName] = new Logger(categoryName); loggers[categoryName] = new Logger(categoryName);
if (appenders[categoryName]) { if (appenders[categoryName]) {
appenderList = appenders[categoryName]; appenderList = appenders[categoryName];
appenderList.forEach(function(appender) { appenderList.forEach(function(appender) {
loggers[categoryName].addListener("log", appender); loggers[categoryName].addListener("log", appender);
}); });
} }
if (appenders[ALL_CATEGORIES]) { if (appenders[ALL_CATEGORIES]) {
appenderList = appenders[ALL_CATEGORIES]; appenderList = appenders[ALL_CATEGORIES];
appenderList.forEach(function(appender) { appenderList.forEach(function(appender) {
loggers[categoryName].addListener("log", appender); loggers[categoryName].addListener("log", appender);
}); });
} }
} }
return loggers[categoryName]; return loggers[categoryName];
@@ -98,65 +98,69 @@ function addAppender () {
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
var appender = args.shift(); var appender = args.shift();
if (args.length == 0 || args[0] === undefined) { if (args.length == 0 || args[0] === undefined) {
args = [ ALL_CATEGORIES ]; args = [ ALL_CATEGORIES ];
} }
//argument may already be an array //argument may already be an array
if (Array.isArray(args[0])) { if (Array.isArray(args[0])) {
args = args[0]; args = args[0];
} }
args.forEach(function(category) { args.forEach(function(category) {
if (!appenders[category]) { if (!appenders[category]) {
appenders[category] = []; appenders[category] = [];
} }
appenders[category].push(appender); appenders[category].push(appender);
if (category === ALL_CATEGORIES) { if (category === ALL_CATEGORIES) {
for (var logger in loggers) { for (var logger in loggers) {
if (loggers.hasOwnProperty(logger)) { if (loggers.hasOwnProperty(logger)) {
loggers[logger].addListener("log", appender); loggers[logger].addListener("log", appender);
} }
} }
} else if (loggers[category]) { } else if (loggers[category]) {
loggers[category].addListener("log", appender); loggers[category].addListener("log", appender);
} }
}); });
} }
function clearAppenders () { function clearAppenders () {
appenders = {}; appenders = {};
for (var logger in loggers) { for (var logger in loggers) {
if (loggers.hasOwnProperty(logger)) { if (loggers.hasOwnProperty(logger)) {
loggers[logger].removeAllListeners("log"); loggers[logger].removeAllListeners("log");
} }
} }
} }
function configureAppenders(appenderList) { function configureAppenders(appenderList) {
clearAppenders(); clearAppenders();
if (appenderList) { if (appenderList) {
appenderList.forEach(function(appenderConfig) { appenderList.forEach(function(appenderConfig) {
var appender; var appender;
appenderConfig.makers = appenderMakers; appenderConfig.makers = appenderMakers;
appender = appenderMakers[appenderConfig.type](appenderConfig); appender = appenderMakers[appenderConfig.type](appenderConfig);
if (appender) { if (appender) {
addAppender(appender, appenderConfig.category); addAppender(appender, appenderConfig.category);
} else { } else {
throw new Error("log4js configuration problem for "+sys.inspect(appenderConfig)); throw new Error("log4js configuration problem for "+sys.inspect(appenderConfig));
} }
}); });
} else { } else {
addAppender(consoleAppender()); addAppender(consoleAppender());
} }
} }
function configureLevels(levels) { function configureLevels(levels) {
if (levels) { if (levels) {
for (var category in levels) { for (var category in levels) {
if (levels.hasOwnProperty(category)) { if (levels.hasOwnProperty(category)) {
getLogger(category).setLevel(levels[category]); getLogger(category).setLevel(levels[category]);
} }
} }
} else {
for (l in loggers) {
loggers[l].setLevel();
}
} }
} }
@@ -214,18 +218,18 @@ Logger.prototype.isLevelEnabled = function(otherLevel) {
['Trace','Debug','Info','Warn','Error','Fatal'].forEach( ['Trace','Debug','Info','Warn','Error','Fatal'].forEach(
function(levelString) { function(levelString) {
var level = levels.toLevel(levelString); var level = levels.toLevel(levelString);
Logger.prototype['is'+levelString+'Enabled'] = function() { Logger.prototype['is'+levelString+'Enabled'] = function() {
return this.isLevelEnabled(level); return this.isLevelEnabled(level);
}; };
Logger.prototype[levelString.toLowerCase()] = function () { Logger.prototype[levelString.toLowerCase()] = function () {
if (this.isLevelEnabled(level)) { if (this.isLevelEnabled(level)) {
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
args.unshift(level); args.unshift(level);
Logger.prototype.log.apply(this, args); Logger.prototype.log.apply(this, args);
} }
}; };
} }
); );
@@ -242,21 +246,6 @@ function getDefaultLogger () {
return getLogger(DEFAULT_CATEGORY); return getLogger(DEFAULT_CATEGORY);
} }
function configure (configurationFileOrObject) {
var config = configurationFileOrObject;
if (typeof(config) === "string") {
config = JSON.parse(fs.readFileSync(config, "utf8"));
}
if (config) {
try {
configureAppenders(config.appenders);
configureLevels(config.levels);
} catch (e) {
throw new Error("Problem reading log4js config " + sys.inspect(config) + ". Error was \"" + e.message + "\" ("+e.stack+")");
}
}
}
function findConfiguration() { function findConfiguration() {
//add current directory onto the list of configPaths //add current directory onto the list of configPaths
var paths = ['.'].concat(require.paths); var paths = ['.'].concat(require.paths);
@@ -276,6 +265,79 @@ function findConfiguration() {
return undefined; return undefined;
} }
function loadConfigurationFile(filename) {
filename = filename || findConfiguration();
if (filename) {
return JSON.parse(fs.readFileSync(filename, "utf8"));
}
return undefined;
}
function configureOnceOff(config) {
if (config) {
try {
configureAppenders(config.appenders);
configureLevels(config.levels);
} catch (e) {
throw new Error("Problem reading log4js config " + sys.inspect(config) + ". Error was \"" + e.message + "\" ("+e.stack+")");
}
}
}
var configState = {};
function reloadConfiguration() {
var filename = configState.filename || findConfiguration(),
mtime;
if (!filename) {
// can't find anything to reload
return;
}
try {
mtime = fs.statSync(filename).mtime;
} catch (e) {
getLogger('log4js').warn('Failed to load configuration file ' + filename);
return;
}
if (configState.lastFilename && configState.lastFilename === filename) {
if (mtime.getTime() > configState.lastMTime.getTime()) {
configState.lastMTime = mtime;
} else {
filename = null;
}
} else {
configState.lastFilename = filename;
configState.lastMTime = mtime;
}
configureOnceOff(loadConfigurationFile(filename));
}
function initReloadConfiguration(filename, options) {
if (configState.timerId) {
clearInterval(configState.timerId);
delete configState.timerId;
}
configState.filename = filename;
configState.timerId = setInterval(reloadConfiguration, options.reloadSecs*1000);
}
function configure (configurationFileOrObject, options) {
var config = configurationFileOrObject;
if (config === undefined || config === null || typeof(config) === 'string') {
options = options || { reloadSecs: 60 };
if (options.reloadSecs) {
initReloadConfiguration(config, options);
}
configureOnceOff(loadConfigurationFile(config));
} else {
options = options || {};
if (options.reloadSecs) {
getLogger('log4js').warn('Ignoring configuration reload parameter for "object" configuration.');
}
configureOnceOff(config);
}
}
function replaceConsole(logger) { function replaceConsole(logger) {
function replaceWith(fn) { function replaceWith(fn) {
return function() { return function() {

View File

@@ -4,54 +4,54 @@ var vows = require('vows')
vows.describe('log4js').addBatch({ vows.describe('log4js').addBatch({
'getLogger': { 'getLogger': {
topic: function() { topic: function() {
var log4js = require('../lib/log4js'); var log4js = require('../lib/log4js');
log4js.clearAppenders(); log4js.clearAppenders();
var logger = log4js.getLogger('tests'); var logger = log4js.getLogger('tests');
logger.setLevel("DEBUG"); logger.setLevel("DEBUG");
return logger; return logger;
}, },
'should take a category and return a logger': function(logger) { 'should take a category and return a logger': function(logger) {
assert.equal(logger.category, 'tests'); assert.equal(logger.category, 'tests');
assert.equal(logger.level.toString(), "DEBUG"); assert.equal(logger.level.toString(), "DEBUG");
assert.isFunction(logger.debug); assert.isFunction(logger.debug);
assert.isFunction(logger.info); assert.isFunction(logger.info);
assert.isFunction(logger.warn); assert.isFunction(logger.warn);
assert.isFunction(logger.error); assert.isFunction(logger.error);
assert.isFunction(logger.fatal); assert.isFunction(logger.fatal);
}, },
'log events' : { 'log events' : {
topic: function(logger) { topic: function(logger) {
var events = []; var events = [];
logger.addListener("log", function (logEvent) { events.push(logEvent); }); logger.addListener("log", function (logEvent) { events.push(logEvent); });
logger.debug("Debug event"); logger.debug("Debug event");
logger.trace("Trace event 1"); logger.trace("Trace event 1");
logger.trace("Trace event 2"); logger.trace("Trace event 2");
logger.warn("Warning event"); logger.warn("Warning event");
logger.error("Aargh!", new Error("Pants are on fire!")); logger.error("Aargh!", new Error("Pants are on fire!"));
logger.error("Simulated CouchDB problem", { err: 127, cause: "incendiary underwear" }); logger.error("Simulated CouchDB problem", { err: 127, cause: "incendiary underwear" });
return events; return events;
}, },
'should emit log events': function(events) { 'should emit log events': function(events) {
assert.equal(events[0].level.toString(), 'DEBUG'); assert.equal(events[0].level.toString(), 'DEBUG');
assert.equal(events[0].data[0], 'Debug event'); assert.equal(events[0].data[0], 'Debug event');
assert.instanceOf(events[0].startTime, Date); assert.instanceOf(events[0].startTime, Date);
}, },
'should not emit events of a lower level': function(events) { 'should not emit events of a lower level': function(events) {
assert.length(events, 4); assert.length(events, 4);
assert.equal(events[1].level.toString(), 'WARN'); assert.equal(events[1].level.toString(), 'WARN');
}, },
'should include the error if passed in': function (events) { 'should include the error if passed in': function (events) {
assert.instanceOf(events[2].data[1], Error); assert.instanceOf(events[2].data[1], Error);
assert.equal(events[2].data[1].message, 'Pants are on fire!'); assert.equal(events[2].data[1].message, 'Pants are on fire!');
} }
}, },
}, },
@@ -65,7 +65,7 @@ vows.describe('log4js').addBatch({
'configuration when passed as object': { 'configuration when passed as object': {
topic: function() { topic: function() {
var appenderConfig var appenderConfig
, log4js = sandbox.require( , log4js = sandbox.require(
'../lib/log4js' '../lib/log4js'
, { requires: , { requires:
@@ -82,22 +82,22 @@ vows.describe('log4js').addBatch({
} }
) )
, config = { , config = {
"appenders": [ "appenders": [
{ {
"type" : "file", "type" : "file",
"filename" : "cheesy-wotsits.log", "filename" : "cheesy-wotsits.log",
"maxLogSize" : 1024, "maxLogSize" : 1024,
"backups" : 3, "backups" : 3,
"pollInterval" : 15 "pollInterval" : 15
} }
] ]
}; };
log4js.configure(config); log4js.configure(config);
return appenderConfig; return appenderConfig;
}, },
'should be passed to appender config': function(configuration) { 'should be passed to appender config': function(configuration) {
assert.equal(configuration.filename, 'cheesy-wotsits.log'); assert.equal(configuration.filename, 'cheesy-wotsits.log');
} }
}, },
'configuration when passed as filename': { 'configuration when passed as filename': {
@@ -169,7 +169,7 @@ vows.describe('log4js').addBatch({
} }
} }
); );
logger = log4js.getLogger("some-logger"); logger = log4js.getLogger("some-logger");
logger.debug("This is a test"); logger.debug("This is a test");
}, },
'should default to the console appender': function(evt) { 'should default to the console appender': function(evt) {
@@ -295,7 +295,7 @@ vows.describe('log4js').addBatch({
return fakeConsole.appender(); return fakeConsole.appender();
} }
}, },
log4js = sandbox.require( log4js = sandbox.require(
'../lib/log4js', '../lib/log4js',
{ {
requires: { requires: {
@@ -305,7 +305,7 @@ vows.describe('log4js').addBatch({
} }
); );
logger = log4js.getLogger('a-test'); logger = log4js.getLogger('a-test');
logger.debug("this is a test"); logger.debug("this is a test");
return [ pathsChecked, appenderEvent, modulePath ]; return [ pathsChecked, appenderEvent, modulePath ];
}, },
@@ -380,16 +380,87 @@ vows.describe('log4js').addBatch({
} }
}, },
'configuration persistence' : { 'configuration persistence' : {
'should maintain appenders between requires': function () { 'should maintain appenders between requires': function () {
var logEvent, firstLog4js = require('../lib/log4js'), secondLog4js; var logEvent, firstLog4js = require('../lib/log4js'), secondLog4js;
firstLog4js.clearAppenders(); firstLog4js.clearAppenders();
firstLog4js.addAppender(function(evt) { logEvent = evt; }); firstLog4js.addAppender(function(evt) { logEvent = evt; });
secondLog4js = require('../lib/log4js'); secondLog4js = require('../lib/log4js');
secondLog4js.getLogger().info("This should go to the appender defined in firstLog4js"); secondLog4js.getLogger().info("This should go to the appender defined in firstLog4js");
assert.equal(logEvent.data[0], "This should go to the appender defined in firstLog4js"); assert.equal(logEvent.data[0], "This should go to the appender defined in firstLog4js");
} }
},
'configuration reload' : {
topic: function() {
var pathsChecked = [],
logEvents = [],
logger,
modulePath = require('path').normalize(__dirname + '/../lib/log4js.json'),
fakeFS = {
config: { appenders: [ { type: 'console', layout: { type: 'messagePassThrough' } } ],
levels: { 'a-test' : 'INFO' } },
readdirSync: function(dir) {
return require('fs').readdirSync(dir);
},
readFileSync: function (file, encoding) {
assert.equal(file, modulePath);
assert.equal(encoding, 'utf8');
return JSON.stringify(fakeFS.config);
},
statSync: function (path) {
pathsChecked.push(path);
if (path === modulePath) {
return { mtime: new Date() };
} else {
throw new Error("no such file");
}
}
},
fakeConsole = {
'name': 'console',
'appender': function () {
return function(evt) { logEvents.push(evt); };
},
'configure': function (config) {
return fakeConsole.appender();
}
},
setIntervalCallback,
fakeSetInterval = function(cb, timeout) {
setIntervalCallback = cb;
},
log4js = sandbox.require(
'../lib/log4js',
{
requires: {
'fs': fakeFS,
'./appenders/console.js': fakeConsole
},
globals: {
'console': fakeConsole,
'setInterval' : fakeSetInterval,
}
}
);
logger = log4js.getLogger('a-test');
logger.info("info1");
logger.debug("debug2 - should be ignored");
delete fakeFS.config.levels;
setIntervalCallback();
logger.info("info3");
logger.debug("debug4");
return [ pathsChecked, logEvents, modulePath ];
},
'should configure log4js from first log4js.json found': function(args) {
var logEvents = args[1];
assert.length(logEvents, 3);
assert.equal(logEvents[0].data[0], 'info1');
assert.equal(logEvents[1].data[0], 'info3');
assert.equal(logEvents[2].data[0], 'debug4');
}
} }
}).export(module); }).export(module);