refactoring to allow dependency injection

This commit is contained in:
csausdev
2011-04-07 07:21:20 +08:00
committed by Gareth Jones
parent fc3d50846d
commit 76fea28bbb
6 changed files with 481 additions and 673 deletions
+5 -4
View File
@@ -4,20 +4,21 @@ This is a conversion of the [log4js](http://log4js.berlios.de/index.html)
framework to work with [node](http://nodejs.org). I've mainly stripped out the browser-specific code framework to work with [node](http://nodejs.org). I've mainly stripped out the browser-specific code
and tidied up some of the javascript. and tidied up some of the javascript.
NOTE: since v0.2.0 require('log4js') returns a function, so you need to call that function in your code before you can use it. I've done this to make testing easier (allows dependency injection).
## installation ## installation
npm install log4js npm install log4js
## tests ## tests
Run the tests with `node tests.js`. They use the awesome [jspec](http://visionmedia.github.com/jspec) - 3.1.3 Tests now use [vows](http://vowsjs.org), run with `vows test/logging.js`. I am slowly porting the previous tests from jspec (run those with `node tests.js`), since jspec is no longer maintained.
## usage ## usage
See example.js: See example.js:
var log4js = require('log4js'); var log4js = require('log4js')(); //note the need to call the function
log4js.addAppender(log4js.consoleAppender()); log4js.addAppender(log4js.consoleAppender());
log4js.addAppender(log4js.fileAppender('logs/cheese.log'), 'cheese'); log4js.addAppender(log4js.fileAppender('logs/cheese.log'), 'cheese');
@@ -40,7 +41,7 @@ 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')`). An example file can be found configuration file (`log4js.configure('path/to/file.json')`). An example file can be found
in spec/fixtures/log4js.json in test/log4js.json
## todo ## todo
+158 -213
View File
@@ -1,7 +1,3 @@
var fs = require('fs'), sys = require('sys');
var DEFAULT_CATEGORY = '[default]';
var ALL_CATEGORIES = '[all]';
/* /*
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -26,7 +22,7 @@ var ALL_CATEGORIES = '[all]';
* *
* <h3>Example:</h3> * <h3>Example:</h3>
* <pre> * <pre>
* var logging = require('log4js-node'); * var logging = require('log4js-node')();
* //add an appender that logs all messages to stdout. * //add an appender that logs all messages to stdout.
* logging.addAppender(logging.consoleAppender()); * logging.addAppender(logging.consoleAppender());
* //add an appender that logs "some-category" to a file * //add an appender that logs "some-category" to a file
@@ -48,125 +44,15 @@ var ALL_CATEGORIES = '[all]';
* @static * @static
* Website: http://log4js.berlios.de * Website: http://log4js.berlios.de
*/ */
var log4js = { module.exports = function (fileSystem) {
var fs = fileSystem || require('fs'),
/** sys = require('sys'),
* Current version of log4js-node. events = require('events'),
* @static DEFAULT_CATEGORY = '[default]',
* @final ALL_CATEGORIES = '[all]',
*/ loggers = {},
version: "0.1.1", appenders = {},
levels = {
/**
* Date of logger initialized.
* @static
* @final
*/
applicationStartDate: new Date(),
/**
* Hashtable of loggers.
* @static
* @final
* @private
*/
loggers: {},
appenders: {}
};
/**
* Get a logger instance. Instance is cached on categoryName level.
* @param {String} categoryName name of category to log to.
* @return {Logger} instance of logger for the category
* @static
*/
exports.getLogger = log4js.getLogger = function(categoryName) {
// Use default logger if categoryName is not specified or invalid
if (!(typeof categoryName == "string")) {
categoryName = DEFAULT_CATEGORY;
}
var appenderList;
if (!log4js.loggers[categoryName]) {
// Create the logger for this name if it doesn't already exist
log4js.loggers[categoryName] = new Logger(categoryName);
if (log4js.appenders[categoryName]) {
appenderList = log4js.appenders[categoryName];
appenderList.forEach(function(appender) {
log4js.loggers[categoryName].addListener("log", appender);
});
}
if (log4js.appenders[ALL_CATEGORIES]) {
appenderList = log4js.appenders[ALL_CATEGORIES];
appenderList.forEach(function(appender) {
log4js.loggers[categoryName].addListener("log", appender);
});
}
}
return log4js.loggers[categoryName];
};
/**
* Get the default logger instance.
* @return {Logger} instance of default logger
* @static
*/
exports.getDefaultLogger = log4js.getDefaultLogger = function() {
return log4js.getLogger(DEFAULT_CATEGORY);
};
/**
* args are appender, then zero or more categories
*/
exports.addAppender = log4js.addAppender = function () {
var args = Array.prototype.slice.call(arguments);
var appender = args.shift();
if (args.length == 0) {
args = [ ALL_CATEGORIES ];
}
//argument may already be an array
if (args[0].forEach) {
args = args[0];
}
args.forEach(function(category) {
if (!log4js.appenders[category]) {
log4js.appenders[category] = [];
}
log4js.appenders[category].push(appender);
if (category === ALL_CATEGORIES) {
for (var logger in log4js.loggers) {
if (log4js.loggers.hasOwnProperty(logger)) {
log4js.loggers[logger].addListener("log", appender);
}
}
} else if (log4js.loggers[category]) {
log4js.loggers[category].addListener("log", appender);
}
});
};
exports.clearAppenders = log4js.clearAppenders = function() {
log4js.appenders = [];
for (var logger in log4js.loggers) {
if (log4js.loggers.hasOwnProperty(logger)) {
log4js.loggers[logger].removeAllListeners("log");
}
}
};
exports.configure = log4js.configure = function(configurationFile) {
var config = JSON.parse(fs.readFileSync(configurationFile));
configureAppenders(config.appenders);
configureLevels(config.levels);
};
exports.levels = log4js.levels = {
ALL: new Level(Number.MIN_VALUE, "ALL"), ALL: new Level(Number.MIN_VALUE, "ALL"),
TRACE: new Level(5000, "TRACE"), TRACE: new Level(5000, "TRACE"),
DEBUG: new Level(10000, "DEBUG"), DEBUG: new Level(10000, "DEBUG"),
@@ -175,9 +61,8 @@ exports.levels = log4js.levels = {
ERROR: new Level(40000, "ERROR"), ERROR: new Level(40000, "ERROR"),
FATAL: new Level(50000, "FATAL"), FATAL: new Level(50000, "FATAL"),
OFF: new Level(Number.MAX_VALUE, "OFF") OFF: new Level(Number.MAX_VALUE, "OFF")
}; },
appenderMakers = {
var appenderMakers = {
"file": function(config) { "file": function(config) {
var layout; var layout;
if (config.layout) { if (config.layout) {
@@ -196,9 +81,8 @@ var appenderMakers = {
var appender = appenderMakers[config.appender.type](config.appender); var appender = appenderMakers[config.appender.type](config.appender);
return logLevelFilter(config.level, appender); return logLevelFilter(config.level, appender);
} }
}; },
layoutMakers = {
var layoutMakers = {
"messagePassThrough": function() { return messagePassThroughLayout; }, "messagePassThrough": function() { return messagePassThroughLayout; },
"basic": function() { return basicLayout; }, "basic": function() { return basicLayout; },
"pattern": function (config) { "pattern": function (config) {
@@ -207,19 +91,102 @@ var layoutMakers = {
} }
}; };
/**
* Get a logger instance. Instance is cached on categoryName level.
* @param {String} categoryName name of category to log to.
* @return {Logger} instance of logger for the category
* @static
*/
function getLogger (categoryName) {
// Use default logger if categoryName is not specified or invalid
if (!(typeof categoryName == "string")) {
categoryName = DEFAULT_CATEGORY;
}
var appenderList;
if (!loggers[categoryName]) {
// 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);
});
}
if (appenders[ALL_CATEGORIES]) {
appenderList = appenders[ALL_CATEGORIES];
appenderList.forEach(function(appender) {
loggers[categoryName].addListener("log", appender);
});
}
}
return loggers[categoryName];
}
/**
* args are appender, then zero or more categories
*/
function addAppender () {
var args = Array.prototype.slice.call(arguments);
var appender = args.shift();
if (args.length == 0) {
args = [ ALL_CATEGORIES ];
}
//argument may already be an array
if (args[0].forEach) {
args = args[0];
}
args.forEach(function(category) {
if (!appenders[category]) {
appenders[category] = [];
}
appenders[category].push(appender);
if (category === ALL_CATEGORIES) {
for (var logger in loggers) {
if (loggers.hasOwnProperty(logger)) {
loggers[logger].addListener("log", appender);
}
}
} else if (loggers[category]) {
loggers[category].addListener("log", appender);
}
});
}
function clearAppenders () {
appenders = [];
for (var logger in loggers) {
if (loggers.hasOwnProperty(logger)) {
loggers[logger].removeAllListeners("log");
}
}
}
function configure (configurationFile) {
var config = JSON.parse(fs.readFileSync(configurationFile));
configureAppenders(config.appenders);
configureLevels(config.levels);
}
function configureAppenders(appenderList) { function configureAppenders(appenderList) {
log4js.clearAppenders(); clearAppenders();
if (appenderList) { if (appenderList) {
appenderList.forEach(function(appenderConfig) { appenderList.forEach(function(appenderConfig) {
var appender = appenderMakers[appenderConfig.type](appenderConfig); var appender = appenderMakers[appenderConfig.type](appenderConfig);
if (appender) { if (appender) {
log4js.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 {
log4js.addAppender(consoleAppender); addAppender(consoleAppender);
} }
} }
@@ -227,24 +194,16 @@ 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)) {
log4js.getLogger(category).setLevel(levels[category]); getLogger(category).setLevel(levels[category]);
} }
} }
} }
} }
/**
* Log4js.Level Enumeration. Do not use directly. Use static objects instead.
* @constructor
* @param {Number} level number of level
* @param {String} levelString String representation of level
* @private
*/
function Level(level, levelStr) { function Level(level, levelStr) {
this.level = level; this.level = level;
this.levelStr = levelStr; this.levelStr = levelStr;
}; }
/** /**
* converts given String to corresponding Level * converts given String to corresponding Level
@@ -261,8 +220,8 @@ Level.toLevel = function(sArg, defaultLevel) {
if (typeof sArg == "string") { if (typeof sArg == "string") {
var s = sArg.toUpperCase(); var s = sArg.toUpperCase();
if (log4js.levels[s]) { if (levels[s]) {
return log4js.levels[s]; return levels[s];
} }
} }
return defaultLevel; return defaultLevel;
@@ -289,44 +248,14 @@ Level.prototype.isGreaterThanOrEqualTo = function(otherLevel) {
* @param {Log4js.Logger} logger the associated logger * @param {Log4js.Logger} logger the associated logger
* @author Seth Chisamore * @author Seth Chisamore
*/ */
LoggingEvent = function(categoryName, level, message, exception, logger) { function LoggingEvent (categoryName, level, message, exception, logger) {
/**
* the timestamp of the Logging Event
* @type Date
* @private
*/
this.startTime = new Date(); this.startTime = new Date();
/**
* category of event
* @type String
* @private
*/
this.categoryName = categoryName; this.categoryName = categoryName;
/**
* the logging message
* @type String
* @private
*/
this.message = message; this.message = message;
/**
* the logging exception
* @type Exception
* @private
*/
this.exception = exception; this.exception = exception;
/**
* level of log
* @type Log4js.Level
* @private
*/
this.level = level; this.level = level;
/**
* reference to logger
* @type Log4js.Logger
* @private
*/
this.logger = logger; this.logger = logger;
}; }
/** /**
* Logger to log messages. * Logger to log messages.
@@ -335,15 +264,14 @@ LoggingEvent = function(categoryName, level, message, exception, logger) {
* @param name name of category to log to * @param name name of category to log to
* @author Stephan Strittmatter * @author Stephan Strittmatter
*/ */
Logger = function(name, level) { function Logger (name, level) {
this.category = name || DEFAULT_CATEGORY; this.category = name || DEFAULT_CATEGORY;
this.level = Level.toLevel(level, log4js.levels.TRACE); this.level = Level.toLevel(level, levels.TRACE);
}; }
sys.inherits(Logger, events.EventEmitter);
sys.inherits(Logger, process.EventEmitter);
Logger.prototype.setLevel = function(level) { Logger.prototype.setLevel = function(level) {
this.level = Level.toLevel(level, log4js.levels.TRACE); this.level = Level.toLevel(level, levels.TRACE);
}; };
Logger.prototype.log = function(logLevel, message, exception) { Logger.prototype.log = function(logLevel, message, exception) {
@@ -370,12 +298,21 @@ Logger.prototype.isLevelEnabled = function(otherLevel) {
} }
); );
var consoleAppender = function (layout) { /**
* Get the default logger instance.
* @return {Logger} instance of default logger
* @static
*/
function getDefaultLogger () {
return getLogger(DEFAULT_CATEGORY);
}
function consoleAppender (layout) {
layout = layout || basicLayout; layout = layout || basicLayout;
return function(loggingEvent) { return function(loggingEvent) {
sys.puts(layout(loggingEvent)); console.log(layout(loggingEvent));
};
}; };
}
/** /**
* File Appender writing the logs to a text file. * File Appender writing the logs to a text file.
@@ -383,30 +320,25 @@ var consoleAppender = function (layout) {
* @param file file log messages will be written to * @param file file log messages will be written to
* @param layout a function that takes a logevent and returns a string (defaults to basicLayout). * @param layout a function that takes a logevent and returns a string (defaults to basicLayout).
*/ */
var fileAppender = function(file, layout) { function fileAppender (file, layout) {
layout = layout || basicLayout; layout = layout || basicLayout;
file = file || "log4js.log";
//syncs are generally bad, but we need //syncs are generally bad, but we need
//the file to be open before we start doing any writing. //the file to be open before we start doing any writing.
var logFile = fs.openSync(file, process.O_APPEND | process.O_WRONLY | process.O_CREAT, 0644); var logFile = fs.openSync(file, 'a', 0644);
//register ourselves as listeners for shutdown
//so that we can close the file.
//not entirely sure this is necessary, but still.
process.addListener("exit", function() { fs.close(logFile); });
return function(loggingEvent) { return function(loggingEvent) {
fs.write(logFile, layout(loggingEvent)+'\n', null, "utf-8"); fs.write(logFile, layout(loggingEvent)+'\n', null, "utf8");
};
}; };
}
var logLevelFilter = function(levelString, appender) { function logLevelFilter (levelString, appender) {
var level = Level.toLevel(levelString); var level = Level.toLevel(levelString);
return function(logEvent) { return function(logEvent) {
if (logEvent.level.isGreaterThanOrEqualTo(level)) { if (logEvent.level.isGreaterThanOrEqualTo(level)) {
appender(logEvent); appender(logEvent);
} }
} }
}; }
/** /**
* BasicLayout is a simple layout for storing the logs. The logs are stored * BasicLayout is a simple layout for storing the logs. The logs are stored
@@ -417,7 +349,7 @@ var logLevelFilter = function(levelString, appender) {
* *
* @author Stephan Strittmatter * @author Stephan Strittmatter
*/ */
var basicLayout = function(loggingEvent) { function basicLayout (loggingEvent) {
var timestampLevelAndCategory = '[' + loggingEvent.startTime.toFormattedString() + '] '; var timestampLevelAndCategory = '[' + loggingEvent.startTime.toFormattedString() + '] ';
timestampLevelAndCategory += '[' + loggingEvent.level.toString() + '] '; timestampLevelAndCategory += '[' + loggingEvent.level.toString() + '] ';
timestampLevelAndCategory += loggingEvent.categoryName + ' - '; timestampLevelAndCategory += loggingEvent.categoryName + ' - ';
@@ -434,21 +366,23 @@ var basicLayout = function(loggingEvent) {
} }
} }
return output; return output;
}; }
var messagePassThroughLayout = function(loggingEvent) { function messagePassThroughLayout (loggingEvent) {
return loggingEvent.message; return loggingEvent.message;
}; }
/** /**
* PatternLayout * PatternLayout
* Takes a pattern string and returns a layout function. * Takes a pattern string and returns a layout function.
* @author Stephan Strittmatter * @author Stephan Strittmatter
*/ */
var patternLayout = function(pattern) { function patternLayout (pattern) {
pattern = pattern || patternLayout.DEFAULT_CONVERSION_PATTERN; var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([cdmnpr%])(\{([^\}]+)\})?|([^%]+)/; var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([cdmnpr%])(\{([^\}]+)\})?|([^%]+)/;
pattern = pattern || patternLayout.TTCC_CONVERSION_PATTERN;
return function(loggingEvent) { return function(loggingEvent) {
var formattedString = ""; var formattedString = "";
var result; var result;
@@ -510,7 +444,7 @@ var patternLayout = function(pattern) {
replacement = loggingEvent.level.toString(); replacement = loggingEvent.level.toString();
break; break;
case "r": case "r":
replacement = "" + loggingEvent.startTime.toLocaleTimeString(); //TODO: .getDifference(Log4js.applicationStartDate); replacement = "" + loggingEvent.startTime.toLocaleTimeString();
break; break;
case "%": case "%":
replacement = "%"; replacement = "%";
@@ -554,8 +488,26 @@ var patternLayout = function(pattern) {
}; };
patternLayout.TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n"; return {
patternLayout.DEFAULT_CONVERSION_PATTERN = "%m%n"; getLogger: getLogger,
getDefaultLogger: getDefaultLogger,
addAppender: addAppender,
clearAppenders: clearAppenders,
configure: configure,
levels: levels,
consoleAppender: consoleAppender,
fileAppender: fileAppender,
logLevelFilter: logLevelFilter,
basicLayout: basicLayout,
messagePassThroughLayout: messagePassThroughLayout,
patternLayout: patternLayout
};
}
Date.ISO8601_FORMAT = "yyyy-MM-dd hh:mm:ss.SSS"; Date.ISO8601_FORMAT = "yyyy-MM-dd hh:mm:ss.SSS";
Date.ISO8601_WITH_TZ_OFFSET_FORMAT = "yyyy-MM-ddThh:mm:ssO"; Date.ISO8601_WITH_TZ_OFFSET_FORMAT = "yyyy-MM-ddThh:mm:ssO";
@@ -614,10 +566,3 @@ Date.prototype.toFormattedString = function(format) {
} }
}; };
exports.consoleAppender = log4js.consoleAppender = consoleAppender;
exports.fileAppender = log4js.fileAppender = fileAppender;
exports.logLevelFilter = log4js.logLevelFilter = logLevelFilter;
exports.basicLayout = log4js.basicLayout = basicLayout;
exports.patternLayout = log4js.patternLayout = patternLayout;
exports.messagePassThroughLayout = log4js.messagePassThroughLayout = messagePassThroughLayout;
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "log4js", "name": "log4js",
"version": "0.1.1", "version": "0.2.0",
"description": "Port of Log4js to work with node.", "description": "Port of Log4js to work with node.",
"keywords": [ "keywords": [
"logging", "logging",
-16
View File
@@ -1,16 +0,0 @@
{
"appenders": [
{
"category": "tests",
"type": "file",
"filename": "tmp-tests.log",
"layout": {
"type": "messagePassThrough"
}
}
],
"levels": {
"tests": "WARN"
}
}
-28
View File
@@ -1,28 +0,0 @@
{
"appenders": [
{
"category": "tests",
"type": "logLevelFilter",
"level": "WARN",
"appender": {
"type": "file",
"filename": "tmp-tests-warnings.log",
"layout": {
"type": "messagePassThrough"
}
}
},
{
"category": "tests",
"type": "file",
"filename": "tmp-tests.log",
"layout": {
"type": "messagePassThrough"
}
}
],
"levels": {
"tests": "DEBUG"
}
}
+1 -95
View File
@@ -1,12 +1,7 @@
describe 'log4js' describe 'log4js'
before before
extend(context, { extend(context, {
log4js : require("log4js"), log4js : require("log4js")()
fs: require("fs"),
waitForWriteAndThenReadFile : function (filename) {
process.loop();
return fs.readFileSync(filename, "utf8");
}
}); });
end end
@@ -18,34 +13,6 @@ describe 'log4js'
logger.addListener("log", function (logEvent) { event = logEvent; }); logger.addListener("log", function (logEvent) { event = logEvent; });
end end
describe 'getLogger'
it 'should take a category and return a Logger'
logger.category.should.be 'tests'
logger.level.should.be log4js.levels.TRACE
logger.should.respond_to 'debug'
logger.should.respond_to 'info'
logger.should.respond_to 'warn'
logger.should.respond_to 'error'
logger.should.respond_to 'fatal'
end
it 'should emit log events'
logger.trace("Trace event");
event.level.toString().should.be 'TRACE'
event.message.should.be 'Trace event'
event.startTime.should.not.be undefined
end
it 'should not emit events of a lower level than the minimum'
logger.setLevel("DEBUG");
event = undefined;
logger.trace("This should not generate a log message");
event.should.be undefined
end
end
describe 'addAppender' describe 'addAppender'
before_each before_each
appenderEvent = undefined; appenderEvent = undefined;
@@ -177,24 +144,6 @@ describe 'log4js'
end end
end end
describe 'fileAppender'
before
log4js.clearAppenders();
try {
fs.unlinkSync('./tmp-tests.log');
} catch(e) {
//print('Could not delete tmp-tests.log: '+e.message);
}
end
it 'should write log events to a file'
log4js.addAppender(log4js.fileAppender('./tmp-tests.log', log4js.messagePassThroughLayout), 'tests');
logger.debug('this is a test');
waitForWriteAndThenReadFile('./tmp-tests.log').should.be 'this is a test\n'
end
end
describe 'logLevelFilter' describe 'logLevelFilter'
it 'should only pass log events greater than or equal to its own level' it 'should only pass log events greater than or equal to its own level'
@@ -216,49 +165,6 @@ describe 'log4js'
end end
describe 'configure'
before_each
log4js.clearAppenders();
try {
fs.unlinkSync('./tmp-tests.log');
} catch(e) {
//print('Could not delete tmp-tests.log: '+e.message);
}
try {
fs.unlinkSync('./tmp-tests-warnings.log');
} catch (e) {
//print('Could not delete tmp-tests-warnings.log: '+e.message);
}
end
it 'should load appender configuration from a json file'
//this config file defines one file appender (to ./tmp-tests.log)
//and sets the log level for "tests" to WARN
log4js.configure('spec/fixtures/log4js.json');
event = undefined;
logger = log4js.getLogger("tests");
logger.addListener("log", function(evt) { event = evt });
logger.info('this should not fire an event');
event.should.be undefined
logger.warn('this should fire an event');
event.message.should.be 'this should fire an event'
waitForWriteAndThenReadFile('./tmp-tests.log').should.be 'this should fire an event\n'
end
it 'should handle logLevelFilter configuration'
log4js.configure('spec/fixtures/with-logLevelFilter.json');
logger.info('main');
logger.error('both');
logger.warn('both');
logger.debug('main');
waitForWriteAndThenReadFile('./tmp-tests.log').should.be 'main\nboth\nboth\nmain\n'
waitForWriteAndThenReadFile('./tmp-tests-warnings.log').should.be 'both\nboth\n'
end
end
end end