From b4a810392b6a8f1ac0ce0502b9552932fe1d3e93 Mon Sep 17 00:00:00 2001 From: csausdev Date: Tue, 10 Aug 2010 22:05:51 +1000 Subject: [PATCH 01/25] Amended readme with new require name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95115fe..981d52c 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Run the tests with `node tests.js`. They use the awesome [jspec](http://visionme See example.js: - var log4js = require('log4js-node'); + var log4js = require('log4js'); log4js.addAppender(log4js.consoleAppender()); log4js.addAppender(log4js.fileAppender('logs/cheese.log'), 'cheese'); From 5ffc00c9dea9df15793ea141a4fe8b201b25326a Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Thu, 7 Oct 2010 15:15:23 +1100 Subject: [PATCH 02/25] appenders can be added to multiple categories at the same time --- lib/log4js.js | 22 +++++++++++++++---- package.json | 2 +- spec/lib/jspec.js | 2 +- spec/spec.logging.js | 52 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 70 insertions(+), 8 deletions(-) diff --git a/lib/log4js.js b/lib/log4js.js index 6857baf..e5d73d4 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -55,7 +55,7 @@ var log4js = { * @static * @final */ - version: "0.1.0", + version: "0.1.1", /** * Date of logger initialized. @@ -119,8 +119,21 @@ exports.getDefaultLogger = log4js.getDefaultLogger = function() { return log4js.getLogger(DEFAULT_CATEGORY); }; -exports.addAppender = log4js.addAppender = function (appender, categoryName) { - var category = categoryName || ALL_CATEGORIES; +/** + * 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] = []; } @@ -135,13 +148,14 @@ exports.addAppender = log4js.addAppender = function (appender, categoryName) { } 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].listeners.length = 0; + log4js.loggers[logger].removeAllListeners("log"); } } }; diff --git a/package.json b/package.json index 9058c2f..16f1429 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.1.0", + "version": "0.1.1", "description": "Port of Log4js to work with node.", "keywords": [ "logging", diff --git a/spec/lib/jspec.js b/spec/lib/jspec.js index 1e95f93..742987c 100644 --- a/spec/lib/jspec.js +++ b/spec/lib/jspec.js @@ -1770,4 +1770,4 @@ } }) -})() \ No newline at end of file +})() diff --git a/spec/spec.logging.js b/spec/spec.logging.js index 17fba5b..fbaea45 100644 --- a/spec/spec.logging.js +++ b/spec/spec.logging.js @@ -48,7 +48,6 @@ describe 'log4js' describe 'addAppender' before_each - log4js.clearAppenders(); appenderEvent = undefined; appender = function(logEvent) { appenderEvent = logEvent; }; end @@ -72,6 +71,15 @@ describe 'log4js' appenderEvent.should.be event otherEvent.should.be event + + otherEvent = undefined; + appenderEvent = undefined; + log4js.getLogger('pants').debug("this should not be propagated to otherEvent"); + otherEvent.should.be undefined + appenderEvent.should.not.be undefined + appenderEvent.message.should.be "this should not be propagated to otherEvent" + + cheeseLogger = null; end end @@ -88,6 +96,45 @@ describe 'log4js' end end + describe 'with multiple categories' + it 'should register the function as a listener for all the categories' + log4js.addAppender(appender, 'tests', 'biscuits'); + + logger.debug('this is a test'); + appenderEvent.should.be event + appenderEvent = undefined; + + var otherLogger = log4js.getLogger('biscuits'); + otherLogger.debug("mmm... garibaldis"); + appenderEvent.should.not.be undefined + appenderEvent.message.should.be "mmm... garibaldis" + appenderEvent = undefined; + + otherLogger = null; + + log4js.getLogger("something else").debug("pants"); + appenderEvent.should.be undefined + end + + it 'should register the function when the list of categories is an array' + log4js.addAppender(appender, ['tests', 'pants']); + + logger.debug('this is a test'); + appenderEvent.should.be event + appenderEvent = undefined; + + var otherLogger = log4js.getLogger('pants'); + otherLogger.debug("big pants"); + appenderEvent.should.not.be undefined + appenderEvent.message.should.be "big pants" + appenderEvent = undefined; + + otherLogger = null; + + log4js.getLogger("something else").debug("pants"); + appenderEvent.should.be undefined + end + end end describe 'basicLayout' @@ -189,6 +236,8 @@ describe 'log4js' //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 @@ -200,7 +249,6 @@ describe 'log4js' it 'should handle logLevelFilter configuration' log4js.configure('spec/fixtures/with-logLevelFilter.json'); - event = undefined; logger.info('main'); logger.error('both'); From c5854b8b623e90848e173931a68cfe827a131228 Mon Sep 17 00:00:00 2001 From: csausdev Date: Sat, 4 Dec 2010 20:47:30 +1100 Subject: [PATCH 03/25] initial refactoring to allow dependency injection --- test/log4js.json | 16 ++++ test/logging.js | 134 ++++++++++++++++++++++++++++++++++ test/with-logLevelFilter.json | 28 +++++++ 3 files changed, 178 insertions(+) create mode 100644 test/log4js.json create mode 100644 test/logging.js create mode 100644 test/with-logLevelFilter.json diff --git a/test/log4js.json b/test/log4js.json new file mode 100644 index 0000000..3a4e54a --- /dev/null +++ b/test/log4js.json @@ -0,0 +1,16 @@ +{ + "appenders": [ + { + "category": "tests", + "type": "file", + "filename": "tmp-tests.log", + "layout": { + "type": "messagePassThrough" + } + } + ], + + "levels": { + "tests": "WARN" + } +} diff --git a/test/logging.js b/test/logging.js new file mode 100644 index 0000000..09b88a1 --- /dev/null +++ b/test/logging.js @@ -0,0 +1,134 @@ +var vows = require('vows'), +assert = require('assert'); + +vows.describe('log4js').addBatch({ + 'getLogger': { + topic: function() { + var log4js = require('../lib/log4js')(); + log4js.clearAppenders(); + var logger = log4js.getLogger('tests'); + logger.setLevel("DEBUG"); + return logger; + }, + + 'should take a category and return a logger': function(logger) { + assert.equal(logger.category, 'tests'); + assert.equal(logger.level.toString(), "DEBUG"); + assert.isFunction(logger.debug); + assert.isFunction(logger.info); + assert.isFunction(logger.warn); + assert.isFunction(logger.error); + assert.isFunction(logger.fatal); + }, + + 'log events' : { + topic: function(logger) { + var events = []; + logger.addListener("log", function (logEvent) { events.push(logEvent); }); + logger.debug("Debug event"); + logger.trace("Trace event 1"); + logger.trace("Trace event 2"); + logger.warn("Warning event"); + return events; + }, + + 'should emit log events': function(events) { + assert.equal(events[0].level.toString(), 'DEBUG'); + assert.equal(events[0].message, 'Debug event'); + assert.instanceOf(events[0].startTime, Date); + }, + + 'should not emit events of a lower level': function(events) { + assert.length(events, 2); + assert.equal(events[1].level.toString(), 'WARN'); + } + }, + + }, + + 'fileAppender': { + topic: function() { + var appender, logmessages = [], thing = "thing", fakeFS = { + openSync: function() { + assert.equal(arguments[0], './tmp-tests.log'); + assert.equal(arguments[1], 'a'); + assert.equal(arguments[2], 0644); + return thing; + }, + write: function() { + assert.equal(arguments[0], thing); + assert.isString(arguments[1]); + assert.isNull(arguments[2]); + assert.equal(arguments[3], "utf8"); + logmessages.push(arguments[1]); + } + }, + log4js = require('../lib/log4js')(fakeFS); + log4js.clearAppenders(); + + appender = log4js.fileAppender('./tmp-tests.log', log4js.messagePassThroughLayout); + log4js.addAppender(appender, 'file-test'); + + var logger = log4js.getLogger('file-test'); + logger.debug("this is a test"); + + return logmessages; + }, + 'should write log messages to file': function(logmessages) { + assert.length(logmessages, 1); + assert.equal(logmessages, "this is a test\n"); + } + }, + + 'configure' : { + topic: function() { + var messages = {}, fakeFS = { + openSync: function(file) { + return file; + }, + write: function(file, message) { + if (!messages.hasOwnProperty(file)) { + messages[file] = []; + } + messages[file].push(message); + }, + readFileSync: function(file, encoding) { + return require('fs').readFileSync(file, encoding); + } + }, + log4js = require('../lib/log4js')(fakeFS); + return [ log4js, messages ]; + }, + 'should load appender configuration from a json file': function(args) { + var log4js = args[0], messages = args[1]; + delete messages['tmp-tests.log']; + log4js.clearAppenders(); + //this config file defines one file appender (to ./tmp-tests.log) + //and sets the log level for "tests" to WARN + log4js.configure('test/log4js.json'); + var logger = log4js.getLogger("tests"); + logger.info('this should not be written to the file'); + logger.warn('this should be written to the file'); + assert.length(messages['tmp-tests.log'], 1); + assert.equal(messages['tmp-tests.log'][0], 'this should be written to the file\n'); + }, + 'should handle logLevelFilter configuration': function(args) { + var log4js = args[0], messages = args[1]; + delete messages['tmp-tests.log']; + delete messages['tmp-tests-warnings.log']; + log4js.clearAppenders(); + log4js.configure('test/with-logLevelFilter.json'); + var logger = log4js.getLogger("tests"); + logger.info('main'); + logger.error('both'); + logger.warn('both'); + logger.debug('main'); + + assert.length(messages['tmp-tests.log'], 4); + assert.length(messages['tmp-tests-warnings.log'], 2); + assert.deepEqual(messages['tmp-tests.log'], ['main\n','both\n','both\n','main\n']); + assert.deepEqual(messages['tmp-tests-warnings.log'], ['both\n','both\n']); + } + } + +}).export(module); diff --git a/test/with-logLevelFilter.json b/test/with-logLevelFilter.json new file mode 100644 index 0000000..c1ac2cd --- /dev/null +++ b/test/with-logLevelFilter.json @@ -0,0 +1,28 @@ +{ + "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" + } +} From c52af1b997692fdcc615380c23fe156816b840bb Mon Sep 17 00:00:00 2001 From: csausdev Date: Sat, 4 Dec 2010 20:49:39 +1100 Subject: [PATCH 04/25] refactoring to allow dependency injection --- README.md | 9 +- lib/log4js.js | 1003 +++++++++++------------- package.json | 2 +- spec/fixtures/log4js.json | 16 - spec/fixtures/with-logLevelFilter.json | 28 - spec/spec.logging.js | 96 +-- 6 files changed, 481 insertions(+), 673 deletions(-) delete mode 100644 spec/fixtures/log4js.json delete mode 100644 spec/fixtures/with-logLevelFilter.json diff --git a/README.md b/README.md index 981d52c..d2afb3b 100644 --- a/README.md +++ b/README.md @@ -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 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 npm install log4js - ## 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 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.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 configuration file (`log4js.configure('path/to/file.json')`). An example file can be found -in spec/fixtures/log4js.json +in test/log4js.json ## todo diff --git a/lib/log4js.js b/lib/log4js.js index e5d73d4..38a3630 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -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"); * you may not use this file except in compliance with the License. @@ -26,7 +22,7 @@ var ALL_CATEGORIES = '[all]'; * *

Example:

*
- *  var logging = require('log4js-node');
+ *  var logging = require('log4js-node')();
  *  //add an appender that logs all messages to stdout.
  *  logging.addAppender(logging.consoleAppender());
  *  //add an appender that logs "some-category" to a file
@@ -48,514 +44,470 @@ var ALL_CATEGORIES = '[all]';
  * @static
  * Website: http://log4js.berlios.de
  */
-var log4js = {
+module.exports = function (fileSystem) {
+    var fs = fileSystem || require('fs'), 
+    sys = require('sys'),
+    events = require('events'),
+    DEFAULT_CATEGORY = '[default]',
+    ALL_CATEGORIES = '[all]',
+    loggers = {},
+    appenders = {},
+    levels = {
+	ALL: new Level(Number.MIN_VALUE, "ALL"),
+	TRACE: new Level(5000, "TRACE"),
+	DEBUG: new Level(10000, "DEBUG"),
+	INFO: new Level(20000, "INFO"),
+	WARN: new Level(30000, "WARN"),
+	ERROR: new Level(40000, "ERROR"),
+	FATAL: new Level(50000, "FATAL"),
+	OFF: new Level(Number.MAX_VALUE, "OFF")  
+    },
+    appenderMakers = {
+	"file": function(config) {
+	    var layout;
+	    if (config.layout) {
+		layout = layoutMakers[config.layout.type](config.layout);
+	    }
+	    return fileAppender(config.filename, layout);
+	},
+	"console": function(config) {
+	    var layout;
+	    if (config.layout) {
+		layout = layoutMakers[config.layout.type](config.layout);
+	    }
+	    return consoleAppender(layout);
+	},
+	"logLevelFilter": function(config) {
+	    var appender = appenderMakers[config.appender.type](config.appender);
+	    return logLevelFilter(config.level, appender);
+	}
+    },
+    layoutMakers = {
+	"messagePassThrough": function() { return messagePassThroughLayout; },
+	"basic": function() { return basicLayout; },
+	"pattern": function (config) {
+	    var pattern = config.pattern || undefined;
+	    return patternLayout(pattern);
+	}
+    };
+
+
+
+    /**
+     * 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];
+	}
 	
-	/** 
-	 * Current version of log4js-node. 
-	 * @static
-	 * @final
-	 */
-  	version: "0.1.1",
-
-	/**  
-	 * 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;
+	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);
+	    }
+	});
+    }
 
-    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);
-        });
-      }
+    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) {
+	clearAppenders();
+	if (appenderList) {
+	    appenderList.forEach(function(appenderConfig) {
+		var appender = appenderMakers[appenderConfig.type](appenderConfig);
+		if (appender) {
+		    addAppender(appender, appenderConfig.category);    
+		} else {
+		    throw new Error("log4js configuration problem for "+sys.inspect(appenderConfig));
 		}
-		
-		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] = [];
+	    });
+	} else {
+	    addAppender(consoleAppender);
+	}
     }
-    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"),
-		TRACE: new Level(5000, "TRACE"),
-		DEBUG: new Level(10000, "DEBUG"),
-    INFO: new Level(20000, "INFO"),
-		WARN: new Level(30000, "WARN"),
-		ERROR: new Level(40000, "ERROR"),
-		FATAL: new Level(50000, "FATAL"),
-		OFF: new Level(Number.MAX_VALUE, "OFF")  
-};
 
-var appenderMakers = {
-  "file": function(config) {
-    var layout;
-    if (config.layout) {
-      layout = layoutMakers[config.layout.type](config.layout);
-    }
-    return fileAppender(config.filename, layout);
-  },
-  "console": function(config) {
-    var layout;
-    if (config.layout) {
-      layout = layoutMakers[config.layout.type](config.layout);
-    }
-    return consoleAppender(layout);
-  },
-  "logLevelFilter": function(config) {
-    var appender = appenderMakers[config.appender.type](config.appender);
-    return logLevelFilter(config.level, appender);
-  }
-};
+    function configureLevels(levels) {
+	if (levels) {
+	    for (var category in levels) {
+		if (levels.hasOwnProperty(category)) {
+		    getLogger(category).setLevel(levels[category]);
+		}
+	    }
+	}
+    } 
 
-var layoutMakers = {
-  "messagePassThrough": function() { return messagePassThroughLayout; },
-  "basic": function() { return basicLayout; },
-  "pattern": function (config) {
-    var pattern = config.pattern || undefined;
-    return patternLayout(pattern);
-  }
-};
-
-function configureAppenders(appenderList) {
-  log4js.clearAppenders();
-  if (appenderList) {
-    appenderList.forEach(function(appenderConfig) {
-      var appender = appenderMakers[appenderConfig.type](appenderConfig);
-      if (appender) {
-        log4js.addAppender(appender, appenderConfig.category);    
-      } else {
-        throw new Error("log4js configuration problem for "+sys.inspect(appenderConfig));
-      }
-    });
-  } else {
-    log4js.addAppender(consoleAppender);
-  }
-}
-
-function configureLevels(levels) {
-  if (levels) {
-    for (var category in levels) {
-      if (levels.hasOwnProperty(category)) {
-        log4js.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.levelStr = levelStr;
-};
-
-/** 
- * converts given String to corresponding Level
- * @param {String} sArg String value of Level
- * @param {Log4js.Level} defaultLevel default Level, if no String representation
- * @return Level object
- * @type Log4js.Level
- */
-Level.toLevel = function(sArg, defaultLevel) {                  
-      
-  if (sArg === null) {
-    return defaultLevel;
-  }
-  
-  if (typeof sArg == "string") { 
-    var s = sArg.toUpperCase();
-    if (log4js.levels[s]) {
-      return log4js.levels[s];
     }
-  }
-  return defaultLevel;
-};
 
-Level.prototype.toString = function() {
-  return this.levelStr;	
-};
-  
-Level.prototype.isLessThanOrEqualTo = function(otherLevel) {
-  return this.level <= otherLevel.level;
-};
-
-Level.prototype.isGreaterThanOrEqualTo = function(otherLevel) {
-    return this.level >= otherLevel.level;
-};
-
-/**
- * Models a logging event.
- * @constructor
- * @param {String} categoryName name of category
- * @param {Log4js.Level} level level of message
- * @param {String} message message to log
- * @param {Log4js.Logger} logger the associated logger
- * @author Seth Chisamore
- */
-LoggingEvent = function(categoryName, level, message, exception, logger) {
-	/**
-	 * the timestamp of the Logging Event
-	 * @type Date
-	 * @private
-	 */
-	this.startTime = new Date();
-	/**
-	 * category of event
-	 * @type String
-	 * @private
-	 */
-	this.categoryName = categoryName;
-	/**
-	 * the logging message
-	 * @type String
-	 * @private
-	 */
-	this.message = message;
-	/**
-	 * the logging exception
-	 * @type Exception
-	 * @private
-	 */
-	this.exception = exception;
-	/**
-	 * level of log
-	 * @type Log4js.Level
-	 * @private
-	 */
-	this.level = level;
-	/**
-	 * reference to logger
-	 * @type Log4js.Logger
-	 * @private
-	 */
-	this.logger = logger;
-};
-
-/**
- * Logger to log messages.
- * use {@see Log4js#getLogger(String)} to get an instance.
- * @constructor
- * @param name name of category to log to
- * @author Stephan Strittmatter
- */
-Logger = function(name, level) {
-	this.category = name || DEFAULT_CATEGORY;
-	this.level = Level.toLevel(level, log4js.levels.TRACE);
-};
-
-sys.inherits(Logger, process.EventEmitter);
-
-Logger.prototype.setLevel = function(level) {
-  this.level = Level.toLevel(level, log4js.levels.TRACE);
-};
+    /** 
+     * converts given String to corresponding Level
+     * @param {String} sArg String value of Level
+     * @param {Log4js.Level} defaultLevel default Level, if no String representation
+     * @return Level object
+     * @type Log4js.Level
+     */
+    Level.toLevel = function(sArg, defaultLevel) {                  
 	
-Logger.prototype.log = function(logLevel, message, exception) {
-  var loggingEvent = new LoggingEvent(this.category, logLevel, message, exception, this);
-  this.emit("log", loggingEvent);
-};
- 
-Logger.prototype.isLevelEnabled = function(otherLevel) {
-  return this.level.isLessThanOrEqualTo(otherLevel);
-};
+	if (sArg === null) {
+	    return defaultLevel;
+	}
+	
+	if (typeof sArg == "string") { 
+	    var s = sArg.toUpperCase();
+	    if (levels[s]) {
+		return levels[s];
+	    }
+	}
+	return defaultLevel;
+    };
 
-['Trace','Debug','Info','Warn','Error','Fatal'].forEach(
-  function(levelString) {
-    var level = Level.toLevel(levelString);
-    Logger.prototype['is'+levelString+'Enabled'] = function() {
-      return this.isLevelEnabled(level);
+    Level.prototype.toString = function() {
+	return this.levelStr;	
     };
     
-    Logger.prototype[levelString.toLowerCase()] = function (message, exception) {
-      if (this.isLevelEnabled(level)) {
-        this.log(level, message, exception);
-      }
+    Level.prototype.isLessThanOrEqualTo = function(otherLevel) {
+	return this.level <= otherLevel.level;
     };
-  }
-);
-	
-var consoleAppender = function (layout) {
-  layout = layout || basicLayout;
-  return function(loggingEvent) {
-    sys.puts(layout(loggingEvent));
-  };  
-};
 
-/**
- * File Appender writing the logs to a text file.
- * 
- * @param file file log messages will be written to
- * @param layout a function that takes a logevent and returns a string (defaults to basicLayout).
- */
-var fileAppender = function(file, layout) {
-	layout = layout || basicLayout;	
-	file = file || "log4js.log";	
-  //syncs are generally bad, but we need 
-  //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);    
-  //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) {
-    fs.write(logFile, layout(loggingEvent)+'\n', null, "utf-8");
-  };
-};
+    Level.prototype.isGreaterThanOrEqualTo = function(otherLevel) {
+	return this.level >= otherLevel.level;
+    };
 
-var logLevelFilter = function(levelString, appender) {
-  var level = Level.toLevel(levelString);
-  return function(logEvent) {
-    if (logEvent.level.isGreaterThanOrEqualTo(level)) {
-      appender(logEvent);
+    /**
+     * Models a logging event.
+     * @constructor
+     * @param {String} categoryName name of category
+     * @param {Log4js.Level} level level of message
+     * @param {String} message message to log
+     * @param {Log4js.Logger} logger the associated logger
+     * @author Seth Chisamore
+     */
+    function LoggingEvent (categoryName, level, message, exception, logger) {
+	this.startTime = new Date();
+	this.categoryName = categoryName;
+	this.message = message;
+	this.exception = exception;
+	this.level = level;
+	this.logger = logger;
     }
-  }
-};
 
-/**
- * BasicLayout is a simple layout for storing the logs. The logs are stored
- * in following format:
- * 
- * [startTime] [logLevel] categoryName - message\n
- * 
- * - * @author Stephan Strittmatter - */ -var basicLayout = function(loggingEvent) { - var timestampLevelAndCategory = '[' + loggingEvent.startTime.toFormattedString() + '] '; - timestampLevelAndCategory += '[' + loggingEvent.level.toString() + '] '; - timestampLevelAndCategory += loggingEvent.categoryName + ' - '; - - var output = timestampLevelAndCategory + loggingEvent.message; - - if (loggingEvent.exception) { - output += '\n' - output += timestampLevelAndCategory; - if (loggingEvent.exception.stack) { - output += loggingEvent.exception.stack; - } else { - output += loggingEvent.exception.name + ': '+loggingEvent.exception.message; + /** + * Logger to log messages. + * use {@see Log4js#getLogger(String)} to get an instance. + * @constructor + * @param name name of category to log to + * @author Stephan Strittmatter + */ + function Logger (name, level) { + this.category = name || DEFAULT_CATEGORY; + this.level = Level.toLevel(level, levels.TRACE); } - } - return output; -}; + sys.inherits(Logger, events.EventEmitter); -var messagePassThroughLayout = function(loggingEvent) { - return loggingEvent.message; -}; + Logger.prototype.setLevel = function(level) { + this.level = Level.toLevel(level, levels.TRACE); + }; + + Logger.prototype.log = function(logLevel, message, exception) { + var loggingEvent = new LoggingEvent(this.category, logLevel, message, exception, this); + this.emit("log", loggingEvent); + }; + + Logger.prototype.isLevelEnabled = function(otherLevel) { + return this.level.isLessThanOrEqualTo(otherLevel); + }; -/** - * PatternLayout - * Takes a pattern string and returns a layout function. - * @author Stephan Strittmatter - */ -var patternLayout = function(pattern) { - pattern = pattern || patternLayout.DEFAULT_CONVERSION_PATTERN; - var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([cdmnpr%])(\{([^\}]+)\})?|([^%]+)/; - - return function(loggingEvent) { - var formattedString = ""; - var result; - var searchString = this.pattern; - - while ((result = regex.exec(searchString))) { - var matchedString = result[0]; - var padding = result[1]; - var truncation = result[2]; - var conversionCharacter = result[3]; - var specifier = result[5]; - var text = result[6]; - - // Check if the pattern matched was just normal text - if (text) { - formattedString += "" + text; - } else { - // Create a raw replacement string based on the conversion - // character and specifier - var replacement = ""; - switch(conversionCharacter) { - case "c": - var loggerName = loggingEvent.categoryName; - if (specifier) { - var precision = parseInt(specifier, 10); - var loggerNameBits = loggingEvent.categoryName.split("."); - if (precision >= loggerNameBits.length) { - replacement = loggerName; - } else { - replacement = loggerNameBits.slice(loggerNameBits.length - precision).join("."); - } - } else { - replacement = loggerName; - } - break; - case "d": - var dateFormat = Date.ISO8601_FORMAT; - if (specifier) { - dateFormat = specifier; - // Pick up special cases - if (dateFormat == "ISO8601") { - dateFormat = Date.ISO8601_FORMAT; - } else if (dateFormat == "ABSOLUTE") { - dateFormat = Date.ABSOLUTETIME_FORMAT; - } else if (dateFormat == "DATE") { - dateFormat = Date.DATETIME_FORMAT; - } - } - // Format the date - replacement = loggingEvent.startTime.toFormattedString(dateFormat); - break; - case "m": - replacement = loggingEvent.message; - break; - case "n": - replacement = "\n"; - break; - case "p": - replacement = loggingEvent.level.toString(); - break; - case "r": - replacement = "" + loggingEvent.startTime.toLocaleTimeString(); //TODO: .getDifference(Log4js.applicationStartDate); - break; - case "%": - replacement = "%"; - break; - default: - replacement = matchedString; - break; - } - // Format the replacement according to any padding or - // truncation specified - - var len; - - // First, truncation - if (truncation) { - len = parseInt(truncation.substr(1), 10); - replacement = replacement.substring(0, len); - } - // Next, padding - if (padding) { - if (padding.charAt(0) == "-") { - len = parseInt(padding.substr(1), 10); - // Right pad with spaces - while (replacement.length < len) { - replacement += " "; - } - } else { - len = parseInt(padding, 10); - // Left pad with spaces - while (replacement.length < len) { - replacement = " " + replacement; - } - } - } - formattedString += replacement; - } - searchString = searchString.substr(result.index + result[0].length); + ['Trace','Debug','Info','Warn','Error','Fatal'].forEach( + function(levelString) { + var level = Level.toLevel(levelString); + Logger.prototype['is'+levelString+'Enabled'] = function() { + return this.isLevelEnabled(level); + }; + + Logger.prototype[levelString.toLowerCase()] = function (message, exception) { + if (this.isLevelEnabled(level)) { + this.log(level, message, exception); } - return formattedString; + }; + } + ); + + /** + * Get the default logger instance. + * @return {Logger} instance of default logger + * @static + */ + function getDefaultLogger () { + return getLogger(DEFAULT_CATEGORY); + } + + function consoleAppender (layout) { + layout = layout || basicLayout; + return function(loggingEvent) { + console.log(layout(loggingEvent)); + }; + } + + /** + * File Appender writing the logs to a text file. + * + * @param file file log messages will be written to + * @param layout a function that takes a logevent and returns a string (defaults to basicLayout). + */ + function fileAppender (file, layout) { + layout = layout || basicLayout; + //syncs are generally bad, but we need + //the file to be open before we start doing any writing. + var logFile = fs.openSync(file, 'a', 0644); + + return function(loggingEvent) { + fs.write(logFile, layout(loggingEvent)+'\n', null, "utf8"); + }; + } + + function logLevelFilter (levelString, appender) { + var level = Level.toLevel(levelString); + return function(logEvent) { + if (logEvent.level.isGreaterThanOrEqualTo(level)) { + appender(logEvent); + } + } + } + + /** + * BasicLayout is a simple layout for storing the logs. The logs are stored + * in following format: + *
+     * [startTime] [logLevel] categoryName - message\n
+     * 
+ * + * @author Stephan Strittmatter + */ + function basicLayout (loggingEvent) { + var timestampLevelAndCategory = '[' + loggingEvent.startTime.toFormattedString() + '] '; + timestampLevelAndCategory += '[' + loggingEvent.level.toString() + '] '; + timestampLevelAndCategory += loggingEvent.categoryName + ' - '; + + var output = timestampLevelAndCategory + loggingEvent.message; + + if (loggingEvent.exception) { + output += '\n' + output += timestampLevelAndCategory; + if (loggingEvent.exception.stack) { + output += loggingEvent.exception.stack; + } else { + output += loggingEvent.exception.name + ': '+loggingEvent.exception.message; + } + } + return output; + } + + function messagePassThroughLayout (loggingEvent) { + return loggingEvent.message; + } + + /** + * PatternLayout + * Takes a pattern string and returns a layout function. + * @author Stephan Strittmatter + */ + function patternLayout (pattern) { + var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n"; + var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([cdmnpr%])(\{([^\}]+)\})?|([^%]+)/; + + pattern = pattern || patternLayout.TTCC_CONVERSION_PATTERN; + + return function(loggingEvent) { + var formattedString = ""; + var result; + var searchString = this.pattern; + + while ((result = regex.exec(searchString))) { + var matchedString = result[0]; + var padding = result[1]; + var truncation = result[2]; + var conversionCharacter = result[3]; + var specifier = result[5]; + var text = result[6]; + + // Check if the pattern matched was just normal text + if (text) { + formattedString += "" + text; + } else { + // Create a raw replacement string based on the conversion + // character and specifier + var replacement = ""; + switch(conversionCharacter) { + case "c": + var loggerName = loggingEvent.categoryName; + if (specifier) { + var precision = parseInt(specifier, 10); + var loggerNameBits = loggingEvent.categoryName.split("."); + if (precision >= loggerNameBits.length) { + replacement = loggerName; + } else { + replacement = loggerNameBits.slice(loggerNameBits.length - precision).join("."); + } + } else { + replacement = loggerName; + } + break; + case "d": + var dateFormat = Date.ISO8601_FORMAT; + if (specifier) { + dateFormat = specifier; + // Pick up special cases + if (dateFormat == "ISO8601") { + dateFormat = Date.ISO8601_FORMAT; + } else if (dateFormat == "ABSOLUTE") { + dateFormat = Date.ABSOLUTETIME_FORMAT; + } else if (dateFormat == "DATE") { + dateFormat = Date.DATETIME_FORMAT; + } + } + // Format the date + replacement = loggingEvent.startTime.toFormattedString(dateFormat); + break; + case "m": + replacement = loggingEvent.message; + break; + case "n": + replacement = "\n"; + break; + case "p": + replacement = loggingEvent.level.toString(); + break; + case "r": + replacement = "" + loggingEvent.startTime.toLocaleTimeString(); + break; + case "%": + replacement = "%"; + break; + default: + replacement = matchedString; + break; + } + // Format the replacement according to any padding or + // truncation specified + + var len; + + // First, truncation + if (truncation) { + len = parseInt(truncation.substr(1), 10); + replacement = replacement.substring(0, len); + } + // Next, padding + if (padding) { + if (padding.charAt(0) == "-") { + len = parseInt(padding.substr(1), 10); + // Right pad with spaces + while (replacement.length < len) { + replacement += " "; + } + } else { + len = parseInt(padding, 10); + // Left pad with spaces + while (replacement.length < len) { + replacement = " " + replacement; + } + } + } + formattedString += replacement; + } + searchString = searchString.substr(result.index + result[0].length); + } + return formattedString; }; -}; + }; + + return { + getLogger: getLogger, + getDefaultLogger: getDefaultLogger, + + addAppender: addAppender, + clearAppenders: clearAppenders, + configure: configure, + + levels: levels, + + consoleAppender: consoleAppender, + fileAppender: fileAppender, + logLevelFilter: logLevelFilter, + + basicLayout: basicLayout, + messagePassThroughLayout: messagePassThroughLayout, + patternLayout: patternLayout + }; +} -patternLayout.TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n"; -patternLayout.DEFAULT_CONVERSION_PATTERN = "%m%n"; Date.ISO8601_FORMAT = "yyyy-MM-dd hh:mm:ss.SSS"; Date.ISO8601_WITH_TZ_OFFSET_FORMAT = "yyyy-MM-ddThh:mm:ssO"; @@ -563,19 +515,19 @@ Date.DATETIME_FORMAT = "dd MMM YYYY hh:mm:ss.SSS"; Date.ABSOLUTETIME_FORMAT = "hh:mm:ss.SSS"; Date.prototype.toFormattedString = function(format) { - format = format || Date.ISO8601_FORMAT; + format = format || Date.ISO8601_FORMAT; - var vDay = addZero(this.getDate()); - var vMonth = addZero(this.getMonth()+1); - var vYearLong = addZero(this.getFullYear()); - var vYearShort = addZero(this.getFullYear().toString().substring(3,4)); - var vYear = (format.indexOf("yyyy") > -1 ? vYearLong : vYearShort); - var vHour = addZero(this.getHours()); - var vMinute = addZero(this.getMinutes()); - var vSecond = addZero(this.getSeconds()); - var vMillisecond = padWithZeros(this.getMilliseconds(), 3); - var vTimeZone = offset(this); - var formatted = format + var vDay = addZero(this.getDate()); + var vMonth = addZero(this.getMonth()+1); + var vYearLong = addZero(this.getFullYear()); + var vYearShort = addZero(this.getFullYear().toString().substring(3,4)); + var vYear = (format.indexOf("yyyy") > -1 ? vYearLong : vYearShort); + var vHour = addZero(this.getHours()); + var vMinute = addZero(this.getMinutes()); + var vSecond = addZero(this.getSeconds()); + var vMillisecond = padWithZeros(this.getMilliseconds(), 3); + var vTimeZone = offset(this); + var formatted = format .replace(/dd/g, vDay) .replace(/MM/g, vMonth) .replace(/y{1,4}/g, vYear) @@ -584,40 +536,33 @@ Date.prototype.toFormattedString = function(format) { .replace(/ss/g, vSecond) .replace(/SSS/g, vMillisecond) .replace(/O/g, vTimeZone); - return formatted; + return formatted; - function padWithZeros(vNumber, width) { - var numAsString = vNumber + ""; - while (numAsString.length < width) { - numAsString = "0" + numAsString; + function padWithZeros(vNumber, width) { + var numAsString = vNumber + ""; + while (numAsString.length < width) { + numAsString = "0" + numAsString; + } + return numAsString; } - return numAsString; - } - function addZero(vNumber) { - return padWithZeros(vNumber, 2); - } + function addZero(vNumber) { + return padWithZeros(vNumber, 2); + } - /** - * Formats the TimeOffest - * Thanks to http://www.svendtofte.com/code/date_format/ - * @private - */ - function offset(date) { - // Difference to Greenwich time (GMT) in hours - var os = Math.abs(date.getTimezoneOffset()); - var h = String(Math.floor(os/60)); - var m = String(os%60); - h.length == 1? h = "0"+h:1; - m.length == 1? m = "0"+m:1; - return date.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m; - } + /** + * Formats the TimeOffest + * Thanks to http://www.svendtofte.com/code/date_format/ + * @private + */ + function offset(date) { + // Difference to Greenwich time (GMT) in hours + var os = Math.abs(date.getTimezoneOffset()); + var h = String(Math.floor(os/60)); + var m = String(os%60); + h.length == 1? h = "0"+h:1; + m.length == 1? m = "0"+m:1; + return date.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m; + } }; -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; - diff --git a/package.json b/package.json index 16f1429..73266e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.1.1", + "version": "0.2.0", "description": "Port of Log4js to work with node.", "keywords": [ "logging", diff --git a/spec/fixtures/log4js.json b/spec/fixtures/log4js.json deleted file mode 100644 index 3a4e54a..0000000 --- a/spec/fixtures/log4js.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "appenders": [ - { - "category": "tests", - "type": "file", - "filename": "tmp-tests.log", - "layout": { - "type": "messagePassThrough" - } - } - ], - - "levels": { - "tests": "WARN" - } -} diff --git a/spec/fixtures/with-logLevelFilter.json b/spec/fixtures/with-logLevelFilter.json deleted file mode 100644 index c1ac2cd..0000000 --- a/spec/fixtures/with-logLevelFilter.json +++ /dev/null @@ -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" - } -} diff --git a/spec/spec.logging.js b/spec/spec.logging.js index fbaea45..5db5932 100644 --- a/spec/spec.logging.js +++ b/spec/spec.logging.js @@ -1,12 +1,7 @@ describe 'log4js' before extend(context, { - log4js : require("log4js"), - fs: require("fs"), - waitForWriteAndThenReadFile : function (filename) { - process.loop(); - return fs.readFileSync(filename, "utf8"); - } + log4js : require("log4js")() }); end @@ -17,35 +12,7 @@ describe 'log4js' logger.setLevel("TRACE"); logger.addListener("log", function (logEvent) { event = logEvent; }); 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' before_each appenderEvent = undefined; @@ -177,24 +144,6 @@ describe 'log4js' 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' it 'should only pass log events greater than or equal to its own level' @@ -216,49 +165,6 @@ describe 'log4js' 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 From ee776feb023b76e403f69b04f2ea4e0f5086e4d9 Mon Sep 17 00:00:00 2001 From: csausdev Date: Sat, 4 Dec 2010 20:51:29 +1100 Subject: [PATCH 05/25] fixed example to work with refactoring --- example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example.js b/example.js index b4eab22..66d9ce4 100644 --- a/example.js +++ b/example.js @@ -1,4 +1,4 @@ -var log4js = require('./lib/log4js'); +var log4js = require('./lib/log4js')(); log4js.addAppender(log4js.consoleAppender()); log4js.addAppender(log4js.fileAppender('cheese.log'), 'cheese'); From 40d3f5aaaa5c24308d730a493dde4670a90af79b Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sun, 5 Dec 2010 10:56:09 +1100 Subject: [PATCH 06/25] Added loading of config from require paths, and now defaults to console appender with basic layout --- README.md | 11 +++++++-- lib/log4js.js | 47 ++++++++++++++++++++++++++++++------- lib/log4js.json | 10 ++++++++ test/logging.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 lib/log4js.json diff --git a/README.md b/README.md index d2afb3b..ee1929f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,13 @@ Tests now use [vows](http://vowsjs.org), run with `vows test/logging.js`. I am s ## usage +Minimalist version: + var log4js = require('log4js')(); + var logger = log4js.getLogger(); + logger.debug("Some debug messages"); +By default, log4js outputs to stdout with the basic layout, so for the above you would see: + [2010-01-17 11:43:37.987] [DEBUG] [default] - Some debug messages + See example.js: var log4js = require('log4js')(); //note the need to call the function @@ -40,8 +47,8 @@ Output ## configuration 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 -in test/log4js.json +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 ## todo diff --git a/lib/log4js.js b/lib/log4js.js index 38a3630..c5424db 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -44,10 +44,13 @@ * @static * Website: http://log4js.berlios.de */ -module.exports = function (fileSystem) { - var fs = fileSystem || require('fs'), +module.exports = function (fileSystem, standardOutput, configPaths) { + var fs = fileSystem || require('fs'), + standardOutput = standardOutput || console.log, + configPaths = configPaths || require.paths, sys = require('sys'), events = require('events'), + path = require('path'), DEFAULT_CATEGORY = '[default]', ALL_CATEGORIES = '[all]', loggers = {}, @@ -91,8 +94,6 @@ module.exports = function (fileSystem) { } }; - - /** * Get a logger instance. Instance is cached on categoryName level. * @param {String} categoryName name of category to log to. @@ -133,7 +134,7 @@ module.exports = function (fileSystem) { function addAppender () { var args = Array.prototype.slice.call(arguments); var appender = args.shift(); - if (args.length == 0) { + if (args.length == 0 || args[0] === undefined) { args = [ ALL_CATEGORIES ]; } //argument may already be an array @@ -169,9 +170,34 @@ module.exports = function (fileSystem) { } function configure (configurationFile) { - var config = JSON.parse(fs.readFileSync(configurationFile)); - configureAppenders(config.appenders); - configureLevels(config.levels); + if (configurationFile) { + try { + var config = JSON.parse(fs.readFileSync(configurationFile, "utf8")); + configureAppenders(config.appenders); + configureLevels(config.levels); + } catch (e) { + throw new Error("Problem reading log4js config file " + configurationFile + ". Error was " + e.message); + } + } + } + + function findConfiguration() { + //add current directory onto the list of configPaths + var paths = ['.'].concat(configPaths); + //add this module's directory to the end of the list, so that we pick up the default config + paths.push(__dirname); + var pathsWithConfig = paths.filter( function (pathToCheck) { + try { + fs.statSync(path.join(pathToCheck, "log4js.json")); + return true; + } catch (e) { + return false; + } + }); + if (pathsWithConfig.length > 0) { + return path.join(pathsWithConfig[0], 'log4js.json'); + } + return undefined; } function configureAppenders(appenderList) { @@ -310,7 +336,7 @@ module.exports = function (fileSystem) { function consoleAppender (layout) { layout = layout || basicLayout; return function(loggingEvent) { - console.log(layout(loggingEvent)); + standardOutput(layout(loggingEvent)); }; } @@ -488,6 +514,9 @@ module.exports = function (fileSystem) { }; + //set ourselves up if we can find a default log4js.json + configure(findConfiguration()); + return { getLogger: getLogger, getDefaultLogger: getDefaultLogger, diff --git a/lib/log4js.json b/lib/log4js.json new file mode 100644 index 0000000..fdd836f --- /dev/null +++ b/lib/log4js.json @@ -0,0 +1,10 @@ +{ + "appenders": [ + { + "type": "console", + "layout": { + "type": "basic" + } + } + ] +} \ No newline at end of file diff --git a/test/logging.js b/test/logging.js index 09b88a1..fc9e471 100644 --- a/test/logging.js +++ b/test/logging.js @@ -129,6 +129,66 @@ vows.describe('log4js').addBatch({ assert.deepEqual(messages['tmp-tests.log'], ['main\n','both\n','both\n','main\n']); assert.deepEqual(messages['tmp-tests-warnings.log'], ['both\n','both\n']); } + }, + + 'with no appenders defined' : { + topic: function() { + var logger, message, log4js = require('../lib/log4js')(null, function (msg) { message = msg; } ); + logger = log4js.getLogger("some-logger"); + logger.debug("This is a test"); + return message; + }, + 'should default to the console appender': function(message) { + assert.isTrue(/This is a test$/.test(message)); + } + }, + + 'default setup': { + topic: function() { + var pathsChecked = [], + message, + logger, + fakeFS = { + readFileSync: function (file, encoding) { + assert.equal(file, '/path/to/config/log4js.json'); + assert.equal(encoding, 'utf8'); + return '{ "appenders" : [ { "type": "console", "layout": { "type": "messagePassThrough" }} ] }'; + }, + statSync: function (path) { + pathsChecked.push(path); + if (path === '/path/to/config/log4js.json') { + return true; + } else { + throw new Error("no such file"); + } + } + }, + fakeConsoleLog = function (msg) { message = msg; }, + fakeRequirePath = [ '/a/b/c', '/some/other/path', '/path/to/config', '/some/later/directory' ], + log4js = require('../lib/log4js')(fakeFS, fakeConsoleLog, fakeRequirePath), + logger = log4js.getLogger('a-test'); + logger.debug("this is a test"); + + return [ pathsChecked, message ]; + }, + + 'should check current directory, require paths, and finally the module dir for log4js.json': function(args) { + var pathsChecked = args[0]; + assert.deepEqual(pathsChecked, [ + 'log4js.json', + '/a/b/c/log4js.json', + '/some/other/path/log4js.json', + '/path/to/config/log4js.json', + '/some/later/directory/log4js.json', + require('path').normalize(__dirname + '/../lib/log4js.json') + ]); + }, + + 'should configure log4js from first log4js.json found': function(args) { + var message = args[1]; + assert.equal(message, 'this is a test'); + } } - + + }).export(module); From 6032c073b392c0bc1a036a319e85aff5cd31b942 Mon Sep 17 00:00:00 2001 From: csausdev Date: Sun, 5 Dec 2010 10:56:09 +1100 Subject: [PATCH 07/25] Added loading of config from require paths, and now defaults to console appender with basic layout --- README.md | 11 +++++++-- lib/log4js.js | 47 ++++++++++++++++++++++++++++++------- lib/log4js.json | 10 ++++++++ test/logging.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 lib/log4js.json diff --git a/README.md b/README.md index d2afb3b..ee1929f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,13 @@ Tests now use [vows](http://vowsjs.org), run with `vows test/logging.js`. I am s ## usage +Minimalist version: + var log4js = require('log4js')(); + var logger = log4js.getLogger(); + logger.debug("Some debug messages"); +By default, log4js outputs to stdout with the basic layout, so for the above you would see: + [2010-01-17 11:43:37.987] [DEBUG] [default] - Some debug messages + See example.js: var log4js = require('log4js')(); //note the need to call the function @@ -40,8 +47,8 @@ Output ## configuration 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 -in test/log4js.json +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 ## todo diff --git a/lib/log4js.js b/lib/log4js.js index 38a3630..c5424db 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -44,10 +44,13 @@ * @static * Website: http://log4js.berlios.de */ -module.exports = function (fileSystem) { - var fs = fileSystem || require('fs'), +module.exports = function (fileSystem, standardOutput, configPaths) { + var fs = fileSystem || require('fs'), + standardOutput = standardOutput || console.log, + configPaths = configPaths || require.paths, sys = require('sys'), events = require('events'), + path = require('path'), DEFAULT_CATEGORY = '[default]', ALL_CATEGORIES = '[all]', loggers = {}, @@ -91,8 +94,6 @@ module.exports = function (fileSystem) { } }; - - /** * Get a logger instance. Instance is cached on categoryName level. * @param {String} categoryName name of category to log to. @@ -133,7 +134,7 @@ module.exports = function (fileSystem) { function addAppender () { var args = Array.prototype.slice.call(arguments); var appender = args.shift(); - if (args.length == 0) { + if (args.length == 0 || args[0] === undefined) { args = [ ALL_CATEGORIES ]; } //argument may already be an array @@ -169,9 +170,34 @@ module.exports = function (fileSystem) { } function configure (configurationFile) { - var config = JSON.parse(fs.readFileSync(configurationFile)); - configureAppenders(config.appenders); - configureLevels(config.levels); + if (configurationFile) { + try { + var config = JSON.parse(fs.readFileSync(configurationFile, "utf8")); + configureAppenders(config.appenders); + configureLevels(config.levels); + } catch (e) { + throw new Error("Problem reading log4js config file " + configurationFile + ". Error was " + e.message); + } + } + } + + function findConfiguration() { + //add current directory onto the list of configPaths + var paths = ['.'].concat(configPaths); + //add this module's directory to the end of the list, so that we pick up the default config + paths.push(__dirname); + var pathsWithConfig = paths.filter( function (pathToCheck) { + try { + fs.statSync(path.join(pathToCheck, "log4js.json")); + return true; + } catch (e) { + return false; + } + }); + if (pathsWithConfig.length > 0) { + return path.join(pathsWithConfig[0], 'log4js.json'); + } + return undefined; } function configureAppenders(appenderList) { @@ -310,7 +336,7 @@ module.exports = function (fileSystem) { function consoleAppender (layout) { layout = layout || basicLayout; return function(loggingEvent) { - console.log(layout(loggingEvent)); + standardOutput(layout(loggingEvent)); }; } @@ -488,6 +514,9 @@ module.exports = function (fileSystem) { }; + //set ourselves up if we can find a default log4js.json + configure(findConfiguration()); + return { getLogger: getLogger, getDefaultLogger: getDefaultLogger, diff --git a/lib/log4js.json b/lib/log4js.json new file mode 100644 index 0000000..fdd836f --- /dev/null +++ b/lib/log4js.json @@ -0,0 +1,10 @@ +{ + "appenders": [ + { + "type": "console", + "layout": { + "type": "basic" + } + } + ] +} \ No newline at end of file diff --git a/test/logging.js b/test/logging.js index 09b88a1..fc9e471 100644 --- a/test/logging.js +++ b/test/logging.js @@ -129,6 +129,66 @@ vows.describe('log4js').addBatch({ assert.deepEqual(messages['tmp-tests.log'], ['main\n','both\n','both\n','main\n']); assert.deepEqual(messages['tmp-tests-warnings.log'], ['both\n','both\n']); } + }, + + 'with no appenders defined' : { + topic: function() { + var logger, message, log4js = require('../lib/log4js')(null, function (msg) { message = msg; } ); + logger = log4js.getLogger("some-logger"); + logger.debug("This is a test"); + return message; + }, + 'should default to the console appender': function(message) { + assert.isTrue(/This is a test$/.test(message)); + } + }, + + 'default setup': { + topic: function() { + var pathsChecked = [], + message, + logger, + fakeFS = { + readFileSync: function (file, encoding) { + assert.equal(file, '/path/to/config/log4js.json'); + assert.equal(encoding, 'utf8'); + return '{ "appenders" : [ { "type": "console", "layout": { "type": "messagePassThrough" }} ] }'; + }, + statSync: function (path) { + pathsChecked.push(path); + if (path === '/path/to/config/log4js.json') { + return true; + } else { + throw new Error("no such file"); + } + } + }, + fakeConsoleLog = function (msg) { message = msg; }, + fakeRequirePath = [ '/a/b/c', '/some/other/path', '/path/to/config', '/some/later/directory' ], + log4js = require('../lib/log4js')(fakeFS, fakeConsoleLog, fakeRequirePath), + logger = log4js.getLogger('a-test'); + logger.debug("this is a test"); + + return [ pathsChecked, message ]; + }, + + 'should check current directory, require paths, and finally the module dir for log4js.json': function(args) { + var pathsChecked = args[0]; + assert.deepEqual(pathsChecked, [ + 'log4js.json', + '/a/b/c/log4js.json', + '/some/other/path/log4js.json', + '/path/to/config/log4js.json', + '/some/later/directory/log4js.json', + require('path').normalize(__dirname + '/../lib/log4js.json') + ]); + }, + + 'should configure log4js from first log4js.json found': function(args) { + var message = args[1]; + assert.equal(message, 'this is a test'); + } } - + + }).export(module); From 8145e3b08f2c6311401a95c6c47fa7e6cbc9cfa1 Mon Sep 17 00:00:00 2001 From: csausdev Date: Sun, 5 Dec 2010 14:36:35 +1100 Subject: [PATCH 08/25] added masylum's coloured layout function --- lib/log4js.js | 76 ++++++++++++++++++++++++++++++++++++++++++------- lib/log4js.json | 5 +--- test/logging.js | 21 ++++++++++++-- 3 files changed, 85 insertions(+), 17 deletions(-) diff --git a/lib/log4js.js b/lib/log4js.js index c5424db..85b6714 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -56,14 +56,14 @@ module.exports = function (fileSystem, standardOutput, configPaths) { loggers = {}, appenders = {}, levels = { - ALL: new Level(Number.MIN_VALUE, "ALL"), - TRACE: new Level(5000, "TRACE"), - DEBUG: new Level(10000, "DEBUG"), - INFO: new Level(20000, "INFO"), - WARN: new Level(30000, "WARN"), - ERROR: new Level(40000, "ERROR"), - FATAL: new Level(50000, "FATAL"), - OFF: new Level(Number.MAX_VALUE, "OFF") + ALL: new Level(Number.MIN_VALUE, "ALL", "grey"), + TRACE: new Level(5000, "TRACE", "blue"), + DEBUG: new Level(10000, "DEBUG", "cyan"), + INFO: new Level(20000, "INFO", "green"), + WARN: new Level(30000, "WARN", "yellow"), + ERROR: new Level(40000, "ERROR", "red"), + FATAL: new Level(50000, "FATAL", "magenta"), + OFF: new Level(Number.MAX_VALUE, "OFF", "grey") }, appenderMakers = { "file": function(config) { @@ -226,9 +226,10 @@ module.exports = function (fileSystem, standardOutput, configPaths) { } } - function Level(level, levelStr) { + function Level(level, levelStr, colour) { this.level = level; this.levelStr = levelStr; + this.colour = colour; } /** @@ -334,7 +335,7 @@ module.exports = function (fileSystem, standardOutput, configPaths) { } function consoleAppender (layout) { - layout = layout || basicLayout; + layout = layout || colouredLayout; return function(loggingEvent) { standardOutput(layout(loggingEvent)); }; @@ -394,6 +395,57 @@ module.exports = function (fileSystem, standardOutput, configPaths) { return output; } + /** + * Taken from masylum's fork (https://github.com/masylum/log4js-node) + */ + function colorize (str, style) { + var styles = { + //styles + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + //grayscale + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [90, 39], + //colors + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] + }; + return '\033[' + styles[style][0] + 'm' + str + + '\033[' + styles[style][1] + 'm'; + } + + /** + * colouredLayout - taken from masylum's fork. + * same as basicLayout, but with colours. + */ + function colouredLayout (loggingEvent) { + var timestampLevelAndCategory = colorize('[' + loggingEvent.startTime.toFormattedString() + '] ', 'grey'); + timestampLevelAndCategory += colorize( + '[' + loggingEvent.level.toString() + '] ', loggingEvent.level.colour + ); + timestampLevelAndCategory += colorize(loggingEvent.categoryName + ' - ', 'grey'); + + var output = timestampLevelAndCategory + loggingEvent.message; + + if (loggingEvent.exception) { + output += '\n' + output += timestampLevelAndCategory; + if (loggingEvent.exception.stack) { + output += loggingEvent.exception.stack; + } else { + output += loggingEvent.exception.name + ': '+loggingEvent.exception.message; + } + } + return output; + } + function messagePassThroughLayout (loggingEvent) { return loggingEvent.message; } @@ -533,7 +585,9 @@ module.exports = function (fileSystem, standardOutput, configPaths) { basicLayout: basicLayout, messagePassThroughLayout: messagePassThroughLayout, - patternLayout: patternLayout + patternLayout: patternLayout, + colouredLayout: colouredLayout, + coloredLayout: colouredLayout }; } diff --git a/lib/log4js.json b/lib/log4js.json index fdd836f..6c2998c 100644 --- a/lib/log4js.json +++ b/lib/log4js.json @@ -1,10 +1,7 @@ { "appenders": [ { - "type": "console", - "layout": { - "type": "basic" - } + "type": "console" } ] } \ No newline at end of file diff --git a/test/logging.js b/test/logging.js index fc9e471..215c73e 100644 --- a/test/logging.js +++ b/test/logging.js @@ -188,7 +188,24 @@ vows.describe('log4js').addBatch({ var message = args[1]; assert.equal(message, 'this is a test'); } + }, + + 'colouredLayout': { + topic: function() { + return require('../lib/log4js')().colouredLayout; + }, + + 'should apply level colour codes to output': function(layout) { + var output = layout({ + message: "nonsense", + startTime: new Date(2010, 11, 5, 14, 18, 30, 45), + categoryName: "cheese", + level: { + colour: "green", + toString: function() { return "ERROR"; } + } + }); + assert.equal(output, '\033[90m[2010-12-05 14:18:30.045] \033[39m\033[32m[ERROR] \033[39m\033[90mcheese - \033[39mnonsense'); + } } - - }).export(module); From 4406f211c40fb4fe0b45e2c2aa32553d1b0d7ba2 Mon Sep 17 00:00:00 2001 From: csausdev Date: Sun, 5 Dec 2010 17:17:37 +1100 Subject: [PATCH 09/25] added test for log roller, not written yet --- README.md | 2 +- lib/log4js.json | 2 +- test/logging.js | 90 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ee1929f..a251877 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Minimalist version: var log4js = require('log4js')(); var logger = log4js.getLogger(); logger.debug("Some debug messages"); -By default, log4js outputs to stdout with the basic layout, so for the above you would see: +By default, log4js outputs to stdout with the coloured layout (thanks to [masylum](http://github.com/masylum)), so for the above you would see: [2010-01-17 11:43:37.987] [DEBUG] [default] - Some debug messages See example.js: diff --git a/lib/log4js.json b/lib/log4js.json index 6c2998c..7b6d3e7 100644 --- a/lib/log4js.json +++ b/lib/log4js.json @@ -1,7 +1,7 @@ { "appenders": [ { - "type": "console" + "type": "console" } ] } \ No newline at end of file diff --git a/test/logging.js b/test/logging.js index 215c73e..f592271 100644 --- a/test/logging.js +++ b/test/logging.js @@ -80,6 +80,94 @@ vows.describe('log4js').addBatch({ } }, + 'fileAppender - with rolling based on size and number of files to keep': { + topic: function() { + var watchCb, + filesOpened = [], + filesClosed = [], + filesRenamed = [], + newFilenames = [], + existingFiles = ['tests.log'], + log4js = require('../lib/log4js')({ + watchFile: function(file, options, callback) { + assert.equal(file, 'tests.log'); + assert.deepEqual(options, { persistent: true, interval: 30000 }); + assert.isFunction(callback); + watchCb = callback; + }, + openSync: function(file) { + assert.equal(file, 'tests.log'); + filesOpened.push(file); + return file; + }, + statSync: function(file) { + if (existingFiles.indexOf(file) < 0) { + throw new Error("this file doesn't exist"); + } else { + return true; + } + }, + renameSync: function(oldFile, newFile) { + filesRenamed.push(oldFile); + existingFiles.push(newFile); + }, + closeSync: function(file) { + //it should always be closing tests.log + assert.equal(file, 'tests.log'); + filesClosed.push(file); + } + }); + var appender = log4js.fileAppender('tests.log', log4js.messagePassThroughLayout, 1024, 2, 30); + return [watchCb, filesOpened, filesClosed, filesRenamed, existingFiles]; + }, + + 'should close current log file, rename all old ones, open new one on rollover': function(args) { + var watchCb = args[0], filesOpened = args[1], filesClosed = args[2], filesRenamed = args[3], existingFiles = args[4]; + assert.isFunction(watchCb); + //tell the watchCb that the file is below the threshold + watchCb({ size: 891 }, { size: 0 }); + //filesOpened should still be the first one. + assert.length(filesOpened, 1); + //tell the watchCb that the file is now over the threshold + watchCb({ size: 1053 }, { size: 891 }); + //it should have closed the first log file. + assert.length(filesClosed, 1); + //it should have renamed the previous log file + assert.length(filesRenamed, 1); + //and we should have two files now + assert.length(existingFiles, 2); + assert.deepEqual(existingFiles, ['tests.log', 'tests.log.1']); + //and opened a new log file. + assert.length(filesOpened, 2); + + //now tell the watchCb that we've flipped over the threshold again + watchCb({ size: 1025 }, { size: 123 }); + //it should have closed the old file + assert.length(filesClosed, 2); + //it should have renamed both the old log file, and the previous '.1' file + assert.length(filesRenamed, 3); + assert.deepEqual(filesRenamed, ['tests.log', 'tests.log', 'tests.log.1' ]); + //it should have renamed 2 more file + assert.length(existingFiles, 2); + assert.deepEqual(existingFiles, ['tests.log', 'tests.log.2', 'tests.log.1']); + //and opened a new log file + assert.length(filesOpened, 3); + + //tell the watchCb we've flipped again. + watchCb({ size: 1024 }, { size: 234 }); + //close the old one again. + assert.length(filesClosed, 3); + //it should have renamed the old log file and the 2 backups, with the last one being overwritten. + assert.length(filesRenamed, 5); + assert.deepEqual(filesRenamed, ['tests.log', 'tests.log', 'tests.log.1', 'tests.log', 'tests.log.1' ]); + //it should have renamed 2 more files + assert.length(existingFiles, 5); + assert.deepEqual(existingFiles, ['tests.log', 'tests.log.2', 'tests.log.1', 'tests.log.2', 'tests.log.1']); + //and opened a new log file + assert.length(filesOpened, 4); + } + }, + 'configure' : { topic: function() { var messages = {}, fakeFS = { @@ -90,7 +178,7 @@ vows.describe('log4js').addBatch({ if (!messages.hasOwnProperty(file)) { messages[file] = []; } - messages[file].push(message); + messages[file].push(message); }, readFileSync: function(file, encoding) { return require('fs').readFileSync(file, encoding); From 75b9e82cacd762217ecca56556e21ed4485e4680 Mon Sep 17 00:00:00 2001 From: csausdev Date: Mon, 6 Dec 2010 09:14:20 +1100 Subject: [PATCH 10/25] added a log rolling function to file appender --- lib/log4js.js | 53 ++++++++++++++++++++++++++++++++++++++++++++++--- test/logging.js | 20 +++++++++++-------- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/lib/log4js.js b/lib/log4js.js index 85b6714..5a83fca 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -342,22 +342,69 @@ module.exports = function (fileSystem, standardOutput, configPaths) { } /** - * File Appender writing the logs to a text file. + * File Appender writing the logs to a text file. Supports rolling of logs by size. * * @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 logSize - the maximum size (in bytes) for a log file, 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 filePollInterval - the time in seconds between file size checks (default 30s) */ - function fileAppender (file, layout) { + function fileAppender (file, layout, logSize, numBackups, filePollInterval) { layout = layout || basicLayout; //syncs are generally bad, but we need //the file to be open before we start doing any writing. - var logFile = fs.openSync(file, 'a', 0644); + var logFile = fs.openSync(file, 'a', 0644); + + if (logSize > 0) { + setupLogRolling(logFile, file, logSize, numBackups || 5, (filePollInterval * 1000) || 30000); + } return function(loggingEvent) { fs.write(logFile, layout(loggingEvent)+'\n', null, "utf8"); }; } + function setupLogRolling (logFile, filename, logSize, numBackups, filePollInterval) { + fs.watchFile(filename, + { + persistent: false, + interval: filePollInterval + }, + function (curr, prev) { + if (curr.size >= logSize) { + rollThatLog(logFile, filename, numBackups); + } + } + ); + } + + function rollThatLog (logFile, filename, numBackups) { + //first close the current one. + fs.closeSync(logFile); + //roll the backups (rename file.n-1 to file.n, where n <= numBackups) + for (var i=numBackups; i > 0; i--) { + if (i > 1) { + if (fileExists(filename + '.' + (i-1))) { + fs.renameSync(filename+'.'+(i-1), filename+'.'+i); + } + } else { + fs.renameSync(filename, filename+'.1'); + } + } + //open it up again + logFile = fs.openSync(filename, 'a', 0644); + } + + function fileExists (filename) { + try { + fs.statSync(filename); + return true; + } catch (e) { + return false; + } + } + function logLevelFilter (levelString, appender) { var level = Level.toLevel(levelString); return function(logEvent) { diff --git a/test/logging.js b/test/logging.js index f592271..23bf0f4 100644 --- a/test/logging.js +++ b/test/logging.js @@ -61,7 +61,10 @@ vows.describe('log4js').addBatch({ assert.isNull(arguments[2]); assert.equal(arguments[3], "utf8"); logmessages.push(arguments[1]); - } + }, + watchFile: function() { + throw new Error("watchFile should not be called if logSize is not defined"); + } }, log4js = require('../lib/log4js')(fakeFS); log4js.clearAppenders(); @@ -91,7 +94,8 @@ vows.describe('log4js').addBatch({ log4js = require('../lib/log4js')({ watchFile: function(file, options, callback) { assert.equal(file, 'tests.log'); - assert.deepEqual(options, { persistent: true, interval: 30000 }); + assert.equal(options.persistent, false); + assert.equal(options.interval, 30000); assert.isFunction(callback); watchCb = callback; }, @@ -146,10 +150,10 @@ vows.describe('log4js').addBatch({ assert.length(filesClosed, 2); //it should have renamed both the old log file, and the previous '.1' file assert.length(filesRenamed, 3); - assert.deepEqual(filesRenamed, ['tests.log', 'tests.log', 'tests.log.1' ]); + assert.deepEqual(filesRenamed, ['tests.log', 'tests.log.1', 'tests.log' ]); //it should have renamed 2 more file - assert.length(existingFiles, 2); - assert.deepEqual(existingFiles, ['tests.log', 'tests.log.2', 'tests.log.1']); + assert.length(existingFiles, 4); + assert.deepEqual(existingFiles, ['tests.log', 'tests.log.1', 'tests.log.2', 'tests.log.1']); //and opened a new log file assert.length(filesOpened, 3); @@ -159,10 +163,10 @@ vows.describe('log4js').addBatch({ assert.length(filesClosed, 3); //it should have renamed the old log file and the 2 backups, with the last one being overwritten. assert.length(filesRenamed, 5); - assert.deepEqual(filesRenamed, ['tests.log', 'tests.log', 'tests.log.1', 'tests.log', 'tests.log.1' ]); + assert.deepEqual(filesRenamed, ['tests.log', 'tests.log.1', 'tests.log', 'tests.log.1', 'tests.log' ]); //it should have renamed 2 more files - assert.length(existingFiles, 5); - assert.deepEqual(existingFiles, ['tests.log', 'tests.log.2', 'tests.log.1', 'tests.log.2', 'tests.log.1']); + assert.length(existingFiles, 6); + assert.deepEqual(existingFiles, ['tests.log', 'tests.log.1', 'tests.log.2', 'tests.log.1', 'tests.log.2', 'tests.log.1']); //and opened a new log file assert.length(filesOpened, 4); } From 966b8ced4f4a6c33f233b1b7e68cb69da2696409 Mon Sep 17 00:00:00 2001 From: csausdev Date: Mon, 6 Dec 2010 18:23:43 +1100 Subject: [PATCH 11/25] Added log rolling to config files --- README.md | 8 +++----- lib/log4js.js | 2 +- test/logging.js | 13 +++++++++++-- test/with-log-rolling.json | 11 +++++++++++ 4 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 test/with-log-rolling.json diff --git a/README.md b/README.md index a251877..82ad54b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 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 -and tidied up some of the javascript. +and tidied up some of the javascript. It includes a basic file logger, with log rolling based on file size. 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). @@ -47,13 +47,11 @@ Output ## configuration 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). -An example file can be found in test/log4js.json +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` ## todo -I need to make a RollingFileAppender, which will do log rotation. - patternLayout has no tests. This is mainly because I haven't found a use for it yet, and am not entirely sure what it was supposed to do. It is more-or-less intact from the original log4js. diff --git a/lib/log4js.js b/lib/log4js.js index 5a83fca..2c47dec 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -71,7 +71,7 @@ module.exports = function (fileSystem, standardOutput, configPaths) { if (config.layout) { layout = layoutMakers[config.layout.type](config.layout); } - return fileAppender(config.filename, layout); + return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.pollInterval); }, "console": function(config) { var layout; diff --git a/test/logging.js b/test/logging.js index 23bf0f4..8045698 100644 --- a/test/logging.js +++ b/test/logging.js @@ -186,7 +186,10 @@ vows.describe('log4js').addBatch({ }, readFileSync: function(file, encoding) { return require('fs').readFileSync(file, encoding); - } + }, + watchFile: function(file) { + messages.watchedFile = file; + } }, log4js = require('../lib/log4js')(fakeFS); return [ log4js, messages ]; @@ -220,7 +223,13 @@ vows.describe('log4js').addBatch({ assert.length(messages['tmp-tests-warnings.log'], 2); assert.deepEqual(messages['tmp-tests.log'], ['main\n','both\n','both\n','main\n']); assert.deepEqual(messages['tmp-tests-warnings.log'], ['both\n','both\n']); - } + }, + 'should handle fileAppender with log rolling' : function(args) { + var log4js = args[0], messages = args[1]; + delete messages['tmp-test.log']; + log4js.configure('test/with-log-rolling.json'); + assert.equal(messages.watchedFile, 'tmp-test.log'); + } }, 'with no appenders defined' : { diff --git a/test/with-log-rolling.json b/test/with-log-rolling.json new file mode 100644 index 0000000..1d745ca --- /dev/null +++ b/test/with-log-rolling.json @@ -0,0 +1,11 @@ +{ + "appenders": [ + { + "type": "file", + "filename": "tmp-test.log", + "maxLogSize": 1024, + "backups": 3, + "pollInterval": 15 + } + ] +} \ No newline at end of file From 2b7be410eaea50c2ae37c66250e2ac142c76b8a7 Mon Sep 17 00:00:00 2001 From: csausdev Date: Mon, 6 Dec 2010 18:31:08 +1100 Subject: [PATCH 12/25] added messagePassThroughLayout to vows tests --- lib/log4js.js | 1 + test/logging.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/log4js.js b/lib/log4js.js index 2c47dec..5ef7f4f 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -380,6 +380,7 @@ module.exports = function (fileSystem, standardOutput, configPaths) { } function rollThatLog (logFile, filename, numBackups) { + //doing all of this fs stuff sync, because I don't want to lose any log events. //first close the current one. fs.closeSync(logFile); //roll the backups (rename file.n-1 to file.n, where n <= numBackups) diff --git a/test/logging.js b/test/logging.js index 8045698..4e713d9 100644 --- a/test/logging.js +++ b/test/logging.js @@ -308,5 +308,22 @@ vows.describe('log4js').addBatch({ }); assert.equal(output, '\033[90m[2010-12-05 14:18:30.045] \033[39m\033[32m[ERROR] \033[39m\033[90mcheese - \033[39mnonsense'); } + }, + + 'messagePassThroughLayout': { + topic: function() { + return require('../lib/log4js')().messagePassThroughLayout; + }, + 'should take a logevent and output only the message' : function(layout) { + assert.equal(layout({ + message: "nonsense", + startTime: new Date(2010, 11, 5, 14, 18, 30, 45), + categoryName: "cheese", + level: { + colour: "green", + toString: function() { return "ERROR"; } + } + }), "nonsense"); + } } }).export(module); From 2e03ad07489a90e6963fd233a2df8382614bf331 Mon Sep 17 00:00:00 2001 From: csausdev Date: Mon, 6 Dec 2010 18:38:20 +1100 Subject: [PATCH 13/25] moved Date tests to vows format --- spec/spec.logging.js | 23 ----------------------- test/logging.js | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/spec/spec.logging.js b/spec/spec.logging.js index 5db5932..7f3ef99 100644 --- a/spec/spec.logging.js +++ b/spec/spec.logging.js @@ -137,12 +137,6 @@ describe 'log4js' end end - describe 'messagePassThroughLayout' - it 'should take a logevent and output only the message' - logger.debug('this is a test'); - log4js.messagePassThroughLayout(event).should.be 'this is a test' - end - end describe 'logLevelFilter' @@ -168,20 +162,3 @@ describe 'log4js' end -describe 'Date' - before - require("log4js"); - end - - describe 'toFormattedString' - it 'should add a toFormattedString method to Date' - var date = new Date(); - date.should.respond_to 'toFormattedString' - end - - it 'should default to a format' - var date = new Date(2010, 0, 11, 14, 31, 30, 5); - date.toFormattedString().should.be '2010-01-11 14:31:30.005' - end - end -end diff --git a/test/logging.js b/test/logging.js index 4e713d9..9b58b04 100644 --- a/test/logging.js +++ b/test/logging.js @@ -325,5 +325,19 @@ vows.describe('log4js').addBatch({ } }), "nonsense"); } + }, + + 'Date extensions': { + topic: function() { + require('../lib/log4js'); + return new Date(2010, 0, 11, 14, 31, 30, 5); + }, + 'should add a toFormattedString method to Date': function(date) { + assert.isFunction(date.toFormattedString); + }, + 'should default to a format': function(date) { + assert.equal(date.toFormattedString(), '2010-01-11 14:31:30.005'); + } } + }).export(module); From 04569674f60f66e87b8a3c87e1c699e3c9b643a1 Mon Sep 17 00:00:00 2001 From: csausdev Date: Mon, 6 Dec 2010 20:26:23 +1100 Subject: [PATCH 14/25] moved logLevelFilter tests to vows --- spec/spec.logging.js | 20 -------------------- test/logging.js | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/spec/spec.logging.js b/spec/spec.logging.js index 7f3ef99..0b91e5a 100644 --- a/spec/spec.logging.js +++ b/spec/spec.logging.js @@ -138,26 +138,6 @@ describe 'log4js' end - describe 'logLevelFilter' - - it 'should only pass log events greater than or equal to its own level' - var logEvent; - log4js.addAppender(log4js.logLevelFilter('ERROR', function(evt) { logEvent = evt; })); - logger.debug('this should not trigger an event'); - logEvent.should.be undefined - - logger.warn('neither should this'); - logEvent.should.be undefined - - logger.error('this should, though'); - logEvent.should.not.be undefined - logEvent.message.should.be 'this should, though' - - logger.fatal('so should this') - logEvent.message.should.be 'so should this' - end - - end end diff --git a/test/logging.js b/test/logging.js index 9b58b04..8fd9d8a 100644 --- a/test/logging.js +++ b/test/logging.js @@ -327,6 +327,25 @@ vows.describe('log4js').addBatch({ } }, + 'logLevelFilter': { + topic: function() { + var log4js = require('../lib/log4js')(), logEvents = [], logger; + log4js.clearAppenders(); + log4js.addAppender(log4js.logLevelFilter('ERROR', function(evt) { logEvents.push(evt); })); + logger = log4js.getLogger(); + logger.debug('this should not trigger an event'); + logger.warn('neither should this'); + logger.error('this should, though'); + logger.fatal('so should this'); + return logEvents; + }, + 'should only pass log events greater than or equal to its own level' : function(logEvents) { + assert.length(logEvents, 2); + assert.equal(logEvents[0].message, 'this should, though'); + assert.equal(logEvents[1].message, 'so should this'); + } + }, + 'Date extensions': { topic: function() { require('../lib/log4js'); From 7d4fdce28f7f1fdb160198283102668fdf6f9dae Mon Sep 17 00:00:00 2001 From: csausdev Date: Mon, 6 Dec 2010 20:42:14 +1100 Subject: [PATCH 15/25] updated npm details --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 73266e8..e5d10f8 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,10 @@ }, "engines": [ "node >=0.1.100" ], "scripts": { - "test": "test.js" + "test": "vows test/logging.js" }, "directories": { - "test": "spec" + "test": "test", + "lib": "lib" } } From 80305ca376580d883e7ff05b4b180bfdb85044ca Mon Sep 17 00:00:00 2001 From: csausdev Date: Tue, 7 Dec 2010 08:08:29 +1100 Subject: [PATCH 16/25] moved basicLayout tests to vows --- spec/spec.logging.js | 33 +---------------------------- test/logging.js | 49 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/spec/spec.logging.js b/spec/spec.logging.js index 0b91e5a..4e6f923 100644 --- a/spec/spec.logging.js +++ b/spec/spec.logging.js @@ -104,39 +104,8 @@ describe 'log4js' end end - describe 'basicLayout' - it 'should take a logevent and output a formatted string' - logger.debug('this is a test'); - var output = log4js.basicLayout(event); - output.should.match /\[.*?\] \[DEBUG\] tests - this is a test/ - end - - it 'should output a stacktrace, message if the event has an error attached' - var error = new Error("Some made-up error"); - var stack = error.stack.split(/\n/); - - logger.debug('this is a test', error); - - var output = log4js.basicLayout(event); - var lines = output.split(/\n/); - lines.length.should.be stack.length+1 - lines[0].should.match /\[.*?\] \[DEBUG\] tests - this is a test/ - lines[1].should.match /\[.*?\] \[DEBUG\] tests - Error: Some made-up error/ - for (var i = 1; i < stack.length; i++) { - lines[i+1].should.eql stack[i] - } - end - - it 'should output a name and message if the event has something that pretends to be an error' - logger.debug('this is a test', { name: 'Cheese', message: 'Gorgonzola smells.' }); - var output = log4js.basicLayout(event); - var lines = output.split(/\n/); - lines.length.should.be 2 - lines[0].should.match /\[.*?\] \[DEBUG\] tests - this is a test/ - lines[1].should.match /\[.*?\] \[DEBUG\] tests - Cheese: Gorgonzola smells./ - end - end + diff --git a/test/logging.js b/test/logging.js index 8fd9d8a..92d674e 100644 --- a/test/logging.js +++ b/test/logging.js @@ -327,6 +327,55 @@ vows.describe('log4js').addBatch({ } }, + 'basicLayout': { + topic: function() { + var layout = require('../lib/log4js')().basicLayout, + event = { + message: 'this is a test', + startTime: new Date(2010, 11, 5, 14, 18, 30, 45), + categoryName: "tests", + level: { + colour: "green", + toString: function() { return "DEBUG"; } + } + }; + return [layout, event]; + }, + 'should take a logevent and output a formatted string': function(args) { + var layout = args[0], event = args[1]; + assert.equal(layout(event), "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test"); + }, + 'should output a stacktrace, message if the event has an error attached': function(args) { + var layout = args[0], event = args[1], output, lines, + error = new Error("Some made-up error"), + stack = error.stack.split(/\n/); + + event.exception = error; + output = layout(event); + lines = output.split(/\n/); + + assert.length(lines, stack.length+1); + assert.equal(lines[0], "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test"); + assert.equal(lines[1], "[2010-12-05 14:18:30.045] [DEBUG] tests - Error: Some made-up error"); + for (var i = 1; i < stack.length; i++) { + assert.equal(lines[i+1], stack[i]); + } + }, + 'should output a name and message if the event has something that pretends to be an error': function(args) { + var layout = args[0], event = args[1], output, lines; + event.exception = { + name: 'Cheese', + message: 'Gorgonzola smells.' + }; + output = layout(event); + lines = output.split(/\n/); + + assert.length(lines, 2); + assert.equal(lines[0], "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test"); + assert.equal(lines[1], "[2010-12-05 14:18:30.045] [DEBUG] tests - Cheese: Gorgonzola smells."); + } + }, + 'logLevelFilter': { topic: function() { var log4js = require('../lib/log4js')(), logEvents = [], logger; From 2e3843205af970f47360f31ad29536b5df899410 Mon Sep 17 00:00:00 2001 From: csausdev Date: Tue, 7 Dec 2010 09:12:43 +1100 Subject: [PATCH 17/25] finished moving all tests to vows --- README.md | 2 +- spec/lib/images/bg.png | Bin 154 -> 0 bytes spec/lib/images/hr.png | Bin 321 -> 0 bytes spec/lib/images/loading.gif | Bin 2608 -> 0 bytes spec/lib/images/sprites.bg.png | Bin 4876 -> 0 bytes spec/lib/images/sprites.png | Bin 3629 -> 0 bytes spec/lib/images/vr.png | Bin 145 -> 0 bytes spec/lib/jspec.css | 149 --- spec/lib/jspec.growl.js | 115 --- spec/lib/jspec.jquery.js | 71 -- spec/lib/jspec.js | 1773 -------------------------------- spec/lib/jspec.shell.js | 39 - spec/lib/jspec.timers.js | 90 -- spec/lib/jspec.xhr.js | 193 ---- spec/spec.logging.js | 113 -- test/logging.js | 85 ++ tests.js | 43 - 17 files changed, 86 insertions(+), 2587 deletions(-) delete mode 100644 spec/lib/images/bg.png delete mode 100644 spec/lib/images/hr.png delete mode 100644 spec/lib/images/loading.gif delete mode 100644 spec/lib/images/sprites.bg.png delete mode 100644 spec/lib/images/sprites.png delete mode 100644 spec/lib/images/vr.png delete mode 100644 spec/lib/jspec.css delete mode 100644 spec/lib/jspec.growl.js delete mode 100644 spec/lib/jspec.jquery.js delete mode 100644 spec/lib/jspec.js delete mode 100644 spec/lib/jspec.shell.js delete mode 100644 spec/lib/jspec.timers.js delete mode 100644 spec/lib/jspec.xhr.js delete mode 100644 spec/spec.logging.js delete mode 100644 tests.js diff --git a/README.md b/README.md index 82ad54b..e423020 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ npm install log4js ## tests -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. +Tests now use [vows](http://vowsjs.org), run with `vows test/logging.js`. ## usage diff --git a/spec/lib/images/bg.png b/spec/lib/images/bg.png deleted file mode 100644 index 947804ff6acaaf93986a0a11d205df3113656816..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^j0_CS3LH#8R&M_M3qVS;#5JNMI6tkVJh3R1!7(L2 zDOJHUH!(dmC^a#qvhZZ84N#Gdr;B4q#jT`Y|Nq+yGczC7kaMcgIDRvs>~})ZZHL{d z3*N|ke!F!0+{6dRCuY6RkTi>G?|T$zbHu=*fssL9)snBgXWIv$ISihzelF{r5}E+; Cx;4N6 diff --git a/spec/lib/images/hr.png b/spec/lib/images/hr.png deleted file mode 100644 index 4a94d12fd8405c32b4c20abe777c1a9a7e6a30cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 321 zcmV-H0lxl;P)A5E2-vx}KFiT96$Z17UamL`af0P}@7%Vq>XUIGjN-4D7? ThAmXZ00000NkvXXu0mjfvYCee diff --git a/spec/lib/images/loading.gif b/spec/lib/images/loading.gif deleted file mode 100644 index c69e937232b24ea30f01c68bbd2ebc798dcecfcb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2608 zcmdVcdr(tX9tZGC9yiG~=H_*Q-0%n(kWqP*D#hw{AQu8;1%gl-Hrf&{2?48KX;hHy z3Ze*zEz4t3XdUFyLbNPUYlA`|B}P=N1fqtL1*}S;87#|-W9v<#G;ul(e%d3)N(^9c$d2Dz{7}?ErjNd;{EMKkCsk21~b9Gvg zDo<7L=3Z5HNbVlZUcm1eg#o#CZCJU`3IYHwM->zCd?uYrF3vKFeM}v?f+%s?E>ly|3W25ry9#NNbTx-}0ON58dTrs^ix{_1O0Wh~SVSBlH)Ajn zPn^Gbjz}PCtN@#keR&hK&Dhl-b$kZ8^S)x#dh0{7X=X%CCJk7P1PSO>T&S8I4{#Lg zb5#)o=;!ZP*1nM{cI4@(x7o27*SA()NHmrn67aN@Pmi~(i_SnrjYnwh36aG%!@i0d zqbvfa44f|?OG4ntP|nbjhEl1)Yp6ZN@yjy zy4==QmLy%t;ps3R?~f2KfTTI|2?q8dFd6^z5GF+Xa&Y)sjG)hxit80pPcOP zJ z*LW{SyGHD%hUotV+W%I}fBLAIx!8|7#}$;clKQ+{&FjDqGQ2ZNx(lYM3*%~}ILnao zM`aui55~ZFJlu^!5rdA9Q_7H68H_;##u{x(Yn-vSfIRCb^Nqsg zGRS!Egm>h+o<}LeV4&CLReo9FrDjDvs}8?JwC)#Qs|ie=r?~xUh)&*d`Fx>FG}%X# zNdtDHBKhLPC0wpooFDAQKL%*6T|ULH$=wX!NhcasgD3d;-d$I6yRK3yN+E~C1335_iLOt+*9uvSZ`>*KA}vm}08wRq=>5l|t*Na&jR z-C1&C`nkEk#sB|@yyt-#fXngP04My zm7u$Q%EJbHp`>~`5W&L{W!6`y&}LMS;jfUpgO~7TLVMRZ9IC)IZp0A${`yp0{&wco z#1nx@XMkhqeK%7?RE7JdLr1^nwFfaJ0Q&Lv?WNJ%9}VSJsNY2+UYs2%EU0J~ayFXv zi*?7KCXQHkD)O6!0Q%4N+HTODHxJ{kQSuQX$l-rSwkwh(zMkdfzxyGwl@yHC)C4p< z&n2%8#M?)Q@mgHL1ot8`SFdSEj9ye|jHy+U8#@HoUExG=@AVkRAe_qYm4EpzK6L*& zh`)26?V#f4#_h^P9G^%>h2-H3)$QP zQovu6J9qDvsxqweDdNNa!Lb?L4_UF{tLX_nN7r0U_vF14YKcGR-*Gl} zx3oG)bzf|65dBxD-;2ZCp??K;+TuQ9onnK?==5hzbkb^r_g>z4#D8mcv8(+XdoszA zCx-qhdgxMNMotj}SiL_6V(tLcsK7(M(r(%u<}QrVfOvyK6_;~NOTlPGfX@M7S5YQF z&*$(ylJMHJt^_aQeu{C6NaTE$G3HNN@_SnN8YcaKn%`)F@~L1x+ah7-gEJPpc6w%3 zyX}r+Qk$4RHZzfH){e~F*qJ{d*L8a6n4;U?+{de0-t)mal#TVxe)3F}^UBh+zd T)6_**#cgp_+?JL9(ew3BlNF>u diff --git a/spec/lib/images/sprites.bg.png b/spec/lib/images/sprites.bg.png deleted file mode 100644 index dc8790f338c4fce6611e0c2d2f811882a4136b32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4876 zcma)AYgAKL7QTt7h!xN>MFk5}M_Q((YAZp6lE`RVTY)U;s*Zq26>P(l+-gO4~V|-c!N~y{MGtpHvVBhI;gHY zgsljhedU(>{lqO9JkK!q%3V?IdF+u!j~J`c%3Gh+S2pe(O5j z{H6lCAKG^vX^m^m8~yBtKBdNS^C82N!4_e-i?g%A_{)XYnY|&08A4UdkZxRe`|-7` z=}VK--OP~5In}YuY2K*7c)y^2SiO0+u_o_xIp@dmw<>16J`T%v4_6G_=p9sNH8fti zrgPKlbJZbSMK=FLOkBeiLjy;hvAZj-eW?9k(m!NF6O5dU$h?&OIr9l^`hf6e#?8Lq z@Qq&l(7n7D0(o;i16vu7xG(GCq^$!+7xN%(g*o)!6kQo^Jua?Z>gLH6z+>6AWLKG->X;Cu>9Fzb}4}Hrki>Mt#tY z>F=vD^vjwn`|nGQxr*h%ilBf2ncfHM-u z?H>KZO&li;y|Y;>I+W-nFidF`>FMb~eIlMNy*^QG>{s>H=^p;xdg+}rljUdh;|(IN zs;94`e7`<>iNFq~+@-*e-{Z4mN6)P0VN;)Os4ay*Z=4g`T^3ACs(KZnVgWyP%3ck& zo@`8GxUA=|{ZyPV+UfO=db?Ur2xo=P}T(j8#6c2&{{f+c|#=r4m0D?ly+eM!)ugO0aP zFkS={CD`(0KS%iRwLjcVlFPv}+8tU<#`^RCGeqi=M%AbAdk5Pj@QKaVTPH=sOI%%* z6=%Uqk|e$8&@_z}YXz^Q)BCL9tyWf|)$o|Lb<#$7k-fdPUiPqVaUB@z1LTi~blfiFBKFoFB11j>%CLc339 zIzyAIp_>*r=eaMy4c!Lmj)TCtx+()GUrfB9UZXTyBoyKVNQ0fQ5@8kugP4&QHqqzC zk=t~XMFbK`Hrec;$^Hg$mQ2L1Jxi5b}|l zS0PICe{UfNSR6(*w4F=hx3Z%z`hZQ=hcOn5K`_DJdKSaKz^SStMzry7%HAS)70aWd zI}6=T2=4=oSRXvYW|@rh2|NhbW9S~)04A^!FRwsgF=KpICSeo!Pk^7}X>hcp6sHib zt?OZRl(aa&7w@lFs6O!5qA~(GLr^SA3JMAVZjtV*zXxU`<*=7{WB7b#f^ITr z2un?_O;SBoy1gi)bhBMaTF>=FBuF%rJBQB63M@+(F-r=a&L-MI_Q?jFVKg8N=)PIl z-h2}qCx#HN_Q)8z;C>p8NrGUow!p4;xKmOW1niLcf%Ha?FCpf~s}Tt>yV(~pg|Q-; zY8&{1t4bDy1a2R?28aumFAI#!Ol4hohMN|99QTJz3f5?J(+@o8;QpnxQB;{AAdrQ_ z$7pI4wWWYLox)t-io`b6dpRuCFVKDfj`U~95SFC(Cs@c9NMrURbUs=^B?J@*W@ChE zU^_lZpjZm4Vr&|aXqS`<{jr=Me;Zxsj!Xc;EUscFiuwOXv77U6fyV-`*av*P15q-` z^JHw37R15wz~WEF*zjkqz!xQVk=#Gkqzbf=`WB_DNF z_5q=1ES8Kad#)T`1xRIcg(j@qyF?jTAQ*i^@VI2@U1+^DzA*KnY^O z>d8cwK7gNuXF@ck!uhvQHLy4cd~_VerhIFu=*R%rTIfa<5?*j2xzi};DS==IQ(AdG zU<{mbC2?m*wNGvJaTf^tW~ zu+R*Vx;_)$xD>_(_ORwUj#3)KXa7 zIfmO8ft&;s2LVx?ZS?}(Vd_Cgz#YrXYdHLvnue(&7SC!BVT(3k8n!46INlP2$n4M= z)k3|(bcLQEjx$sj0*e)z)L?6Pk$?1Ilj1JY!G>v{w^#F>>BdurgC8kmZlR74G z09CC>FfA(#p@7(>boNG*G93`A8x1v7dxY^}#s|n9PNVFxgYi8w8G1p~VuIjCYMQNa zbHHufOkHv%7AO0{-k&NvU}>`3FSL6?O|$$dhO%KAgvb~)2vTfBuNF;0r`BjV+fkRg zEc92T^@H4z`BWLp*W)pfEyP;5g>}1=CUa)Y@Pe9v-d`!d^Tw4SwR@8`J$&nUXXy z(ov8tR(=dk`e$cnPaYBr=7x&o>E!&lwqL5Sr7;b+uLKo5@COG^X2V~;a~jP__2c#{ z3R_hp)6mwGZa2>;XcG%G%G>Jf3 z&k1(v7y2HOhNe~Po634)gtM`~itESq%@;>&#(0cJT2Z<5aaq?$fcVDnOm;&HS9|98 zh>p{(5|4AgJ-wmWcxhc6-;3AiQ6F3E7jwnW(Tle?i{Dl?GVT4yxK?*4PCj%boLOwJ zl@I8(WzF7$6Hy~`4J!5dYOzxtKPKjho^dm9)Ocb<-OmYkxg(nI?wB|@Id!ljy4U#K z)~-30N9sQr8*M5ZGIlf1fPXmD=WPx*KDvAU)s*C?!@q^6b6t#+8f}MG z+Lpr2Dm@c-n< diff --git a/spec/lib/images/sprites.png b/spec/lib/images/sprites.png deleted file mode 100644 index 010b98eeef699c0d0f4d0136d7d2d352b2b51d4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3629 zcmcJSc{tQ-8^=)+DkW>S!X(?+5@U%%lAU3)r!f@9zJ(!*4kMim#$fECVuZ0TEq0Zy zEHic!LktmP%Xn4SIj6UC&U@Z}&UM|__5AMZe!ll}|E}k`f1l?UV`8LxnB@cu9Ua|a zz02BWd!q*(9fRn>1A9-qOoBsu1FxUXRX=l2XTLy49~j*QC(rA!lX`GR7nm8$(dm|V z2TW~G+^wg50UShJAcn+&`vqPsk0ahTjsq8g6AZ|#NXDmABE)OfHqfN^?86+lzVvFO zrc7nRQM7*AyR-1)$5X*+XCLZj%jh46!=L=ZXfsc9G;pGiih@PJj!_TSm9LNa8g)06 z=ZX-hY}!li+dDuMb`;Bt`n;STvN+-$CwI|T27QVy{K+n~QC$*rcZU9zJ}y)ms4G+l<*Xqb@my)!w{*z24HmFms;FLn)eMPmG|^ zbSQ%kOM?UbCyLDtRrlS;`dQnyRrm=fC0RVmDeDvx34}+*PAehs@Jy+K&(`D`C<$78D6rKl;IX^=4cETX7KxLOo$lk; zNvmYhI=@Hjh;ifRRrqs?@*})W7Bdp+-FnQ(U6!lypI<^_*V)1FeDYg2a+9=GlhsMm z5GjT6Seq{Z)DGZF?qd_guwRBgKty;3ViTop-E*8A@o7ivLgJvyYD-#qy!B3xYH+Ii z;AT#pTRdVJSgkIm*U(yit_N_V$)V(Hr!5&xwo_!C@a560wrzt~K+Gl#D0j`+a!Eul zX1l`~piX`kF2t91trOM=mQX(-39}vCo{i4?5H{%&@}=CtqCbi1cg-nfvWo{-QQ_}( zJ@nPGz%ou-zma=MOrfN<%>GhzE7-zfS|?f+s;u!Tzp-?AsHZ(;?IfHl;JB!7=}!R*$2u_Wa*AX%2>-MTnxUGyEw18XmQG;k(#I3u;7 zO{FpsMu%pV5XC!mUf=9kmX`COEE^Kk^B~nK*9z=|f#{Ki%;wh?vP4Xi1qZGe0AUdB z=9B^2GxsPg*`==9akh2gKfHAwXEoF8JOM=O6Z!zAA?$UgEa)sw<4%-KnRal%rCP2* zCSXW43oANnqfye719`+JC#g)o0P9b!MyCgF8K{<6_kJS}AG?H(wy+cKMP0L@7AAMGhFjobX^m z$8_(=oDVVcIn~b*klbR`W@dGyt$Nkk#?g|qq|K@+p*yX5j&WeWhllq1tJi&6@WmK(hq{R&$Ze;Viv!;qjq~SV#jZzw8qs;e+yNp^2o6hz+>| zW5sX6AXW_EbpIAMrlclz|PcKGv^a7e8!my50VS(q&8ic62X5PK>G<&c#`KjN<0La=1H466oV-~&4aFphS4 zmDXPVF$;FD{rr1Iulr)pj(P3bpM83!-{aqd-~1mY|Lp&m><94+x&6QA|9>T4>-izY zAM0lt(Ap3Y?~K*^VTof$kwo`bg@#Bj=+p&3@V#@c>#+`$Iao>wB3^FhNoJ_?y!kw| zxUzq|9^c@+=IVW7UT5W2Ocz`GC&`lb_D?+^*!zLufCWTGHt=}$enat!hD}yCH&;u< zrS~yS!enFQ-E8Fw!>JLkJBJ!JxIv?VU&<_(lU3L2_uCJ6?buWSOJ9>-CU=j%Gexw) zbEwZxqRel3gGD)OH2LApcW*tkIWNgg+bT;3jca5XhVC{zN38AF%9xyK&Vd&#-k2+_ zUE?xkvS(LR2LuP3MRs`i=4$1%-qRtA0aP~1@aI3>22U5LCR+0GF2*T|5lz!vEyqN+ z+K1&qKBiNFEZYw{Bn8oKZoh6gmEcPnA(z+$hMdCJw4&9tl@jWRd{T3q((aY^r6?#f>DkQ30?xo@a%!u=-FZQ(8I zTCdsM5vrCG&=jagmKDeO1*!<@>$&&$^3fV5d@Jk5quA93*NOk<+V<+OvQCwM zGkCs@$G6{@)~TR7aL*AB_r7z};T_@38c1emX9z6C^Z<(vJOIor-;F9B)!6v*`H(;6 zMe(*y_m-Cgkv4I^Ur~}g*g?oPInee<`=-d@{tUmf;LcLy@v=aNC75bSm7hk#k@})Z z!JSQ2{hTxBHzxX0NeFz`$lyZUv)=-$bHl_j+j-PLjrCNj30mNz5oK*xMxLc|7ZCr?MK zlI-%aj-S&$hJpgRfDf)v2Bw6{;RpE^(8&T1C|lBqOaDF*;f; zemxR0G|PkJLRRV*R_Z%z8AhcGWgczo-Rs4o$yY(-hzx~3f;F_eoRV;(Uaj(hg@3xg?()^3ZKN=|H&=m={ABSt-1H%-}XUudS^7-oeS$(K2EvR%!>~vPF}`u zzik*NfA3U(70utc^_QIZ&b2?d@!RA-LGKX#-=XGjB|mZVuh;*VIhiRx(mkf;@-fV> zC)B(9RMRtg(kv?avn?c!?-x=~+(+9;Uu|SBF3Ojuzh*yVdYHE$4jjxsb^bm-6z7pO zNZWe$ctv2FLAVhM7i$#(fs)ZgTcZI#a65P*cBxuk1#2v{TO$25C3aAD*EMrvVA(sy zTp5Pe(~WD8p(+P!uL-JxroJ8UbaXQR;qd*aTan;tkc6_VYNc( z%?wAnUcB!d+8In{x6Kn-CYb<@6$)VupzPg;y#IV(=@sjF&d5V|bw=gv8|G%sz5hQt NJsl%$+{J5={{f-$rB(m{ diff --git a/spec/lib/images/vr.png b/spec/lib/images/vr.png deleted file mode 100644 index b2e76175d3e8a4b5092757c9cbbfb81802804bc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 145 zcmeAS@N?(olHy`uVBq!ia0y~yVEzDPGjgy2$;wJseIO-S;u=vBoS#-wo>-L1;Fyx1 zl&avFo0y&&l$w}QS$Hzl2B^r`)5S5Q;?|q%hN29LJggh+4ZdAv`VrpRF7+Vg&V(H& syf&pj3kwA*Y^eWvZr(dP{{}_|Ay#%3&0R090!0}-UHx3vIVCg!0D1l{cmMzZ diff --git a/spec/lib/jspec.css b/spec/lib/jspec.css deleted file mode 100644 index 629d41c..0000000 --- a/spec/lib/jspec.css +++ /dev/null @@ -1,149 +0,0 @@ -body.jspec { - margin: 45px 0; - font: 12px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; - background: #efefef url(images/bg.png) top left repeat-x; - text-align: center; -} -#jspec { - margin: 0 auto; - padding-top: 30px; - width: 1008px; - background: url(images/vr.png) top left repeat-y; - text-align: left; -} -#jspec-top { - position: relative; - margin: 0 auto; - width: 1008px; - height: 40px; - background: url(images/sprites.bg.png) top left no-repeat; -} -#jspec-bottom { - margin: 0 auto; - width: 1008px; - height: 15px; - background: url(images/sprites.bg.png) bottom left no-repeat; -} -#jspec .loading { - margin-top: -45px; - width: 1008px; - height: 80px; - background: url(images/loading.gif) 50% 50% no-repeat; -} -#jspec-title { - position: absolute; - top: 15px; - left: 20px; - width: 160px; - font-size: 22px; - font-weight: normal; - background: url(images/sprites.png) 0 -126px no-repeat; - text-align: center; -} -#jspec-title em { - font-size: 10px; - font-style: normal; - color: #BCC8D1; -} -#jspec-report * { - margin: 0; - padding: 0; - background: none; - border: none; -} -#jspec-report { - padding: 15px 40px; - font: 11px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; - color: #7B8D9B; -} -#jspec-report.has-failures { - padding-bottom: 30px; -} -#jspec-report .hidden { - display: none; -} -#jspec-report .heading { - margin-bottom: 15px; -} -#jspec-report .heading span { - padding-right: 10px; -} -#jspec-report .heading .passes em { - color: #0ea0eb; -} -#jspec-report .heading .failures em { - color: #FA1616; -} -#jspec-report table { - font-size: 11px; - border-collapse: collapse; -} -#jspec-report td { - padding: 8px; - text-indent: 30px; - color: #7B8D9B; -} -#jspec-report tr.body { - display: none; -} -#jspec-report tr.body pre { - margin: 0; - padding: 0 0 5px 25px; -} -#jspec-report tr.even:hover + tr.body, -#jspec-report tr.odd:hover + tr.body { - display: block; -} -#jspec-report tr td:first-child em { - display: block; - clear: both; - font-style: normal; - font-weight: normal; - color: #7B8D9B; -} -#jspec-report tr.even:hover, -#jspec-report tr.odd:hover { - text-shadow: 1px 1px 1px #fff; - background: #F2F5F7; -} -#jspec-report td + td { - padding-right: 0; - width: 15px; -} -#jspec-report td.pass { - background: url(images/sprites.png) 3px -7px no-repeat; -} -#jspec-report td.fail { - background: url(images/sprites.png) 3px -158px no-repeat; - font-weight: bold; - color: #FC0D0D; -} -#jspec-report td.requires-implementation { - background: url(images/sprites.png) 3px -333px no-repeat; -} -#jspec-report tr.description td { - margin-top: 25px; - padding-top: 25px; - font-size: 12px; - font-weight: bold; - text-indent: 0; - color: #1a1a1a; -} -#jspec-report tr.description:first-child td { - border-top: none; -} -#jspec-report .assertion { - display: block; - float: left; - margin: 0 0 0 1px; - padding: 0; - width: 1px; - height: 5px; - background: #7B8D9B; -} -#jspec-report .assertion.failed { - background: red; -} -.jspec-sandbox { - display: none; -} \ No newline at end of file diff --git a/spec/lib/jspec.growl.js b/spec/lib/jspec.growl.js deleted file mode 100644 index a150257..0000000 --- a/spec/lib/jspec.growl.js +++ /dev/null @@ -1,115 +0,0 @@ - -// JSpec - Growl - Copyright TJ Holowaychuk (MIT Licensed) - -;(function(){ - - Growl = { - - // --- Version - - version: '1.0.0', - - /** - * Execute the given _cmd_, returning an array of lines from stdout. - * - * Examples: - * - * Growl.exec('growlnotify', '-m', msg) - * - * @param {string ...} cmd - * @return {array} - * @api public - */ - - exec: function(cmd) { - var lines = [], line - with (JavaImporter(java.lang, java.io)) { - var proccess = Runtime.getRuntime().exec(Array.prototype.slice.call(arguments)) - var stream = new DataInputStream(proccess.getInputStream()) - while (line = stream.readLine()) - lines.push(line + '') - stream.close() - } - return lines - }, - - /** - * Return the extension of the given _path_ or null. - * - * @param {string} path - * @return {string} - * @api private - */ - - extname: function(path) { - return path.lastIndexOf('.') != -1 ? - path.slice(path.lastIndexOf('.') + 1, path.length) : - null - }, - - /** - * Version of the 'growlnotify' binary. - * - * @return {string} - * @api private - */ - - binVersion: function() { - try { return this.exec('growlnotify', '-v')[0].split(' ')[1] } catch (e) {} - }, - - /** - * Send growl notification _msg_ with _options_. - * - * Options: - * - * - title Notification title - * - sticky Make the notification stick (defaults to false) - * - name Application name (defaults to growlnotify) - * - image - * - path to an icon sets --iconpath - * - path to an image sets --image - * - capitalized word sets --appIcon - * - filename uses extname as --icon - * - otherwise treated as --icon - * - * Examples: - * - * Growl.notify('New email') - * Growl.notify('5 new emails', { title: 'Thunderbird' }) - * - * @param {string} msg - * @param {options} hash - * @api public - */ - - notify: function(msg, options) { - options = options || {} - var args = ['growlnotify', '-m', msg] - if (!this.binVersion()) throw new Error('growlnotify executable is required') - if (image = options.image) { - var flag, ext = this.extname(image) - flag = flag || ext == 'icns' && 'iconpath' - flag = flag || /^[A-Z]/.test(image) && 'appIcon' - flag = flag || /^png|gif|jpe?g$/.test(ext) && 'image' - flag = flag || ext && (image = ext) && 'icon' - flag = flag || 'icon' - args.push('--' + flag, image) - } - if (options.sticky) args.push('--sticky') - if (options.name) args.push('--name', options.name) - if (options.title) args.push(options.title) - this.exec.apply(this, args) - } - } - - JSpec.include({ - name: 'Growl', - reporting: function(options){ - var stats = JSpec.stats - if (stats.failures) Growl.notify('failed ' + stats.failures + ' assertions', { title: 'JSpec'}) - else Growl.notify('passed ' + stats.passes + ' assertions', { title: 'JSpec' }) - } - }) - -})() \ No newline at end of file diff --git a/spec/lib/jspec.jquery.js b/spec/lib/jspec.jquery.js deleted file mode 100644 index 3c1f784..0000000 --- a/spec/lib/jspec.jquery.js +++ /dev/null @@ -1,71 +0,0 @@ - -// JSpec - jQuery - Copyright TJ Holowaychuk (MIT Licensed) - -JSpec -.requires('jQuery', 'when using jspec.jquery.js') -.include({ - name: 'jQuery', - - // --- Initialize - - init : function() { - jQuery.ajaxSetup({ async: false }) - }, - - // --- Utilities - - utilities : { - element: jQuery, - elements: jQuery, - sandbox : function() { - return jQuery('
') - } - }, - - // --- Matchers - - matchers : { - have_tag : "jQuery(expected, actual).length == 1", - have_one : "alias have_tag", - have_tags : "jQuery(expected, actual).length > 1", - have_many : "alias have_tags", - have_child : "jQuery(actual).children(expected).length == 1", - have_children : "jQuery(actual).children(expected).length > 1", - have_text : "jQuery(actual).text() == expected", - have_value : "jQuery(actual).val() == expected", - be_enabled : "!jQuery(actual).attr('disabled')", - have_class : "jQuery(actual).hasClass(expected)", - - be_visible : function(actual) { - return jQuery(actual).css('display') != 'none' && - jQuery(actual).css('visibility') != 'hidden' && - jQuery(actual).attr('type') != 'hidden' - }, - - be_hidden : function(actual) { - return !JSpec.does(actual, 'be_visible') - }, - - have_classes : function(actual) { - return !JSpec.any(JSpec.toArray(arguments, 1), function(arg){ - return !JSpec.does(actual, 'have_class', arg) - }) - }, - - have_attr : function(actual, attr, value) { - return value ? jQuery(actual).attr(attr) == value: - jQuery(actual).attr(attr) - }, - - 'be disabled selected checked' : function(attr) { - return 'jQuery(actual).attr("' + attr + '")' - }, - - 'have type id title alt href src sel rev name target' : function(attr) { - return function(actual, value) { - return JSpec.does(actual, 'have_attr', attr, value) - } - } - } -}) - diff --git a/spec/lib/jspec.js b/spec/lib/jspec.js deleted file mode 100644 index 742987c..0000000 --- a/spec/lib/jspec.js +++ /dev/null @@ -1,1773 +0,0 @@ - -// JSpec - Core - Copyright TJ Holowaychuk (MIT Licensed) - -;(function(){ - - JSpec = { - version : '3.1.3', - assert : true, - cache : {}, - suites : [], - modules : [], - allSuites : [], - matchers : {}, - stubbed : [], - options : {}, - request : 'XMLHttpRequest' in this ? XMLHttpRequest : null, - stats : { specs: 0, assertions: 0, failures: 0, passes: 0, specsFinished: 0, suitesFinished: 0 }, - - /** - * Default context in which bodies are evaluated. - * - * Replace context simply by setting JSpec.context - * to your own like below: - * - * JSpec.context = { foo : 'bar' } - * - * Contexts can be changed within any body, this can be useful - * in order to provide specific helper methods to specific suites. - * - * To reset (usually in after hook) simply set to null like below: - * - * JSpec.context = null - * - */ - - defaultContext : { - - /** - * Return an object used for proxy assertions. - * This object is used to indicate that an object - * should be an instance of _object_, not the constructor - * itself. - * - * @param {function} constructor - * @return {hash} - * @api public - */ - - an_instance_of : function(constructor) { - return { an_instance_of : constructor } - }, - - /** - * Load fixture at _path_. - * - * Fixtures are resolved as: - * - * - - * - .html - * - * @param {string} path - * @return {string} - * @api public - */ - - fixture : function(path) { - if (JSpec.cache[path]) return JSpec.cache[path] - return JSpec.cache[path] = - JSpec.tryLoading(JSpec.options.fixturePath + '/' + path) || - JSpec.tryLoading(JSpec.options.fixturePath + '/' + path + '.html') - } - }, - - // --- Objects - - reporters : { - - /** - * Report to server. - * - * Options: - * - uri specific uri to report to. - * - verbose weither or not to output messages - * - failuresOnly output failure messages only - * - * @api public - */ - - Server : function(results, options) { - var uri = options.uri || 'http://' + window.location.host + '/results' - JSpec.post(uri, { - stats: JSpec.stats, - options: options, - results: map(results.allSuites, function(suite) { - if (suite.hasSpecs()) - return { - description: suite.description, - specs: map(suite.specs, function(spec) { - return { - description: spec.description, - message: !spec.passed() ? spec.failure().message : null, - status: spec.requiresImplementation() ? 'pending' : - spec.passed() ? 'pass' : - 'fail', - assertions: map(spec.assertions, function(assertion){ - return { - passed: assertion.passed - } - }) - } - }) - } - }) - }) - if ('close' in main) main.close() - }, - - /** - * Default reporter, outputting to the DOM. - * - * Options: - * - reportToId id of element to output reports to, defaults to 'jspec' - * - failuresOnly displays only suites with failing specs - * - * @api public - */ - - DOM : function(results, options) { - var id = option('reportToId') || 'jspec' - var report = document.getElementById(id) - var failuresOnly = option('failuresOnly') - var classes = results.stats.failures ? 'has-failures' : '' - if (!report) throw 'JSpec requires the element #' + id + ' to output its reports' - - function bodyContents(body) { - return JSpec. - escape(JSpec.contentsOf(body)). - replace(/^ */gm, function(a){ return (new Array(Math.round(a.length / 3))).join(' ') }). - replace(/\r\n|\r|\n/gm, '
') - } - - report.innerHTML = '
\ - Passes: ' + results.stats.passes + ' \ - Failures: ' + results.stats.failures + ' \ - Duration: ' + results.duration + ' ms \ -
' + map(results.allSuites, function(suite) { - var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran - if (displaySuite && suite.hasSpecs()) - return '' + - map(suite.specs, function(i, spec) { - return '' + - (spec.requiresImplementation() ? - '' : - (spec.passed() && !failuresOnly) ? - '' : - !spec.passed() ? - '' : - '') + - '' - }).join('') + '' - }).join('') + '
' + escape(suite.description) + '
' + escape(spec.description) + '' + escape(spec.description)+ '' + spec.assertionsGraph() + '' + escape(spec.description) + - map(spec.failures(), function(a){ return '' + escape(a.message) + '' }).join('') + - '' + spec.assertionsGraph() + '
' + bodyContents(spec.body) + '
' - }, - - /** - * Terminal reporter. - * - * @api public - */ - - Terminal : function(results, options) { - failuresOnly = option('failuresOnly') - print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') + - color(" Failures: ", 'bold') + color(results.stats.failures, 'red') + - color(" Duration: ", 'bold') + color(results.duration, 'green') + " ms \n") - - function indent(string) { - return string.replace(/^(.)/gm, ' $1') - } - - each(results.allSuites, function(suite) { - var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran - if (displaySuite && suite.hasSpecs()) { - print(color(' ' + suite.description, 'bold')) - each(suite.specs, function(spec){ - var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){ - return graph + color('.', assertion.passed ? 'green' : 'red') - }) - if (spec.requiresImplementation()) - print(color(' ' + spec.description, 'blue') + assertionsGraph) - else if (spec.passed() && !failuresOnly) - print(color(' ' + spec.description, 'green') + assertionsGraph) - else if (!spec.passed()) - print(color(' ' + spec.description, 'red') + assertionsGraph + - "\n" + indent(map(spec.failures(), function(a){ return a.message }).join("\n")) + "\n") - }) - print("") - } - }) - - quit(results.stats.failures) - }, - - /** - * Console reporter. - * - * @api public - */ - - Console : function(results, options) { - console.log('') - console.log('Passes: ' + results.stats.passes + ' Failures: ' + results.stats.failures) - each(results.allSuites, function(suite) { - if (suite.ran) { - console.group(suite.description) - each(suite.specs, function(spec){ - var assertionCount = spec.assertions.length + ':' - if (spec.requiresImplementation()) - console.warn(spec.description) - else if (spec.passed()) - console.log(assertionCount + ' ' + spec.description) - else - console.error(assertionCount + ' ' + spec.description + ', ' + spec.failure().message) - }) - console.groupEnd() - } - }) - } - }, - - Assertion : function(matcher, actual, expected, negate) { - extend(this, { - message: '', - passed: false, - actual: actual, - negate: negate, - matcher: matcher, - expected: expected, - - // Report assertion results - - report : function() { - if (JSpec.assert) - this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++ - return this - }, - - // Run the assertion - - run : function() { - // TODO: remove unshifting - expected.unshift(actual) - this.result = matcher.match.apply(this, expected) - this.passed = negate ? !this.result : this.result - if (!this.passed) this.message = matcher.message.call(this, actual, expected, negate, matcher.name) - return this - } - }) - }, - - ProxyAssertion : function(object, method, times, negate) { - var self = this - var old = object[method] - - // Proxy - - object[method] = function(){ - args = toArray(arguments) - result = old.apply(object, args) - self.calls.push({ args : args, result : result }) - return result - } - - // Times - - this.times = { - once : 1, - twice : 2 - }[times] || times || 1 - - extend(this, { - calls: [], - message: '', - defer: true, - passed: false, - negate: negate, - object: object, - method: method, - - // Proxy return value - - and_return : function(result) { - this.expectedResult = result - return this - }, - - // Proxy arguments passed - - with_args : function() { - this.expectedArgs = toArray(arguments) - return this - }, - - // Check if any calls have failing results - - anyResultsFail : function() { - return any(this.calls, function(call){ - return self.expectedResult.an_instance_of ? - call.result.constructor != self.expectedResult.an_instance_of: - !equal(self.expectedResult, call.result) - }) - }, - - // Check if any calls have passing results - - anyResultsPass : function() { - return any(this.calls, function(call){ - return self.expectedResult.an_instance_of ? - call.result.constructor == self.expectedResult.an_instance_of: - equal(self.expectedResult, call.result) - }) - }, - - // Return the passing result - - passingResult : function() { - return this.anyResultsPass().result - }, - - // Return the failing result - - failingResult : function() { - return this.anyResultsFail().result - }, - - // Check if any arguments fail - - anyArgsFail : function() { - return any(this.calls, function(call){ - return any(self.expectedArgs, function(i, arg){ - if (arg == null) return call.args[i] == null - return arg.an_instance_of ? - call.args[i].constructor != arg.an_instance_of: - !equal(arg, call.args[i]) - - }) - }) - }, - - // Check if any arguments pass - - anyArgsPass : function() { - return any(this.calls, function(call){ - return any(self.expectedArgs, function(i, arg){ - return arg.an_instance_of ? - call.args[i].constructor == arg.an_instance_of: - equal(arg, call.args[i]) - - }) - }) - }, - - // Return the passing args - - passingArgs : function() { - return this.anyArgsPass().args - }, - - // Return the failing args - - failingArgs : function() { - return this.anyArgsFail().args - }, - - // Report assertion results - - report : function() { - if (JSpec.assert) - this.passed ? ++JSpec.stats.passes : ++JSpec.stats.failures - return this - }, - - // Run the assertion - - run : function() { - var methodString = 'expected ' + object.toString() + '.' + method + '()' + (negate ? ' not' : '' ) - - function times(n) { - return n > 2 ? n + ' times' : { 1: 'once', 2: 'twice' }[n] - } - - if (this.expectedResult != null && (negate ? this.anyResultsPass() : this.anyResultsFail())) - this.message = methodString + ' to return ' + puts(this.expectedResult) + - ' but ' + (negate ? 'it did' : 'got ' + puts(this.failingResult())) - - if (this.expectedArgs && (negate ? !this.expectedResult && this.anyArgsPass() : this.anyArgsFail())) - this.message = methodString + ' to be called with ' + puts.apply(this, this.expectedArgs) + - ' but was' + (negate ? '' : ' called with ' + puts.apply(this, this.failingArgs())) - - if (negate ? !this.expectedResult && !this.expectedArgs && this.calls.length >= this.times : this.calls.length != this.times) - this.message = methodString + ' to be called ' + times(this.times) + - ', but ' + (this.calls.length == 0 ? ' was not called' : ' was called ' + times(this.calls.length)) - - if (!this.message.length) - this.passed = true - - return this - } - }) - }, - - /** - * Specification Suite block object. - * - * @param {string} description - * @param {function} body - * @api private - */ - - Suite : function(description, body) { - var self = this - extend(this, { - body: body, - description: description, - suites: [], - specs: [], - ran: false, - hooks: { 'before' : [], 'after' : [], 'before_each' : [], 'after_each' : [] }, - - // Add a spec to the suite - - addSpec : function(description, body) { - var spec = new JSpec.Spec(description, body) - this.specs.push(spec) - JSpec.stats.specs++ // TODO: abstract - spec.suite = this - }, - - // Add a hook to the suite - - addHook : function(hook, body) { - this.hooks[hook].push(body) - }, - - // Add a nested suite - - addSuite : function(description, body) { - var suite = new JSpec.Suite(description, body) - JSpec.allSuites.push(suite) - suite.name = suite.description - suite.description = this.description + ' ' + suite.description - this.suites.push(suite) - suite.suite = this - }, - - // Invoke a hook in context to this suite - - hook : function(hook) { - if (this.suite) this.suite.hook(hook) - each(this.hooks[hook], function(body) { - JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" + self.description + "': ") - }) - }, - - // Check if nested suites are present - - hasSuites : function() { - return this.suites.length - }, - - // Check if this suite has specs - - hasSpecs : function() { - return this.specs.length - }, - - // Check if the entire suite passed - - passed : function() { - return !any(this.specs, function(spec){ - return !spec.passed() - }) - } - }) - }, - - /** - * Specification block object. - * - * @param {string} description - * @param {function} body - * @api private - */ - - Spec : function(description, body) { - extend(this, { - body: body, - description: description, - assertions: [], - - // Add passing assertion - - pass : function(message) { - this.assertions.push({ passed: true, message: message }) - if (JSpec.assert) ++JSpec.stats.passes - }, - - // Add failing assertion - - fail : function(message) { - this.assertions.push({ passed: false, message: message }) - if (JSpec.assert) ++JSpec.stats.failures - }, - - // Run deferred assertions - - runDeferredAssertions : function() { - each(this.assertions, function(assertion){ - if (assertion.defer) assertion.run().report(), hook('afterAssertion', assertion) - }) - }, - - // Find first failing assertion - - failure : function() { - return find(this.assertions, function(assertion){ - return !assertion.passed - }) - }, - - // Find all failing assertions - - failures : function() { - return select(this.assertions, function(assertion){ - return !assertion.passed - }) - }, - - // Weither or not the spec passed - - passed : function() { - return !this.failure() - }, - - // Weither or not the spec requires implementation (no assertions) - - requiresImplementation : function() { - return this.assertions.length == 0 - }, - - // Sprite based assertions graph - - assertionsGraph : function() { - return map(this.assertions, function(assertion){ - return '' - }).join('') - } - }) - }, - - Module : function(methods) { - extend(this, methods) - }, - - JSON : { - - /** - * Generic sequences. - */ - - meta : { - '\b' : '\\b', - '\t' : '\\t', - '\n' : '\\n', - '\f' : '\\f', - '\r' : '\\r', - '"' : '\\"', - '\\' : '\\\\' - }, - - /** - * Escapable sequences. - */ - - escapable : /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - - /** - * JSON encode _object_. - * - * @param {mixed} object - * @return {string} - * @api private - */ - - encode : function(object) { - var self = this - if (object == undefined || object == null) return 'null' - if (object === true) return 'true' - if (object === false) return 'false' - switch (typeof object) { - case 'number': return object - case 'string': return this.escapable.test(object) ? - '"' + object.replace(this.escapable, function (a) { - return typeof self.meta[a] === 'string' ? self.meta[a] : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4) - }) + '"' : - '"' + object + '"' - case 'object': - if (object.constructor == Array) - return '[' + map(object, function(val){ - return self.encode(val) - }).join(', ') + ']' - else if (object) - return '{' + map(object, function(key, val){ - return self.encode(key) + ':' + self.encode(val) - }).join(', ') + '}' - } - return 'null' - } - }, - - // --- DSLs - - DSLs : { - snake : { - expect : function(actual){ - return JSpec.expect(actual) - }, - - describe : function(description, body) { - return JSpec.currentSuite.addSuite(description, body) - }, - - it : function(description, body) { - return JSpec.currentSuite.addSpec(description, body) - }, - - before : function(body) { - return JSpec.currentSuite.addHook('before', body) - }, - - after : function(body) { - return JSpec.currentSuite.addHook('after', body) - }, - - before_each : function(body) { - return JSpec.currentSuite.addHook('before_each', body) - }, - - after_each : function(body) { - return JSpec.currentSuite.addHook('after_each', body) - }, - - should_behave_like : function(description) { - return JSpec.shareBehaviorsOf(description) - } - } - }, - - // --- Methods - - /** - * Check if _value_ is 'stop'. For use as a - * utility callback function. - * - * @param {mixed} value - * @return {bool} - * @api public - */ - - haveStopped : function(value) { - return value === 'stop' - }, - - /** - * Include _object_ which may be a hash or Module instance. - * - * @param {hash, Module} object - * @return {JSpec} - * @api public - */ - - include : function(object) { - var module = object.constructor == JSpec.Module ? object : new JSpec.Module(object) - this.modules.push(module) - if ('init' in module) module.init() - if ('utilities' in module) extend(this.defaultContext, module.utilities) - if ('matchers' in module) this.addMatchers(module.matchers) - if ('reporters' in module) extend(this.reporters, module.reporters) - if ('DSLs' in module) - each(module.DSLs, function(name, methods){ - JSpec.DSLs[name] = JSpec.DSLs[name] || {} - extend(JSpec.DSLs[name], methods) - }) - return this - }, - - /** - * Add a module hook _name_, which is immediately - * called per module with the _args_ given. An array of - * hook return values is returned. - * - * @param {name} string - * @param {...} args - * @return {array} - * @api private - */ - - hook : function(name, args) { - args = toArray(arguments, 1) - return inject(JSpec.modules, [], function(results, module){ - if (typeof module[name] == 'function') - results.push(JSpec.evalHook(module, name, args)) - }) - }, - - /** - * Eval _module_ hook _name_ with _args_. Evaluates in context - * to the module itself, JSpec, and JSpec.context. - * - * @param {Module} module - * @param {string} name - * @param {array} args - * @return {mixed} - * @api private - */ - - evalHook : function(module, name, args) { - hook('evaluatingHookBody', module, name) - try { return module[name].apply(module, args) } - catch(e) { error('Error in hook ' + module.name + '.' + name + ': ', e) } - }, - - /** - * Same as hook() however accepts only one _arg_ which is - * considered immutable. This function passes the arg - * to the first module, then passes the return value of the last - * module called, to the following module. - * - * @param {string} name - * @param {mixed} arg - * @return {mixed} - * @api private - */ - - hookImmutable : function(name, arg) { - return inject(JSpec.modules, arg, function(result, module){ - if (typeof module[name] == 'function') - return JSpec.evalHook(module, name, [result]) - }) - }, - - /** - * Find a suite by its description or name. - * - * @param {string} description - * @return {Suite} - * @api private - */ - - findSuite : function(description) { - return find(this.allSuites, function(suite){ - return suite.name == description || suite.description == description - }) - }, - - /** - * Share behaviors (specs) of the given suite with - * the current suite. - * - * @param {string} description - * @api public - */ - - shareBehaviorsOf : function(description) { - if (suite = this.findSuite(description)) this.copySpecs(suite, this.currentSuite) - else throw 'failed to share behaviors. ' + puts(description) + ' is not a valid Suite name' - }, - - /** - * Copy specs from one suite to another. - * - * @param {Suite} fromSuite - * @param {Suite} toSuite - * @api public - */ - - copySpecs : function(fromSuite, toSuite) { - each(fromSuite.specs, function(spec){ - spec.assertions = [] - toSuite.specs.push(spec) - }) - }, - - /** - * Convert arguments to an array. - * - * @param {object} arguments - * @param {int} offset - * @return {array} - * @api public - */ - - toArray : function(arguments, offset) { - return Array.prototype.slice.call(arguments, offset || 0) - }, - - /** - * Return ANSI-escaped colored string. - * - * @param {string} string - * @param {string} color - * @return {string} - * @api public - */ - - color : function(string, color) { - return "\u001B[" + { - bold : 1, - black : 30, - red : 31, - green : 32, - yellow : 33, - blue : 34, - magenta : 35, - cyan : 36, - white : 37 - }[color] + 'm' + string + "\u001B[0m" - }, - - /** - * Default matcher message callback. - * - * @api private - */ - - defaultMatcherMessage : function(actual, expected, negate, name) { - return 'expected ' + puts(actual) + ' to ' + - (negate ? 'not ' : '') + - name.replace(/_/g, ' ') + - ' ' + (expected.length > 1 ? - puts.apply(this, expected.slice(1)) : - '') - }, - - /** - * Normalize a matcher message. - * - * When no messge callback is present the defaultMatcherMessage - * will be assigned, will suffice for most matchers. - * - * @param {hash} matcher - * @return {hash} - * @api public - */ - - normalizeMatcherMessage : function(matcher) { - if (typeof matcher.message != 'function') - matcher.message = this.defaultMatcherMessage - return matcher - }, - - /** - * Normalize a matcher body - * - * This process allows the following conversions until - * the matcher is in its final normalized hash state. - * - * - '==' becomes 'actual == expected' - * - 'actual == expected' becomes 'return actual == expected' - * - function(actual, expected) { return actual == expected } becomes - * { match : function(actual, expected) { return actual == expected }} - * - * @param {mixed} body - * @return {hash} - * @api public - */ - - normalizeMatcherBody : function(body) { - switch (body.constructor) { - case String: - if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)] - if (body.length < 4) body = 'actual ' + body + ' expected' - return { match: function(actual, expected) { return eval(body) }} - - case Function: - return { match: body } - - default: - return body - } - }, - - /** - * Get option value. This method first checks if - * the option key has been set via the query string, - * otherwise returning the options hash value. - * - * @param {string} key - * @return {mixed} - * @api public - */ - - option : function(key) { - return (value = query(key)) !== null ? value : - JSpec.options[key] || null - }, - - /** - * Check if object _a_, is equal to object _b_. - * - * @param {object} a - * @param {object} b - * @return {bool} - * @api private - */ - - equal: function(a, b) { - if (typeof a != typeof b) return - if (a === b) return true - if (a instanceof RegExp) - return a.toString() === b.toString() - if (a instanceof Date) - return Number(a) === Number(b) - if (typeof a != 'object') return - if (a.length !== undefined) - if (a.length !== b.length) return - else - for (var i = 0, len = a.length; i < len; ++i) - if (!equal(a[i], b[i])) - return - for (var key in a) - if (!equal(a[key], b[key])) - return - return true - }, - - /** - * Return last element of an array. - * - * @param {array} array - * @return {object} - * @api public - */ - - last : function(array) { - return array[array.length - 1] - }, - - /** - * Convert object(s) to a print-friend string. - * - * @param {...} object - * @return {string} - * @api public - */ - - puts : function(object) { - if (arguments.length > 1) - return map(toArray(arguments), function(arg){ - return puts(arg) - }).join(', ') - if (object === undefined) return 'undefined' - if (object === null) return 'null' - if (object === true) return 'true' - if (object === false) return 'false' - if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name - if (object.jquery && object.selector.length > 0) return 'selector ' + puts(object.selector) - if (object.jquery) return object.get(0).outerHTML - if (object.nodeName) return object.outerHTML - switch (object.constructor) { - case Function: return object.name || object - case String: - return '"' + object - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\t/g, '\\t') - + '"' - case Array: - return inject(object, '[', function(b, v){ - return b + ', ' + puts(v) - }).replace('[,', '[') + ' ]' - case Object: - object.__hit__ = true - return inject(object, '{', function(b, k, v) { - if (k == '__hit__') return b - return b + ', ' + k + ': ' + (v && v.__hit__ ? '' : puts(v)) - }).replace('{,', '{') + ' }' - default: - return object.toString() - } - }, - - /** - * Escape HTML. - * - * @param {string} html - * @return {string} - * @api public - */ - - escape : function(html) { - return html.toString() - .replace(/&/gmi, '&') - .replace(/"/gmi, '"') - .replace(/>/gmi, '>') - .replace(/ current) while (++current <= end) values.push(current) - else while (--current >= end) values.push(current) - return '[' + values + ']' - }, - - /** - * Report on the results. - * - * @api public - */ - - report : function() { - this.duration = Number(new Date) - this.start - hook('reporting', JSpec.options) - new (JSpec.options.reporter || JSpec.reporters.DOM)(JSpec, JSpec.options) - }, - - /** - * Run the spec suites. Options are merged - * with JSpec options when present. - * - * @param {hash} options - * @return {JSpec} - * @api public - */ - - run : function(options) { - if (any(hook('running'), haveStopped)) return this - if (options) extend(this.options, options) - this.start = Number(new Date) - each(this.suites, function(suite) { JSpec.runSuite(suite) }) - return this - }, - - /** - * Run a suite. - * - * @param {Suite} suite - * @api public - */ - - runSuite : function(suite) { - this.currentSuite = suite - this.evalBody(suite.body) - suite.ran = true - hook('beforeSuite', suite), suite.hook('before') - each(suite.specs, function(spec) { - hook('beforeSpec', spec) - suite.hook('before_each') - JSpec.runSpec(spec) - hook('afterSpec', spec) - suite.hook('after_each') - }) - if (suite.hasSuites()) { - each(suite.suites, function(suite) { - JSpec.runSuite(suite) - }) - } - hook('afterSuite', suite), suite.hook('after') - this.stats.suitesFinished++ - }, - - /** - * Report a failure for the current spec. - * - * @param {string} message - * @api public - */ - - fail : function(message) { - JSpec.currentSpec.fail(message) - }, - - /** - * Report a passing assertion for the current spec. - * - * @param {string} message - * @api public - */ - - pass : function(message) { - JSpec.currentSpec.pass(message) - }, - - /** - * Run a spec. - * - * @param {Spec} spec - * @api public - */ - - runSpec : function(spec) { - this.currentSpec = spec - try { this.evalBody(spec.body) } - catch (e) { fail(e) } - spec.runDeferredAssertions() - destub() - this.stats.specsFinished++ - this.stats.assertions += spec.assertions.length - }, - - /** - * Require a dependency, with optional message. - * - * @param {string} dependency - * @param {string} message (optional) - * @return {JSpec} - * @api public - */ - - requires : function(dependency, message) { - hook('requiring', dependency, message) - try { eval(dependency) } - catch (e) { throw 'JSpec depends on ' + dependency + ' ' + message } - return this - }, - - /** - * Query against the current query strings keys - * or the queryString specified. - * - * @param {string} key - * @param {string} queryString - * @return {string, null} - * @api private - */ - - query : function(key, queryString) { - var queryString = (queryString || (main.location ? main.location.search : null) || '').substring(1) - return inject(queryString.split('&'), null, function(value, pair){ - parts = pair.split('=') - return parts[0] == key ? parts[1].replace(/%20|\+/gmi, ' ') : value - }) - }, - - /** - * Throw a JSpec related error. - * - * @param {string} message - * @param {Exception} e - * @api public - */ - - error : function(message, e) { - throw (message ? message : '') + e.toString() + - (e.line ? ' near line ' + e.line : '') - }, - - /** - * Ad-hoc POST request for JSpec server usage. - * - * @param {string} uri - * @param {string} data - * @api private - */ - - post : function(uri, data) { - if (any(hook('posting', uri, data), haveStopped)) return - var request = this.xhr() - request.open('POST', uri, false) - request.setRequestHeader('Content-Type', 'application/json') - request.send(JSpec.JSON.encode(data)) - }, - - /** - * Instantiate an XMLHttpRequest. - * - * Here we utilize IE's lame ActiveXObjects first which - * allow IE access serve files via the file: protocol, otherwise - * we then default to XMLHttpRequest. - * - * @return {XMLHttpRequest, ActiveXObject} - * @api private - */ - - xhr : function() { - return this.ieXhr() || new JSpec.request - }, - - /** - * Return Microsoft piece of crap ActiveXObject. - * - * @return {ActiveXObject} - * @api public - */ - - ieXhr : function() { - function object(str) { - try { return new ActiveXObject(str) } catch(e) {} - } - return object('Msxml2.XMLHTTP.6.0') || - object('Msxml2.XMLHTTP.3.0') || - object('Msxml2.XMLHTTP') || - object('Microsoft.XMLHTTP') - }, - - /** - * Check for HTTP request support. - * - * @return {bool} - * @api private - */ - - hasXhr : function() { - return JSpec.request || 'ActiveXObject' in main - }, - - /** - * Try loading _file_ returning the contents - * string or null. Chain to locate / read a file. - * - * @param {string} file - * @return {string} - * @api public - */ - - tryLoading : function(file) { - try { return JSpec.load(file) } catch (e) {} - }, - - /** - * Load a _file_'s contents. - * - * @param {string} file - * @param {function} callback - * @return {string} - * @api public - */ - - load : function(file, callback) { - if (any(hook('loading', file), haveStopped)) return - if ('readFile' in main) - return readFile(file) - else if (this.hasXhr()) { - var request = this.xhr() - request.open('GET', file, false) - request.send(null) - if (request.readyState == 4 && - (request.status == 0 || - request.status.toString().charAt(0) == 2)) - return request.responseText - } - else - error("failed to load `" + file + "'") - }, - - /** - * Load, pre-process, and evaluate a file. - * - * @param {string} file - * @param {JSpec} - * @api public - */ - - exec : function(file) { - if (any(hook('executing', file), haveStopped)) return this - eval('with (JSpec){' + this.preprocess(this.load(file)) + '}') - return this - } - } - - // --- Utility functions - - var main = this - var find = JSpec.any - var utils = 'haveStopped stub hookImmutable hook destub map any last pass fail range each option inject select \ - error escape extend puts query strip color does addMatchers callIterator toArray equal'.split(/\s+/) - while (utils.length) eval('var ' + utils[0] + ' = JSpec.' + utils.shift()) - if (!main.setTimeout) main.setTimeout = function(callback){ callback() } - - // --- Matchers - - addMatchers({ - equal : "===", - eql : "equal(actual, expected)", - be : "alias equal", - be_greater_than : ">", - be_less_than : "<", - be_at_least : ">=", - be_at_most : "<=", - be_a : "actual.constructor == expected", - be_an : "alias be_a", - be_an_instance_of : "actual instanceof expected", - be_null : "actual == null", - be_true : "actual == true", - be_false : "actual == false", - be_undefined : "typeof actual == 'undefined'", - be_type : "typeof actual == expected", - match : "typeof actual == 'string' ? actual.match(expected) : false", - respond_to : "typeof actual[expected] == 'function'", - have_length : "actual.length == expected", - be_within : "actual >= expected[0] && actual <= last(expected)", - have_length_within : "actual.length >= expected[0] && actual.length <= last(expected)", - - receive : { defer : true, match : function(actual, method, times) { - proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate) - JSpec.currentSpec.assertions.push(proxy) - return proxy - }}, - - be_empty : function(actual) { - if (actual.constructor == Object && actual.length == undefined) - for (var key in actual) - return false; - return !actual.length - }, - - include : function(actual) { - for (state = true, i = 1; i < arguments.length; i++) { - arg = arguments[i] - switch (actual.constructor) { - case String: - case Number: - case RegExp: - case Function: - state = actual.toString().indexOf(arg) !== -1 - break - - case Object: - state = arg in actual - break - - case Array: - state = any(actual, function(value){ return equal(value, arg) }) - break - } - if (!state) return false - } - return true - }, - - throw_error : { match : function(actual, expected, message) { - try { actual() } - catch (e) { - this.e = e - var assert = function(arg) { - switch (arg.constructor) { - case RegExp : return arg.test(e.message || e.toString()) - case String : return arg == (e.message || e.toString()) - case Function : return e instanceof arg || e.name == arg.name - } - } - return message ? assert(expected) && assert(message) : - expected ? assert(expected) : - true - } - }, message : function(actual, expected, negate) { - // TODO: refactor when actual is not in expected [0] - var message_for = function(i) { - if (expected[i] == undefined) return 'exception' - switch (expected[i].constructor) { - case RegExp : return 'exception matching ' + puts(expected[i]) - case String : return 'exception of ' + puts(expected[i]) - case Function : return expected[i].name || 'Error' - } - } - exception = message_for(1) + (expected[2] ? ' and ' + message_for(2) : '') - return 'expected ' + exception + (negate ? ' not ' : '' ) + - ' to be thrown, but ' + (this.e ? 'got ' + puts(this.e) : 'nothing was') - }}, - - have : function(actual, length, property) { - return actual[property].length == length - }, - - have_at_least : function(actual, length, property) { - return actual[property].length >= length - }, - - have_at_most :function(actual, length, property) { - return actual[property].length <= length - }, - - have_within : function(actual, range, property) { - length = actual[property].length - return length >= range.shift() && length <= range.pop() - }, - - have_prop : function(actual, property, value) { - return actual[property] == null || - actual[property] instanceof Function ? false: - value == null ? true: - does(actual[property], 'eql', value) - }, - - have_property : function(actual, property, value) { - return actual[property] == null || - actual[property] instanceof Function ? false: - value == null ? true: - value === actual[property] - } - }) - -})() diff --git a/spec/lib/jspec.shell.js b/spec/lib/jspec.shell.js deleted file mode 100644 index cb19c69..0000000 --- a/spec/lib/jspec.shell.js +++ /dev/null @@ -1,39 +0,0 @@ - -// JSpec - Shell - Copyright TJ Holowaychuk (MIT Licensed) - -;(function(){ - - var _quit = quit - - Shell = { - - // --- Global - - main: this, - - // --- Commands - - commands: { - quit: ['Terminate the shell', function(){ _quit() }], - exit: ['Terminate the shell', function(){ _quit() }], - p: ['Inspect an object', function(o){ return o.toSource() }] - }, - - /** - * Start the interactive shell. - * - * @api public - */ - - start : function() { - for (var name in this.commands) - if (this.commands.hasOwnProperty(name)) - this.commands[name][1].length ? - this.main[name] = this.commands[name][1] : - this.main.__defineGetter__(name, this.commands[name][1]) - } - } - - Shell.start() - -})() \ No newline at end of file diff --git a/spec/lib/jspec.timers.js b/spec/lib/jspec.timers.js deleted file mode 100644 index c88d10b..0000000 --- a/spec/lib/jspec.timers.js +++ /dev/null @@ -1,90 +0,0 @@ - -// JSpec - Mock Timers - Copyright TJ Holowaychuk (MIT Licensed) - -;(function(){ - - /** - * Version. - */ - - mockTimersVersion = '1.0.2' - - /** - * Localized timer stack. - */ - - var timers = [] - - /** - * Set mock timeout with _callback_ and timeout of _ms_. - * - * @param {function} callback - * @param {int} ms - * @return {int} - * @api public - */ - - setTimeout = function(callback, ms) { - var id - return id = setInterval(function(){ - callback() - clearInterval(id) - }, ms) - } - - /** - * Set mock interval with _callback_ and interval of _ms_. - * - * @param {function} callback - * @param {int} ms - * @return {int} - * @api public - */ - - setInterval = function(callback, ms) { - callback.step = ms, callback.current = callback.last = 0 - return timers[timers.length] = callback, timers.length - } - - /** - * Destroy timer with _id_. - * - * @param {int} id - * @return {bool} - * @api public - */ - - clearInterval = clearTimeout = function(id) { - return delete timers[--id] - } - - /** - * Reset timers. - * - * @return {array} - * @api public - */ - - resetTimers = function() { - return timers = [] - } - - /** - * Increment each timers internal clock by _ms_. - * - * @param {int} ms - * @api public - */ - - tick = function(ms) { - for (var i = 0, len = timers.length; i < len; ++i) - if (timers[i] && (timers[i].current += ms)) - if (timers[i].current - timers[i].last >= timers[i].step) { - var times = Math.floor((timers[i].current - timers[i].last) / timers[i].step) - var remainder = (timers[i].current - timers[i].last) % timers[i].step - timers[i].last = timers[i].current - remainder - while (times-- && timers[i]) timers[i]() - } - } - -})() \ No newline at end of file diff --git a/spec/lib/jspec.xhr.js b/spec/lib/jspec.xhr.js deleted file mode 100644 index 906e4c5..0000000 --- a/spec/lib/jspec.xhr.js +++ /dev/null @@ -1,193 +0,0 @@ - -// JSpec - XHR - Copyright TJ Holowaychuk (MIT Licensed) - -(function(){ - - // --- Original XMLHttpRequest - - var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ? - XMLHttpRequest : - function(){} - var OriginalActiveXObject = 'ActiveXObject' in this ? - ActiveXObject : - undefined - - // --- MockXMLHttpRequest - - var MockXMLHttpRequest = function() { - this.requestHeaders = {} - } - - MockXMLHttpRequest.prototype = { - status: 0, - async: true, - readyState: 0, - responseText: '', - abort: function(){}, - onreadystatechange: function(){}, - - /** - * Return response headers hash. - */ - - getAllResponseHeaders : function(){ - return this.responseHeaders - }, - - /** - * Return case-insensitive value for header _name_. - */ - - getResponseHeader : function(name) { - return this.responseHeaders[name.toLowerCase()] - }, - - /** - * Set case-insensitive _value_ for header _name_. - */ - - setRequestHeader : function(name, value) { - this.requestHeaders[name.toLowerCase()] = value - }, - - /** - * Open mock request. - */ - - open : function(method, url, async, user, password) { - this.user = user - this.password = password - this.url = url - this.readyState = 1 - this.method = method.toUpperCase() - if (async != undefined) this.async = async - if (this.async) this.onreadystatechange() - }, - - /** - * Send request _data_. - */ - - send : function(data) { - var self = this - this.data = data - this.readyState = 4 - if (this.method == 'HEAD') this.responseText = null - this.responseHeaders['content-length'] = (this.responseText || '').length - if(this.async) this.onreadystatechange() - lastRequest = function(){ - return self - } - } - } - - // --- Response status codes - - JSpec.statusCodes = { - 100: 'Continue', - 101: 'Switching Protocols', - 200: 'OK', - 201: 'Created', - 202: 'Accepted', - 203: 'Non-Authoritative Information', - 204: 'No Content', - 205: 'Reset Content', - 206: 'Partial Content', - 300: 'Multiple Choice', - 301: 'Moved Permanently', - 302: 'Found', - 303: 'See Other', - 304: 'Not Modified', - 305: 'Use Proxy', - 307: 'Temporary Redirect', - 400: 'Bad Request', - 401: 'Unauthorized', - 402: 'Payment Required', - 403: 'Forbidden', - 404: 'Not Found', - 405: 'Method Not Allowed', - 406: 'Not Acceptable', - 407: 'Proxy Authentication Required', - 408: 'Request Timeout', - 409: 'Conflict', - 410: 'Gone', - 411: 'Length Required', - 412: 'Precondition Failed', - 413: 'Request Entity Too Large', - 414: 'Request-URI Too Long', - 415: 'Unsupported Media Type', - 416: 'Requested Range Not Satisfiable', - 417: 'Expectation Failed', - 422: 'Unprocessable Entity', - 500: 'Internal Server Error', - 501: 'Not Implemented', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - 504: 'Gateway Timeout', - 505: 'HTTP Version Not Supported' - } - - /** - * Mock XMLHttpRequest requests. - * - * mockRequest().and_return('some data', 'text/plain', 200, { 'X-SomeHeader' : 'somevalue' }) - * - * @return {hash} - * @api public - */ - - function mockRequest() { - return { and_return : function(body, type, status, headers) { - XMLHttpRequest = MockXMLHttpRequest - ActiveXObject = false - status = status || 200 - headers = headers || {} - headers['content-type'] = type - JSpec.extend(XMLHttpRequest.prototype, { - responseText: body, - responseHeaders: headers, - status: status, - statusText: JSpec.statusCodes[status] - }) - }} - } - - /** - * Unmock XMLHttpRequest requests. - * - * @api public - */ - - function unmockRequest() { - XMLHttpRequest = OriginalXMLHttpRequest - ActiveXObject = OriginalActiveXObject - } - - JSpec.include({ - name: 'Mock XHR', - - // --- Utilities - - utilities : { - mockRequest: mockRequest, - unmockRequest: unmockRequest - }, - - // --- Hooks - - afterSpec : function() { - unmockRequest() - }, - - // --- DSLs - - DSLs : { - snake : { - mock_request: mockRequest, - unmock_request: unmockRequest, - last_request: function(){ return lastRequest() } - } - } - - }) -})() \ No newline at end of file diff --git a/spec/spec.logging.js b/spec/spec.logging.js deleted file mode 100644 index 4e6f923..0000000 --- a/spec/spec.logging.js +++ /dev/null @@ -1,113 +0,0 @@ -describe 'log4js' - before - extend(context, { - log4js : require("log4js")() - }); - end - - before_each - log4js.clearAppenders(); - event = ''; - logger = log4js.getLogger('tests'); - logger.setLevel("TRACE"); - logger.addListener("log", function (logEvent) { event = logEvent; }); - end - - describe 'addAppender' - before_each - appenderEvent = undefined; - appender = function(logEvent) { appenderEvent = logEvent; }; - end - - describe 'without a category' - it 'should register the function as a listener for all loggers' - log4js.addAppender(appender); - logger.debug("This is a test"); - appenderEvent.should.be event - end - - it 'should also register as an appender for loggers if an appender for that category is defined' - var otherEvent; - log4js.addAppender(appender); - log4js.addAppender(function (evt) { otherEvent = evt; }, 'cheese'); - - var cheeseLogger = log4js.getLogger('cheese'); - cheeseLogger.addListener("log", function (logEvent) { event = logEvent; }); - - cheeseLogger.debug('This is a test'); - - appenderEvent.should.be event - otherEvent.should.be event - - otherEvent = undefined; - appenderEvent = undefined; - log4js.getLogger('pants').debug("this should not be propagated to otherEvent"); - otherEvent.should.be undefined - appenderEvent.should.not.be undefined - appenderEvent.message.should.be "this should not be propagated to otherEvent" - - cheeseLogger = null; - end - end - - describe 'with a category' - it 'should only register the function as a listener for that category' - log4js.addAppender(appender, 'tests'); - - logger.debug('this is a test'); - appenderEvent.should.be event - - appenderEvent = undefined; - log4js.getLogger('some other category').debug('Cheese'); - appenderEvent.should.be undefined - end - end - - describe 'with multiple categories' - it 'should register the function as a listener for all the categories' - log4js.addAppender(appender, 'tests', 'biscuits'); - - logger.debug('this is a test'); - appenderEvent.should.be event - appenderEvent = undefined; - - var otherLogger = log4js.getLogger('biscuits'); - otherLogger.debug("mmm... garibaldis"); - appenderEvent.should.not.be undefined - appenderEvent.message.should.be "mmm... garibaldis" - appenderEvent = undefined; - - otherLogger = null; - - log4js.getLogger("something else").debug("pants"); - appenderEvent.should.be undefined - end - - it 'should register the function when the list of categories is an array' - log4js.addAppender(appender, ['tests', 'pants']); - - logger.debug('this is a test'); - appenderEvent.should.be event - appenderEvent = undefined; - - var otherLogger = log4js.getLogger('pants'); - otherLogger.debug("big pants"); - appenderEvent.should.not.be undefined - appenderEvent.message.should.be "big pants" - appenderEvent = undefined; - - otherLogger = null; - - log4js.getLogger("something else").debug("pants"); - appenderEvent.should.be undefined - end - end - end - - - - - - -end - diff --git a/test/logging.js b/test/logging.js index 92d674e..3be6d0a 100644 --- a/test/logging.js +++ b/test/logging.js @@ -244,6 +244,91 @@ vows.describe('log4js').addBatch({ } }, + 'addAppender' : { + topic: function() { + var log4js = require('../lib/log4js')(); + log4js.clearAppenders(); + return log4js; + }, + 'without a category': { + 'should register the function as a listener for all loggers': function (log4js) { + var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger("tests"); + log4js.addAppender(appender); + logger.debug("This is a test"); + assert.equal(appenderEvent.message, "This is a test"); + assert.equal(appenderEvent.categoryName, "tests"); + assert.equal(appenderEvent.level.toString(), "DEBUG"); + }, + 'should also register as an appender for loggers if an appender for that category is defined': function (log4js) { + var otherEvent, appenderEvent, cheeseLogger; + log4js.addAppender(function (evt) { appenderEvent = evt; }); + log4js.addAppender(function (evt) { otherEvent = evt; }, 'cheese'); + + cheeseLogger = log4js.getLogger('cheese'); + cheeseLogger.debug('This is a test'); + assert.deepEqual(appenderEvent, otherEvent); + assert.equal(otherEvent.message, 'This is a test'); + assert.equal(otherEvent.categoryName, 'cheese'); + + otherEvent = undefined; + appenderEvent = undefined; + log4js.getLogger('pants').debug("this should not be propagated to otherEvent"); + assert.isUndefined(otherEvent); + assert.equal(appenderEvent.message, "this should not be propagated to otherEvent"); + } + }, + + 'with a category': { + 'should only register the function as a listener for that category': function(log4js) { + var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger("tests"); + log4js.addAppender(appender, 'tests'); + logger.debug('this is a category test'); + assert.equal(appenderEvent.message, 'this is a category test'); + + appenderEvent = undefined; + log4js.getLogger('some other category').debug('Cheese'); + assert.isUndefined(appenderEvent); + } + }, + + 'with multiple categories': { + 'should register the function as a listener for all the categories': function(log4js) { + var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger('tests'); + log4js.addAppender(appender, 'tests', 'biscuits'); + + logger.debug('this is a test'); + assert.equal(appenderEvent.message, 'this is a test'); + appenderEvent = undefined; + + var otherLogger = log4js.getLogger('biscuits'); + otherLogger.debug("mmm... garibaldis"); + assert.equal(appenderEvent.message, "mmm... garibaldis"); + + appenderEvent = undefined; + + log4js.getLogger("something else").debug("pants"); + assert.isUndefined(appenderEvent); + }, + 'should register the function when the list of categories is an array': function(log4js) { + var appenderEvent, appender = function(evt) { appenderEvent = evt; }; + log4js.addAppender(appender, ['tests', 'pants']); + + log4js.getLogger('tests').debug('this is a test'); + assert.equal(appenderEvent.message, 'this is a test'); + + appenderEvent = undefined; + + log4js.getLogger('pants').debug("big pants"); + assert.equal(appenderEvent.message, "big pants"); + + appenderEvent = undefined; + + log4js.getLogger("something else").debug("pants"); + assert.isUndefined(appenderEvent); + } + } + }, + 'default setup': { topic: function() { var pathsChecked = [], diff --git a/tests.js b/tests.js deleted file mode 100644 index 3090813..0000000 --- a/tests.js +++ /dev/null @@ -1,43 +0,0 @@ -require.paths.unshift("./spec/lib", "./lib"); -require("jspec"); - -var sys = require("sys"), fs = require("fs"); - -quit = process.exit -print = sys.puts - -readFile = function(path) { - var result; - try { - result = fs.readFileSync(path, "utf8"); - } catch (e) { - throw e; - } - return result; -} - -var specsFound = false; - -if (process.ARGV[2]) { - specsFound = true; - JSpec.exec('spec/spec.' + process.ARGV[2] + '.js'); -} else { - var files = fs.readdirSync('spec/'); - files.filter( - function (file) { - return file.indexOf('spec.') === 0; - } - ).forEach( - function(file) { - specsFound = true; - JSpec.exec('spec/'+file); - } - ); -} -if (specsFound) { - JSpec.run({ reporter: JSpec.reporters.Terminal, failuresOnly: false }); - JSpec.report(); -} else { - print("No tests to run. This makes me sad."); -} - From c2f9ccce730fced579e6e51fb35878b1dfb7ba69 Mon Sep 17 00:00:00 2001 From: csausdev Date: Wed, 8 Dec 2010 08:53:59 +1100 Subject: [PATCH 18/25] enhanced console.log --- README.md | 5 ++++- lib/log4js.js | 28 +++++++++++++++++++++++----- package.json | 2 +- test/logging.js | 33 ++++++++++++++++++++++++++++++++- 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e423020..a5b6ba2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 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 -and tidied up some of the javascript. It includes a basic file logger, with log rolling based on file size. +and tidied up some of the javascript. It includes a basic file logger, with log rolling based on file size. It also enhances the default console logging functions (console.log, console.debug, etc) so that they use log4js and can be directed to a file, with log rolling etc - which is handy if you have some third party modules that use console.log but want that output included in your application log files. 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). @@ -20,6 +20,9 @@ Minimalist version: var log4js = require('log4js')(); var logger = log4js.getLogger(); logger.debug("Some debug messages"); +Even more minimalist version: + require('log4js')(); + console.debug("Some debug messages"); By default, log4js outputs to stdout with the coloured layout (thanks to [masylum](http://github.com/masylum)), so for the above you would see: [2010-01-17 11:43:37.987] [DEBUG] [default] - Some debug messages diff --git a/lib/log4js.js b/lib/log4js.js index 5ef7f4f..2a0e0b1 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -45,12 +45,12 @@ * Website: http://log4js.berlios.de */ module.exports = function (fileSystem, standardOutput, configPaths) { - var fs = fileSystem || require('fs'), - standardOutput = standardOutput || console.log, - configPaths = configPaths || require.paths, - sys = require('sys'), - events = require('events'), + var events = require('events'), path = require('path'), + sys = require('sys'), + fs = fileSystem || require('fs'), + standardOutput = standardOutput || sys.puts, + configPaths = configPaths || require.paths, DEFAULT_CATEGORY = '[default]', ALL_CATEGORIES = '[all]', loggers = {}, @@ -614,8 +614,26 @@ module.exports = function (fileSystem, standardOutput, configPaths) { }; + function replaceConsole(logger) { + function replaceWith (fn) { + return function() { + fn.apply(logger, arguments); + } + } + + console.log = replaceWith(logger.info); + console.debug = replaceWith(logger.debug); + console.trace = replaceWith(logger.trace); + console.info = replaceWith(logger.info); + console.warn = replaceWith(logger.warn); + console.error = replaceWith(logger.error); + + } + //set ourselves up if we can find a default log4js.json configure(findConfiguration()); + //replace console.log, etc with log4js versions + replaceConsole(getLogger("console")); return { getLogger: getLogger, diff --git a/package.json b/package.json index e5d10f8..3ad42ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.2.0", + "version": "0.2.1", "description": "Port of Log4js to work with node.", "keywords": [ "logging", diff --git a/test/logging.js b/test/logging.js index 3be6d0a..0d48492 100644 --- a/test/logging.js +++ b/test/logging.js @@ -482,7 +482,7 @@ vows.describe('log4js').addBatch({ 'Date extensions': { topic: function() { - require('../lib/log4js'); + require('../lib/log4js')(); return new Date(2010, 0, 11, 14, 31, 30, 5); }, 'should add a toFormattedString method to Date': function(date) { @@ -491,6 +491,37 @@ vows.describe('log4js').addBatch({ 'should default to a format': function(date) { assert.equal(date.toFormattedString(), '2010-01-11 14:31:30.005'); } + }, + + 'console' : { + topic: function() { + return require('../lib/log4js')(); + }, + 'should replace console.log methods with log4js ones': function(log4js) { + var logEvent; + log4js.clearAppenders(); + log4js.addAppender(function(evt) { logEvent = evt; }); + + console.log("Some debug message someone put in a module"); + assert.equal(logEvent.message, "Some debug message someone put in a module"); + assert.equal(logEvent.level.toString(), "INFO"); + logEvent = undefined; + console.debug("Some debug"); + assert.equal(logEvent.message, "Some debug"); + assert.equal(logEvent.level.toString(), "DEBUG"); + logEvent = undefined; + console.error("An error"); + assert.equal(logEvent.message, "An error"); + assert.equal(logEvent.level.toString(), "ERROR"); + logEvent = undefined; + console.info("some info"); + assert.equal(logEvent.message, "some info"); + assert.equal(logEvent.level.toString(), "INFO"); + logEvent = undefined; + console.trace("tracing"); + assert.equal(logEvent.message, "tracing"); + assert.equal(logEvent.level.toString(), "TRACE"); + } } }).export(module); From c87028992814683c293dc9b5b8c6ad709148db87 Mon Sep 17 00:00:00 2001 From: csausdev Date: Sat, 11 Dec 2010 21:55:21 +1100 Subject: [PATCH 19/25] now handles exceptions that aren't Errors --- lib/log4js.js | 6 +++++- test/logging.js | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/log4js.js b/lib/log4js.js index 2a0e0b1..a840be2 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -279,9 +279,13 @@ module.exports = function (fileSystem, standardOutput, configPaths) { this.startTime = new Date(); this.categoryName = categoryName; this.message = message; - this.exception = exception; this.level = level; this.logger = logger; + if (exception && exception.message && exception.name) { + this.exception = exception; + } else if (exception) { + this.exception = new Error(exception); + } } /** diff --git a/test/logging.js b/test/logging.js index 0d48492..8e15e1d 100644 --- a/test/logging.js +++ b/test/logging.js @@ -29,6 +29,8 @@ vows.describe('log4js').addBatch({ logger.trace("Trace event 1"); logger.trace("Trace event 2"); logger.warn("Warning event"); + logger.error("Aargh!", new Error("Pants are on fire!")); + logger.error("Simulated CouchDB problem", JSON.stringify({ err: 127, cause: "incendiary underwear" })); return events; }, @@ -39,9 +41,20 @@ vows.describe('log4js').addBatch({ }, 'should not emit events of a lower level': function(events) { - assert.length(events, 2); + assert.length(events, 4); assert.equal(events[1].level.toString(), 'WARN'); - } + }, + + 'should include the error if passed in': function (events) { + assert.instanceOf(events[2].exception, Error); + assert.equal(events[2].exception.message, 'Pants are on fire!'); + }, + + 'should convert things that claim to be errors into Error objects': function (events) { + assert.instanceOf(events[3].exception, Error); + assert.equal(events[3].exception.message, '{"err":127,"cause":"incendiary underwear"}'); + }, + }, }, From 612d9eeb2332ccec271ad56f90411293e18ce19c Mon Sep 17 00:00:00 2001 From: csausdev Date: Sat, 11 Dec 2010 21:59:50 +1100 Subject: [PATCH 20/25] small tweak to exception handling --- lib/log4js.js | 2 +- package.json | 2 +- test/logging.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/log4js.js b/lib/log4js.js index a840be2..8e88217 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -284,7 +284,7 @@ module.exports = function (fileSystem, standardOutput, configPaths) { if (exception && exception.message && exception.name) { this.exception = exception; } else if (exception) { - this.exception = new Error(exception); + this.exception = new Error(sys.inspect(exception)); } } diff --git a/package.json b/package.json index 3ad42ae..b2eb3e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.2.1", + "version": "0.2.2", "description": "Port of Log4js to work with node.", "keywords": [ "logging", diff --git a/test/logging.js b/test/logging.js index 8e15e1d..0e4cc41 100644 --- a/test/logging.js +++ b/test/logging.js @@ -30,7 +30,7 @@ vows.describe('log4js').addBatch({ logger.trace("Trace event 2"); logger.warn("Warning event"); logger.error("Aargh!", new Error("Pants are on fire!")); - logger.error("Simulated CouchDB problem", JSON.stringify({ err: 127, cause: "incendiary underwear" })); + logger.error("Simulated CouchDB problem", { err: 127, cause: "incendiary underwear" }); return events; }, @@ -52,7 +52,7 @@ vows.describe('log4js').addBatch({ 'should convert things that claim to be errors into Error objects': function (events) { assert.instanceOf(events[3].exception, Error); - assert.equal(events[3].exception.message, '{"err":127,"cause":"incendiary underwear"}'); + assert.equal(events[3].exception.message, "{ err: 127, cause: 'incendiary underwear' }"); }, }, From c6dd2398ab73f01436efcbd532a00012848493d5 Mon Sep 17 00:00:00 2001 From: csausdev Date: Sun, 16 Jan 2011 13:05:13 +1100 Subject: [PATCH 21/25] Persist logging config across invocations --- lib/log4js.js | 941 ++++++++++++++++++++++++------------------------ test/logging.js | 28 +- 2 files changed, 498 insertions(+), 471 deletions(-) diff --git a/lib/log4js.js b/lib/log4js.js index 8e88217..4a2dbfd 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -44,299 +44,480 @@ * @static * Website: http://log4js.berlios.de */ -module.exports = function (fileSystem, standardOutput, configPaths) { - var events = require('events'), - path = require('path'), - sys = require('sys'), - fs = fileSystem || require('fs'), - standardOutput = standardOutput || sys.puts, - configPaths = configPaths || require.paths, - DEFAULT_CATEGORY = '[default]', - ALL_CATEGORIES = '[all]', - loggers = {}, - appenders = {}, - levels = { - ALL: new Level(Number.MIN_VALUE, "ALL", "grey"), - TRACE: new Level(5000, "TRACE", "blue"), - DEBUG: new Level(10000, "DEBUG", "cyan"), - INFO: new Level(20000, "INFO", "green"), - WARN: new Level(30000, "WARN", "yellow"), - ERROR: new Level(40000, "ERROR", "red"), - FATAL: new Level(50000, "FATAL", "magenta"), - OFF: new Level(Number.MAX_VALUE, "OFF", "grey") +var events = require('events'), +path = require('path'), +sys = require('sys'), +DEFAULT_CATEGORY = '[default]', +ALL_CATEGORIES = '[all]', +appenders = {}, +loggers = {}, +levels = { + ALL: new Level(Number.MIN_VALUE, "ALL", "grey"), + TRACE: new Level(5000, "TRACE", "blue"), + DEBUG: new Level(10000, "DEBUG", "cyan"), + INFO: new Level(20000, "INFO", "green"), + WARN: new Level(30000, "WARN", "yellow"), + ERROR: new Level(40000, "ERROR", "red"), + FATAL: new Level(50000, "FATAL", "magenta"), + OFF: new Level(Number.MAX_VALUE, "OFF", "grey") +}, +appenderMakers = { + "file": function(config, fileAppender) { + var layout; + if (config.layout) { + layout = layoutMakers[config.layout.type](config.layout); + } + return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.pollInterval); }, - appenderMakers = { - "file": function(config) { - var layout; - if (config.layout) { - layout = layoutMakers[config.layout.type](config.layout); - } - return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.pollInterval); - }, - "console": function(config) { - var layout; - if (config.layout) { - layout = layoutMakers[config.layout.type](config.layout); - } - return consoleAppender(layout); - }, - "logLevelFilter": function(config) { - var appender = appenderMakers[config.appender.type](config.appender); - return logLevelFilter(config.level, appender); + "console": function(config, fileAppender, consoleAppender) { + var layout; + if (config.layout) { + layout = layoutMakers[config.layout.type](config.layout); } + return consoleAppender(layout); }, - layoutMakers = { - "messagePassThrough": function() { return messagePassThroughLayout; }, - "basic": function() { return basicLayout; }, - "pattern": function (config) { - var pattern = config.pattern || undefined; - return patternLayout(pattern); - } - }; + "logLevelFilter": function(config, fileAppender, consoleAppender) { + var appender = appenderMakers[config.appender.type](config.appender, fileAppender, consoleAppender); + return logLevelFilter(config.level, appender); + } +}, +layoutMakers = { + "messagePassThrough": function() { return messagePassThroughLayout; }, + "basic": function() { return basicLayout; }, + "pattern": function (config) { + var pattern = config.pattern || undefined; + return patternLayout(pattern); + } +}; - /** - * 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]; +/** + * 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; } - /** - * 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[0] === undefined) { - args = [ ALL_CATEGORIES ]; + 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); + }); } - //argument may already be an array - if (args[0].forEach) { - args = args[0]; + 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[0] === undefined) { + 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); - 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); - } + 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); + } + } else if (loggers[category]) { + loggers[category].addListener("log", appender); + } + }); + appenders.count = appenders.count ? appenders.count++ : 1; +} + +function clearAppenders () { + appenders = {}; + for (var logger in loggers) { + if (loggers.hasOwnProperty(logger)) { + loggers[logger].removeAllListeners("log"); + } + } +} + +function configureAppenders(appenderList, fileAppender, consoleAppender) { + clearAppenders(); + if (appenderList) { + appenderList.forEach(function(appenderConfig) { + var appender = appenderMakers[appenderConfig.type](appenderConfig, fileAppender, consoleAppender); + if (appender) { + addAppender(appender, appenderConfig.category); + } else { + throw new Error("log4js configuration problem for "+sys.inspect(appenderConfig)); } }); + } else { + addAppender(consoleAppender); } +} - function clearAppenders () { - appenders = []; - for (var logger in loggers) { - if (loggers.hasOwnProperty(logger)) { - loggers[logger].removeAllListeners("log"); +function configureLevels(levels) { + if (levels) { + for (var category in levels) { + if (levels.hasOwnProperty(category)) { + getLogger(category).setLevel(levels[category]); } } } +} - function configure (configurationFile) { - if (configurationFile) { - try { - var config = JSON.parse(fs.readFileSync(configurationFile, "utf8")); - configureAppenders(config.appenders); - configureLevels(config.levels); - } catch (e) { - throw new Error("Problem reading log4js config file " + configurationFile + ". Error was " + e.message); - } - } - } +function Level(level, levelStr, colour) { + this.level = level; + this.levelStr = levelStr; + this.colour = colour; +} - function findConfiguration() { - //add current directory onto the list of configPaths - var paths = ['.'].concat(configPaths); - //add this module's directory to the end of the list, so that we pick up the default config - paths.push(__dirname); - var pathsWithConfig = paths.filter( function (pathToCheck) { - try { - fs.statSync(path.join(pathToCheck, "log4js.json")); - return true; - } catch (e) { - return false; - } - }); - if (pathsWithConfig.length > 0) { - return path.join(pathsWithConfig[0], 'log4js.json'); - } - return undefined; - } - - function configureAppenders(appenderList) { - clearAppenders(); - if (appenderList) { - appenderList.forEach(function(appenderConfig) { - var appender = appenderMakers[appenderConfig.type](appenderConfig); - if (appender) { - addAppender(appender, appenderConfig.category); - } else { - throw new Error("log4js configuration problem for "+sys.inspect(appenderConfig)); - } - }); - } else { - addAppender(consoleAppender); - } - } - - function configureLevels(levels) { - if (levels) { - for (var category in levels) { - if (levels.hasOwnProperty(category)) { - getLogger(category).setLevel(levels[category]); - } - } - } - } - - function Level(level, levelStr, colour) { - this.level = level; - this.levelStr = levelStr; - this.colour = colour; - } - - /** - * converts given String to corresponding Level - * @param {String} sArg String value of Level - * @param {Log4js.Level} defaultLevel default Level, if no String representation - * @return Level object - * @type Log4js.Level - */ - Level.toLevel = function(sArg, defaultLevel) { - - if (sArg === null) { - return defaultLevel; - } - - if (typeof sArg == "string") { - var s = sArg.toUpperCase(); - if (levels[s]) { - return levels[s]; - } - } +/** + * converts given String to corresponding Level + * @param {String} sArg String value of Level + * @param {Log4js.Level} defaultLevel default Level, if no String representation + * @return Level object + * @type Log4js.Level + */ +Level.toLevel = function(sArg, defaultLevel) { + + if (sArg === null) { return defaultLevel; - }; - - Level.prototype.toString = function() { - return this.levelStr; - }; + } - Level.prototype.isLessThanOrEqualTo = function(otherLevel) { - return this.level <= otherLevel.level; - }; + if (typeof sArg == "string") { + var s = sArg.toUpperCase(); + if (levels[s]) { + return levels[s]; + } + } + return defaultLevel; +}; - Level.prototype.isGreaterThanOrEqualTo = function(otherLevel) { - return this.level >= otherLevel.level; - }; +Level.prototype.toString = function() { + return this.levelStr; +}; - /** - * Models a logging event. - * @constructor - * @param {String} categoryName name of category - * @param {Log4js.Level} level level of message - * @param {String} message message to log - * @param {Log4js.Logger} logger the associated logger - * @author Seth Chisamore - */ - function LoggingEvent (categoryName, level, message, exception, logger) { - this.startTime = new Date(); - this.categoryName = categoryName; - this.message = message; - this.level = level; - this.logger = logger; - if (exception && exception.message && exception.name) { - this.exception = exception; - } else if (exception) { - this.exception = new Error(sys.inspect(exception)); +Level.prototype.isLessThanOrEqualTo = function(otherLevel) { + return this.level <= otherLevel.level; +}; + +Level.prototype.isGreaterThanOrEqualTo = function(otherLevel) { + return this.level >= otherLevel.level; +}; + +/** + * Models a logging event. + * @constructor + * @param {String} categoryName name of category + * @param {Log4js.Level} level level of message + * @param {String} message message to log + * @param {Log4js.Logger} logger the associated logger + * @author Seth Chisamore + */ +function LoggingEvent (categoryName, level, message, exception, logger) { + this.startTime = new Date(); + this.categoryName = categoryName; + this.message = message; + this.level = level; + this.logger = logger; + if (exception && exception.message && exception.name) { + this.exception = exception; + } else if (exception) { + this.exception = new Error(sys.inspect(exception)); + } +} + +/** + * Logger to log messages. + * use {@see Log4js#getLogger(String)} to get an instance. + * @constructor + * @param name name of category to log to + * @author Stephan Strittmatter + */ +function Logger (name, level) { + this.category = name || DEFAULT_CATEGORY; + this.level = Level.toLevel(level, levels.TRACE); +} +sys.inherits(Logger, events.EventEmitter); + +Logger.prototype.setLevel = function(level) { + this.level = Level.toLevel(level, levels.TRACE); +}; + +Logger.prototype.log = function(logLevel, message, exception) { + var loggingEvent = new LoggingEvent(this.category, logLevel, message, exception, this); + this.emit("log", loggingEvent); +}; + +Logger.prototype.isLevelEnabled = function(otherLevel) { + return this.level.isLessThanOrEqualTo(otherLevel); +}; + +['Trace','Debug','Info','Warn','Error','Fatal'].forEach( + function(levelString) { + var level = Level.toLevel(levelString); + Logger.prototype['is'+levelString+'Enabled'] = function() { + return this.isLevelEnabled(level); + }; + + Logger.prototype[levelString.toLowerCase()] = function (message, exception) { + if (this.isLevelEnabled(level)) { + this.log(level, message, exception); + } + }; + } +); + +/** + * Get the default logger instance. + * @return {Logger} instance of default logger + * @static + */ +function getDefaultLogger () { + return getLogger(DEFAULT_CATEGORY); +} + +function logLevelFilter (levelString, appender) { + var level = Level.toLevel(levelString); + return function(logEvent) { + if (logEvent.level.isGreaterThanOrEqualTo(level)) { + appender(logEvent); + } + } +} + +/** + * BasicLayout is a simple layout for storing the logs. The logs are stored + * in following format: + *
+ * [startTime] [logLevel] categoryName - message\n
+ * 
+ * + * @author Stephan Strittmatter + */ +function basicLayout (loggingEvent) { + var timestampLevelAndCategory = '[' + loggingEvent.startTime.toFormattedString() + '] '; + timestampLevelAndCategory += '[' + loggingEvent.level.toString() + '] '; + timestampLevelAndCategory += loggingEvent.categoryName + ' - '; + + var output = timestampLevelAndCategory + loggingEvent.message; + + if (loggingEvent.exception) { + output += '\n' + output += timestampLevelAndCategory; + if (loggingEvent.exception.stack) { + output += loggingEvent.exception.stack; + } else { + output += loggingEvent.exception.name + ': '+loggingEvent.exception.message; + } + } + return output; +} + +/** + * Taken from masylum's fork (https://github.com/masylum/log4js-node) + */ +function colorize (str, style) { + var styles = { + //styles + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + //grayscale + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [90, 39], + //colors + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] + }; + return '\033[' + styles[style][0] + 'm' + str + + '\033[' + styles[style][1] + 'm'; +} + +/** + * colouredLayout - taken from masylum's fork. + * same as basicLayout, but with colours. + */ +function colouredLayout (loggingEvent) { + var timestampLevelAndCategory = colorize('[' + loggingEvent.startTime.toFormattedString() + '] ', 'grey'); + timestampLevelAndCategory += colorize( + '[' + loggingEvent.level.toString() + '] ', loggingEvent.level.colour + ); + timestampLevelAndCategory += colorize(loggingEvent.categoryName + ' - ', 'grey'); + + var output = timestampLevelAndCategory + loggingEvent.message; + + if (loggingEvent.exception) { + output += '\n' + output += timestampLevelAndCategory; + if (loggingEvent.exception.stack) { + output += loggingEvent.exception.stack; + } else { + output += loggingEvent.exception.name + ': '+loggingEvent.exception.message; } } + return output; +} - /** - * Logger to log messages. - * use {@see Log4js#getLogger(String)} to get an instance. - * @constructor - * @param name name of category to log to - * @author Stephan Strittmatter - */ - function Logger (name, level) { - this.category = name || DEFAULT_CATEGORY; - this.level = Level.toLevel(level, levels.TRACE); - } - sys.inherits(Logger, events.EventEmitter); +function messagePassThroughLayout (loggingEvent) { + return loggingEvent.message; +} - Logger.prototype.setLevel = function(level) { - this.level = Level.toLevel(level, levels.TRACE); - }; +/** + * PatternLayout + * Takes a pattern string and returns a layout function. + * @author Stephan Strittmatter + */ +function patternLayout (pattern) { + var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n"; + var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([cdmnpr%])(\{([^\}]+)\})?|([^%]+)/; - Logger.prototype.log = function(logLevel, message, exception) { - var loggingEvent = new LoggingEvent(this.category, logLevel, message, exception, this); - this.emit("log", loggingEvent); - }; + pattern = pattern || patternLayout.TTCC_CONVERSION_PATTERN; - Logger.prototype.isLevelEnabled = function(otherLevel) { - return this.level.isLessThanOrEqualTo(otherLevel); - }; + return function(loggingEvent) { + var formattedString = ""; + var result; + var searchString = this.pattern; - ['Trace','Debug','Info','Warn','Error','Fatal'].forEach( - function(levelString) { - var level = Level.toLevel(levelString); - Logger.prototype['is'+levelString+'Enabled'] = function() { - return this.isLevelEnabled(level); - }; - - Logger.prototype[levelString.toLowerCase()] = function (message, exception) { - if (this.isLevelEnabled(level)) { - this.log(level, message, exception); + while ((result = regex.exec(searchString))) { + var matchedString = result[0]; + var padding = result[1]; + var truncation = result[2]; + var conversionCharacter = result[3]; + var specifier = result[5]; + var text = result[6]; + + // Check if the pattern matched was just normal text + if (text) { + formattedString += "" + text; + } else { + // Create a raw replacement string based on the conversion + // character and specifier + var replacement = ""; + switch(conversionCharacter) { + case "c": + var loggerName = loggingEvent.categoryName; + if (specifier) { + var precision = parseInt(specifier, 10); + var loggerNameBits = loggingEvent.categoryName.split("."); + if (precision >= loggerNameBits.length) { + replacement = loggerName; + } else { + replacement = loggerNameBits.slice(loggerNameBits.length - precision).join("."); + } + } else { + replacement = loggerName; + } + break; + case "d": + var dateFormat = Date.ISO8601_FORMAT; + if (specifier) { + dateFormat = specifier; + // Pick up special cases + if (dateFormat == "ISO8601") { + dateFormat = Date.ISO8601_FORMAT; + } else if (dateFormat == "ABSOLUTE") { + dateFormat = Date.ABSOLUTETIME_FORMAT; + } else if (dateFormat == "DATE") { + dateFormat = Date.DATETIME_FORMAT; + } + } + // Format the date + replacement = loggingEvent.startTime.toFormattedString(dateFormat); + break; + case "m": + replacement = loggingEvent.message; + break; + case "n": + replacement = "\n"; + break; + case "p": + replacement = loggingEvent.level.toString(); + break; + case "r": + replacement = "" + loggingEvent.startTime.toLocaleTimeString(); + break; + case "%": + replacement = "%"; + break; + default: + replacement = matchedString; + break; } - }; - } - ); + // Format the replacement according to any padding or + // truncation specified + + var len; + + // First, truncation + if (truncation) { + len = parseInt(truncation.substr(1), 10); + replacement = replacement.substring(0, len); + } + // Next, padding + if (padding) { + if (padding.charAt(0) == "-") { + len = parseInt(padding.substr(1), 10); + // Right pad with spaces + while (replacement.length < len) { + replacement += " "; + } + } else { + len = parseInt(padding, 10); + // Left pad with spaces + while (replacement.length < len) { + replacement = " " + replacement; + } + } + } + formattedString += replacement; + } + searchString = searchString.substr(result.index + result[0].length); + } + return formattedString; + }; + +}; + + +module.exports = function (fileSystem, standardOutput, configPaths) { + var fs = fileSystem || require('fs'), + standardOutput = standardOutput || sys.puts, + configPaths = configPaths || require.paths; - /** - * Get the default logger instance. - * @return {Logger} instance of default logger - * @static - */ - function getDefaultLogger () { - return getLogger(DEFAULT_CATEGORY); - } function consoleAppender (layout) { layout = layout || colouredLayout; @@ -410,214 +591,37 @@ module.exports = function (fileSystem, standardOutput, configPaths) { } } - function logLevelFilter (levelString, appender) { - var level = Level.toLevel(levelString); - return function(logEvent) { - if (logEvent.level.isGreaterThanOrEqualTo(level)) { - appender(logEvent); - } - } - } - - /** - * BasicLayout is a simple layout for storing the logs. The logs are stored - * in following format: - *
-     * [startTime] [logLevel] categoryName - message\n
-     * 
- * - * @author Stephan Strittmatter - */ - function basicLayout (loggingEvent) { - var timestampLevelAndCategory = '[' + loggingEvent.startTime.toFormattedString() + '] '; - timestampLevelAndCategory += '[' + loggingEvent.level.toString() + '] '; - timestampLevelAndCategory += loggingEvent.categoryName + ' - '; - - var output = timestampLevelAndCategory + loggingEvent.message; - - if (loggingEvent.exception) { - output += '\n' - output += timestampLevelAndCategory; - if (loggingEvent.exception.stack) { - output += loggingEvent.exception.stack; - } else { - output += loggingEvent.exception.name + ': '+loggingEvent.exception.message; - } - } - return output; - } - - /** - * Taken from masylum's fork (https://github.com/masylum/log4js-node) - */ - function colorize (str, style) { - var styles = { - //styles - 'bold' : [1, 22], - 'italic' : [3, 23], - 'underline' : [4, 24], - 'inverse' : [7, 27], - //grayscale - 'white' : [37, 39], - 'grey' : [90, 39], - 'black' : [90, 39], - //colors - 'blue' : [34, 39], - 'cyan' : [36, 39], - 'green' : [32, 39], - 'magenta' : [35, 39], - 'red' : [31, 39], - 'yellow' : [33, 39] - }; - return '\033[' + styles[style][0] + 'm' + str + - '\033[' + styles[style][1] + 'm'; - } - - /** - * colouredLayout - taken from masylum's fork. - * same as basicLayout, but with colours. - */ - function colouredLayout (loggingEvent) { - var timestampLevelAndCategory = colorize('[' + loggingEvent.startTime.toFormattedString() + '] ', 'grey'); - timestampLevelAndCategory += colorize( - '[' + loggingEvent.level.toString() + '] ', loggingEvent.level.colour - ); - timestampLevelAndCategory += colorize(loggingEvent.categoryName + ' - ', 'grey'); - - var output = timestampLevelAndCategory + loggingEvent.message; - - if (loggingEvent.exception) { - output += '\n' - output += timestampLevelAndCategory; - if (loggingEvent.exception.stack) { - output += loggingEvent.exception.stack; - } else { - output += loggingEvent.exception.name + ': '+loggingEvent.exception.message; + function configure (configurationFile) { + if (configurationFile) { + try { + var config = JSON.parse(fs.readFileSync(configurationFile, "utf8")); + configureAppenders(config.appenders, fileAppender, consoleAppender); + configureLevels(config.levels); + } catch (e) { + throw new Error("Problem reading log4js config file " + configurationFile + ". Error was \"" + e.message + "\" ("+e.stack+")"); } } - return output; } - function messagePassThroughLayout (loggingEvent) { - return loggingEvent.message; + function findConfiguration() { + //add current directory onto the list of configPaths + var paths = ['.'].concat(configPaths); + //add this module's directory to the end of the list, so that we pick up the default config + paths.push(__dirname); + var pathsWithConfig = paths.filter( function (pathToCheck) { + try { + fs.statSync(path.join(pathToCheck, "log4js.json")); + return true; + } catch (e) { + return false; + } + }); + if (pathsWithConfig.length > 0) { + return path.join(pathsWithConfig[0], 'log4js.json'); + } + return undefined; } - /** - * PatternLayout - * Takes a pattern string and returns a layout function. - * @author Stephan Strittmatter - */ - function patternLayout (pattern) { - var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n"; - var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([cdmnpr%])(\{([^\}]+)\})?|([^%]+)/; - - pattern = pattern || patternLayout.TTCC_CONVERSION_PATTERN; - - return function(loggingEvent) { - var formattedString = ""; - var result; - var searchString = this.pattern; - - while ((result = regex.exec(searchString))) { - var matchedString = result[0]; - var padding = result[1]; - var truncation = result[2]; - var conversionCharacter = result[3]; - var specifier = result[5]; - var text = result[6]; - - // Check if the pattern matched was just normal text - if (text) { - formattedString += "" + text; - } else { - // Create a raw replacement string based on the conversion - // character and specifier - var replacement = ""; - switch(conversionCharacter) { - case "c": - var loggerName = loggingEvent.categoryName; - if (specifier) { - var precision = parseInt(specifier, 10); - var loggerNameBits = loggingEvent.categoryName.split("."); - if (precision >= loggerNameBits.length) { - replacement = loggerName; - } else { - replacement = loggerNameBits.slice(loggerNameBits.length - precision).join("."); - } - } else { - replacement = loggerName; - } - break; - case "d": - var dateFormat = Date.ISO8601_FORMAT; - if (specifier) { - dateFormat = specifier; - // Pick up special cases - if (dateFormat == "ISO8601") { - dateFormat = Date.ISO8601_FORMAT; - } else if (dateFormat == "ABSOLUTE") { - dateFormat = Date.ABSOLUTETIME_FORMAT; - } else if (dateFormat == "DATE") { - dateFormat = Date.DATETIME_FORMAT; - } - } - // Format the date - replacement = loggingEvent.startTime.toFormattedString(dateFormat); - break; - case "m": - replacement = loggingEvent.message; - break; - case "n": - replacement = "\n"; - break; - case "p": - replacement = loggingEvent.level.toString(); - break; - case "r": - replacement = "" + loggingEvent.startTime.toLocaleTimeString(); - break; - case "%": - replacement = "%"; - break; - default: - replacement = matchedString; - break; - } - // Format the replacement according to any padding or - // truncation specified - - var len; - - // First, truncation - if (truncation) { - len = parseInt(truncation.substr(1), 10); - replacement = replacement.substring(0, len); - } - // Next, padding - if (padding) { - if (padding.charAt(0) == "-") { - len = parseInt(padding.substr(1), 10); - // Right pad with spaces - while (replacement.length < len) { - replacement += " "; - } - } else { - len = parseInt(padding, 10); - // Left pad with spaces - while (replacement.length < len) { - replacement = " " + replacement; - } - } - } - formattedString += replacement; - } - searchString = searchString.substr(result.index + result[0].length); - } - return formattedString; - }; - - }; - function replaceConsole(logger) { function replaceWith (fn) { return function() { @@ -634,10 +638,13 @@ module.exports = function (fileSystem, standardOutput, configPaths) { } - //set ourselves up if we can find a default log4js.json - configure(findConfiguration()); - //replace console.log, etc with log4js versions - replaceConsole(getLogger("console")); + //do we already have appenders? + if (!appenders.count) { + //set ourselves up if we can find a default log4js.json + configure(findConfiguration()); + //replace console.log, etc with log4js versions + replaceConsole(getLogger("console")); + } return { getLogger: getLogger, diff --git a/test/logging.js b/test/logging.js index 0e4cc41..b108ec1 100644 --- a/test/logging.js +++ b/test/logging.js @@ -247,7 +247,9 @@ vows.describe('log4js').addBatch({ 'with no appenders defined' : { topic: function() { - var logger, message, log4js = require('../lib/log4js')(null, function (msg) { message = msg; } ); + var logger, message, log4jsFn = require('../lib/log4js'), log4js; + log4jsFn().clearAppenders(); + log4js = log4jsFn(null, function (msg) { message = msg; } ); logger = log4js.getLogger("some-logger"); logger.debug("This is a test"); return message; @@ -364,10 +366,15 @@ vows.describe('log4js').addBatch({ }, fakeConsoleLog = function (msg) { message = msg; }, fakeRequirePath = [ '/a/b/c', '/some/other/path', '/path/to/config', '/some/later/directory' ], - log4js = require('../lib/log4js')(fakeFS, fakeConsoleLog, fakeRequirePath), + log4jsFn = require('../lib/log4js'), + log4js, logger; + + log4jsFn().clearAppenders(); + log4js = log4jsFn(fakeFS, fakeConsoleLog, fakeRequirePath); logger = log4js.getLogger('a-test'); logger.debug("this is a test"); + return [ pathsChecked, message ]; }, @@ -478,8 +485,8 @@ vows.describe('log4js').addBatch({ topic: function() { var log4js = require('../lib/log4js')(), logEvents = [], logger; log4js.clearAppenders(); - log4js.addAppender(log4js.logLevelFilter('ERROR', function(evt) { logEvents.push(evt); })); - logger = log4js.getLogger(); + log4js.addAppender(log4js.logLevelFilter('ERROR', function(evt) { logEvents.push(evt); }), "logLevelTest"); + logger = log4js.getLogger("logLevelTest"); logger.debug('this should not trigger an event'); logger.warn('neither should this'); logger.error('this should, though'); @@ -535,6 +542,19 @@ vows.describe('log4js').addBatch({ assert.equal(logEvent.message, "tracing"); assert.equal(logEvent.level.toString(), "TRACE"); } + }, + + 'configuration persistence' : { + 'should maintain appenders between requires': function () { + var logEvent, firstLog4js = require('../lib/log4js')(), secondLog4js; + firstLog4js.clearAppenders(); + firstLog4js.addAppender(function(evt) { logEvent = evt; }); + + secondLog4js = require('../lib/log4js')(); + secondLog4js.getLogger().info("This should go to the appender defined in firstLog4js"); + + assert.equal(logEvent.message, "This should go to the appender defined in firstLog4js"); + } } }).export(module); From a876dfbe9cc5e54fde2a96234a3788ca712b3b1f Mon Sep 17 00:00:00 2001 From: csausdev Date: Sun, 16 Jan 2011 13:21:37 +1100 Subject: [PATCH 22/25] configure now takes a filename or object --- lib/log4js.js | 21 ++++++++++++--------- test/logging.js | 20 +++++++++++++++++++- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/log4js.js b/lib/log4js.js index 4a2dbfd..460161c 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -591,16 +591,19 @@ module.exports = function (fileSystem, standardOutput, configPaths) { } } - function configure (configurationFile) { - if (configurationFile) { - try { - var config = JSON.parse(fs.readFileSync(configurationFile, "utf8")); - configureAppenders(config.appenders, fileAppender, consoleAppender); - configureLevels(config.levels); - } catch (e) { - throw new Error("Problem reading log4js config file " + configurationFile + ". Error was \"" + e.message + "\" ("+e.stack+")"); - } + function configure (configurationFileOrObject) { + var config = configurationFileOrObject; + if (typeof(config) === "string") { + config = JSON.parse(fs.readFileSync(config, "utf8")); } + if (config) { + try { + configureAppenders(config.appenders, fileAppender, consoleAppender); + configureLevels(config.levels); + } catch (e) { + throw new Error("Problem reading log4js config " + sys.inspect(config) + ". Error was \"" + e.message + "\" ("+e.stack+")"); + } + } } function findConfiguration() { diff --git a/test/logging.js b/test/logging.js index b108ec1..42b71a6 100644 --- a/test/logging.js +++ b/test/logging.js @@ -242,7 +242,25 @@ vows.describe('log4js').addBatch({ delete messages['tmp-test.log']; log4js.configure('test/with-log-rolling.json'); assert.equal(messages.watchedFile, 'tmp-test.log'); - } + }, + 'should handle an object or a file name': function(args) { + var log4js = args[0], + messages = args[1], + config = { + "appenders": [ + { + "type" : "file", + "filename" : "cheesy-wotsits.log", + "maxLogSize" : 1024, + "backups" : 3, + "pollInterval" : 15 + } + ] + }; + delete messages['cheesy-wotsits.log']; + log4js.configure(config); + assert.equal(messages.watchedFile, 'cheesy-wotsits.log'); + } }, 'with no appenders defined' : { From 079edd19c8e370dac65cfd5e9456dfcdfd327c9f Mon Sep 17 00:00:00 2001 From: csausdev Date: Sun, 16 Jan 2011 13:24:07 +1100 Subject: [PATCH 23/25] bumped version, added configure(object) to README --- README.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a5b6ba2..29a7e59 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Output 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). An example file can be found in `test/log4js.json`. An example config file with log rolling is in `test/with-log-rolling.json` +You can also pass an object to the configure function, which has the same properties as the json versions. ## todo diff --git a/package.json b/package.json index b2eb3e8..cb5cd0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.2.2", + "version": "0.2.3", "description": "Port of Log4js to work with node.", "keywords": [ "logging", From 71459ab6d34d380b1d8a0fa1f68a7c20c348a117 Mon Sep 17 00:00:00 2001 From: csausdev Date: Fri, 4 Mar 2011 19:49:43 +1100 Subject: [PATCH 24/25] changed array detection (thanks fkei) --- lib/log4js.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/log4js.js b/lib/log4js.js index 460161c..3785b84 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -134,7 +134,7 @@ function addAppender () { args = [ ALL_CATEGORIES ]; } //argument may already be an array - if (args[0].forEach) { + if (Array.isArray(args[0])) { args = args[0]; } From f21fa2bcf822f9daab213db836fe0922f4ecfadc Mon Sep 17 00:00:00 2001 From: csausdev Date: Fri, 4 Mar 2011 19:52:48 +1100 Subject: [PATCH 25/25] changed array detection (thanks fkei) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cb5cd0d..04df4df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "log4js", - "version": "0.2.3", + "version": "0.2.4", "description": "Port of Log4js to work with node.", "keywords": [ "logging",