diff --git a/lib/log4js.js b/lib/log4js.js index 22ac913..c781a30 100644 --- a/lib/log4js.js +++ b/lib/log4js.js @@ -44,25 +44,21 @@ * Website: http://log4js.berlios.de */ var debug = require('./debug')('core') -, weak = require('weak') , fs = require('fs') -, path = require('path') , util = require('util') , layouts = require('./layouts') , levels = require('./levels') -, LoggingEvent = require('./logger').LoggingEvent -, Logger = require('./logger').Logger -, loggerRefs = [] -, categoryLevels = {} -, globalLogLevel = null -, ALL_CATEGORIES = '[all]' +, Logger = require('./logger') , appenders = {} +, categories = {} , appenderMakers = {} , defaultConfig = { - appenders: [ - { type: "console" } - ], - replaceConsole: false + appenders: { + console: { type: "console" } + }, + categories: { + default: { level: levels.DEBUG, appenders: [ "console" ] } + } }; /** @@ -72,100 +68,9 @@ var debug = require('./debug')('core') * @static */ function getLogger (categoryName) { - var level, logger, ref; - - // Use default logger if categoryName is not specified or invalid - if (typeof categoryName !== "string") { - categoryName = Logger.DEFAULT_CATEGORY; - } - - level = categoryLevels[categoryName]; - - if (globalLogLevel) { - level = globalLogLevel; - } - - debug("getLogger(" + categoryName + ") - level is " + level); - logger = new Logger(categoryName, level || null, dispatch); - ref = weak(logger); - loggerRefs.push(ref); - - return logger; -} - -/** - * 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 (Array.isArray(args[0])) { - args = args[0]; - } - - args.forEach(function(category) { - addAppenderToCategory(appender, category); - }); -} - -function addAppenderToCategory(appender, category) { - debug("adding appender " + appender + " to category " + category); - if (!appenders[category]) { - appenders[category] = []; - } - appenders[category].push(appender); -} - -function clearAppenders () { - debug("clearing appenders"); - appenders = {}; -} - -function configureAppenders(appenderList, options) { - clearAppenders(); - if (appenderList) { - appenderList.forEach(function(appenderConfig) { - loadAppender(appenderConfig.type); - var appender; - appenderConfig.makers = appenderMakers; - try { - appender = appenderMakers[appenderConfig.type](appenderConfig, options); - addAppender(appender, appenderConfig.category); - } catch(e) { - throw new Error("log4js configuration problem for " + util.inspect(appenderConfig), e); - } - }); - } -} + debug("getLogger(" + categoryName + ")"); -function configureLevels(levels) { - categoryLevels = levels || {}; -} - -function setGlobalLogLevel(level) { - //Logger.prototype.level = levels.toLevel(level, levels.TRACE); - globalLogLevel = level; - var workingRefs = []; - loggerRefs.forEach(function(logger) { - if (logger.setLevel) { - workingRefs.push(logger); - logger.setLevel(level); - } - }); - loggerRefs = workingRefs; -} - -/** - * Get the default logger instance. - * @return {Logger} instance of default logger - * @static - */ -function getDefaultLogger () { - return getLogger(Logger.DEFAULT_CATEGORY); + return new Logger(dispatch, categoryName || 'default'); } /** @@ -174,25 +79,17 @@ function getDefaultLogger () { */ function dispatch(event) { debug("event is " + util.inspect(event)); - debug("appenders is " + util.inspect(appenders)); - if (appenders[event.categoryName]) { - debug("appender defined for " + event.categoryName); - dispatchToCategory(event.categoryName, event); - } + var category = categories[event.category] || categories.default; + debug("category.level[" + category.level + "] <= " + event.level + " ? " + category.level.isLessThanOrEqualTo(event.level)); - if (appenders[ALL_CATEGORIES]) { - debug("appender defined for " + ALL_CATEGORIES); - dispatchToCategory(ALL_CATEGORIES, event); + if (category.level.isLessThanOrEqualTo(event.level)) { + category.appenders.forEach(function(appender) { + appenders[appender](event); + }); } } -function dispatchToCategory(category, event) { - appenders[category].forEach(function(appender) { - debug("Sending " + util.inspect(event) + " to appender " + appender); - appender(event); - }); -} - +/* var configState = {}; function loadConfigurationFile(filename) { @@ -251,8 +148,34 @@ function initReloadConfiguration(filename, options) { configState.lastMTime = getMTime(filename); configState.timerId = setInterval(reloadConfiguration, options.reloadSecs*1000); } +*/ + +function load(file) { + return JSON.parse(fs.readFileSync(file, "utf-8")); +} -function configure(configurationFileOrObject, options) { +function configure(configurationFileOrObject) { + var filename, config = configurationFileOrObject || process.env.LOG4JS_CONFIG; + + if (!config || !(typeof config === 'string' || typeof config === 'object')) { + throw new Error("You must specify configuration as an object or a filename."); + } + + if (typeof config === 'string') { + filename = config; + config = load(filename); + } + + if (!config.appenders || !Object.keys(config.appenders).length) { + throw new Error("You must specify at least one appender."); + } + + configureAppenders(config.appenders); + + validateCategories(config.categories); + categories = config.categories; + +/* var config = configurationFileOrObject; config = config || process.env.LOG4JS_CONFIG; options = options || {}; @@ -270,8 +193,70 @@ function configure(configurationFileOrObject, options) { } } configureOnceOff(config, options); +*/ +} + +function validateCategories(cats) { + if (!cats || !cats.default) { + throw new Error("You must specify an appender for the default category"); + } + + Object.keys(cats).forEach(function(categoryName) { + var category = cats[categoryName], inputLevel = category.level; + if (!category.level) { + throw new Error("You must specify a level for category '" + categoryName + "'."); + } + category.level = levels.toLevel(inputLevel); + if (!category.level) { + throw new Error("Level '" + inputLevel + "' is not valid for category '" + categoryName + "'. Acceptable values are: " + levels.levels.join(', ') + "."); + } + + if (!category.appenders || !category.appenders.length) { + throw new Error("You must specify an appender for category '" + categoryName + "'."); + } + + category.appenders.forEach(function(appender) { + if (!appenders[appender]) { + throw new Error("Appender '" + appender + "' for category '" + categoryName + "' does not exist. Known appenders are: " + Object.keys(appenders).join(', ') + "."); + } + }); + }); +} + +function clearAppenders () { + debug("clearing appenders"); + appenders = {}; } +function configureAppenders(appenderMap) { + clearAppenders(); + Object.keys(appenderMap).forEach(function(appenderName) { + var appender, appenderConfig = appenderMap[appenderName]; + loadAppender(appenderConfig.type); + appenderConfig.makers = appenderMakers; + try { + appenders[appenderName] = appenderMakers[appenderConfig.type](appenderConfig); + } catch(e) { + throw new Error("log4js configuration problem for appender '" + appenderName + "'. Error was " + e.stack); + } + }); +} + +function loadAppender(appender) { + var appenderModule; + try { + appenderModule = require('./appenders/' + appender); + } catch (e) { + try { + appenderModule = require(appender); + } catch (err) { + throw new Error("Could not load appender of type '" + appender + "'."); + } + } + appenderMakers[appender] = appenderModule.configure.bind(appenderModule); +} + +/* var originalConsoleFunctions = { log: console.log, debug: console.debug, @@ -298,40 +283,29 @@ function restoreConsole() { }); } -function loadAppender(appender) { - var appenderModule; - try { - appenderModule = require('./appenders/' + appender); - } catch (e) { - appenderModule = require(appender); - } - module.exports.appenders[appender] = appenderModule.appender.bind(appenderModule); - appenderMakers[appender] = appenderModule.configure.bind(appenderModule); -} - +*/ module.exports = { getLogger: getLogger, - getDefaultLogger: getDefaultLogger, - dispatch: dispatch, - +/* addAppender: addAppender, loadAppender: loadAppender, clearAppenders: clearAppenders, +*/ configure: configure, - +/* replaceConsole: replaceConsole, restoreConsole: restoreConsole, levels: levels, - setGlobalLogLevel: setGlobalLogLevel, layouts: layouts, appenders: {}, appenderMakers: appenderMakers, connectLogger: require('./connect-logger').connectLogger +*/ }; //set ourselves up -debug("Starting configuration"); -configure(); +//debug("Starting configuration"); +//configure(); diff --git a/test/log4js-test.js b/test/log4js-test.js new file mode 100644 index 0000000..40742c9 --- /dev/null +++ b/test/log4js-test.js @@ -0,0 +1,291 @@ +"use strict"; +var should = require('should') +, fs = require('fs') +, sandbox = require('sandboxed-module') +, log4js = require('../lib/log4js'); + +describe('../lib/log4js', function() { + describe('#getLogger', function() { + it('should return a Logger', function() { + log4js.getLogger().should.have.property('debug').be.a('function'); + log4js.getLogger().should.have.property('info').be.a('function'); + log4js.getLogger().should.have.property('error').be.a('function'); + }); + }); + + describe('#configure', function() { + it('should require an object or a filename', function() { + [ + undefined, + null, + true, + 42, + function() {} + ].forEach(function(arg) { + (function() { log4js.configure(arg); }).should.throw( + "You must specify configuration as an object or a filename." + ); + }); + }); + + it('should complain if the file cannot be found', function() { + (function() { log4js.configure("pants"); }).should.throw( + "ENOENT, no such file or directory 'pants'" + ); + }); + + it('should pick up the configuration filename from env.LOG4JS_CONFIG', function() { + process.env.LOG4JS_CONFIG = 'made-up-file'; + (function() { log4js.configure(); }).should.throw( + "ENOENT, no such file or directory 'made-up-file'" + ); + process.env.LOG4JS_CONFIG = null; + }); + + it('should complain if the config does not specify any appenders', function() { + + (function() { log4js.configure({}); }).should.throw( + "You must specify at least one appender." + ); + + (function() { log4js.configure({ appenders: {} }); }).should.throw( + "You must specify at least one appender." + ); + + }); + + it('should complain if the config does not specify an appender for the default category', function() { + + (function() { + log4js.configure( + { + appenders: { + "console": { type: "console" } + }, + categories: {} + } + ); + }).should.throw( + "You must specify an appender for the default category" + ); + + (function() { + log4js.configure({ + appenders: { + "console": { type: "console" } + }, + categories: { + "cheese": { level: "DEBUG", appenders: [ "console" ] } + } + }); + }).should.throw( + "You must specify an appender for the default category" + ); + + }); + + it('should complain if a category does not specify level or appenders', function() { + (function() { + log4js.configure( + { appenders: { "console": { type: "console" } }, + categories: { + "default": { thing: "thing" } + } + } + ); + }).should.throw( + "You must specify a level for category 'default'." + ); + + (function() { + log4js.configure( + { appenders: { "console": { type: "console" } }, + categories: { + "default": { level: "DEBUG" } + } + } + ); + }).should.throw( + "You must specify an appender for category 'default'." + ); + }); + + it('should complain if a category specifies a level that does not exist', function() { + (function() { + log4js.configure( + { appenders: { "console": { type: "console" }}, + categories: { + "default": { level: "PICKLES" } + } + } + ); + }).should.throw( + "Level 'PICKLES' is not valid for category 'default'. " + + "Acceptable values are: OFF, TRACE, DEBUG, INFO, WARN, ERROR, FATAL." + ); + }); + + it('should complain if a category specifies an appender that does not exist', function() { + (function() { + log4js.configure( + { appenders: { "console": { type: "console" }}, + categories: { + "default": { level: "DEBUG", appenders: [ "cheese" ] } + } + } + ); + }).should.throw( + "Appender 'cheese' for category 'default' does not exist. Known appenders are: console." + ); + }); + + before(function(done) { + fs.unlink("test.log", function (err) { done(); }); + }); + + it('should set up the included appenders', function(done) { + log4js.configure({ + appenders: { + "file": { type: "file", filename: "test.log" } + }, + categories: { + default: { level: "DEBUG", appenders: [ "file" ] } + } + }); + log4js.getLogger('test').debug("cheese"); + + setTimeout(function() { + fs.readFile("test.log", "utf-8", function(err, contents) { + contents.should.include("cheese"); + done(err); + }); + }, 50); + }); + + after(function(done) { + fs.unlink("test.log", function (err) { done(); }); + }); + + it('should set up third-party appenders', function() { + var events = [], log4js_sandbox = sandbox.require( + '../lib/log4js', + { + requires: { + 'cheese': { + configure: function() { + return function(evt) { events.push(evt); }; + } + } + } + } + ); + log4js_sandbox.configure({ + appenders: { + "thing": { type: "cheese" } + }, + categories: { + default: { level: "DEBUG", appenders: [ "thing" ] } + } + }); + log4js_sandbox.getLogger().info("edam"); + + events.should.have.length(1); + events[0].data[0].should.eql("edam"); + + }); + + it('should complain about unknown appenders', function() { + (function() { + log4js.configure({ + appenders: { + "thing": { type: "madeupappender" } + }, + categories: { + default: { level: "DEBUG", appenders: [ "thing" ] } + } + }); + }).should.throw( + "Could not load appender of type 'madeupappender'." + ); + }); + + it('should read config from a file', function() { + var events = [], log4js_sandbox = sandbox.require( + '../lib/log4js', + { requires: + { + 'cheese': { + configure: function() { + return function(event) { events.push(event); }; + } + } + } + } + ); + + log4js_sandbox.configure(__dirname + "/with-cheese.json"); + log4js_sandbox.getLogger().debug("gouda"); + + events.should.have.length(1); + events[0].data[0].should.eql("gouda"); + }); + + it('should set up log levels for categories', function() { + var events = [] + , noisyLogger + , log4js_sandbox = sandbox.require( + '../lib/log4js', + { requires: + { + 'cheese': { + configure: function() { + return function(event) { events.push(event); }; + } + } + } + } + ); + + log4js_sandbox.configure(__dirname + "/with-cheese.json"); + noisyLogger = log4js_sandbox.getLogger("noisy"); + noisyLogger.debug("pow"); + noisyLogger.info("crash"); + noisyLogger.warn("bang"); + noisyLogger.error("boom"); + noisyLogger.fatal("aargh"); + + events.should.have.length(2); + events[0].data[0].should.eql("boom"); + events[1].data[0].should.eql("aargh"); + + }); + + it('should have a default log level for all categories', function() { + var events = [] + , log4js_sandbox = sandbox.require( + '../lib/log4js', + { requires: + { + 'cheese': { + configure: function() { + return function(event) { events.push(event); }; + } + } + } + } + ); + + //with-cheese.json only specifies categories noisy and default + //unspecified categories should use the default category config + log4js_sandbox.configure(__dirname + "/with-cheese.json"); + log4js_sandbox.getLogger("surprise").trace("not seen"); + log4js_sandbox.getLogger("surprise").info("should be seen"); + + events.should.have.length(1); + events[0].data[0].should.eql("should be seen"); + + }); + + it('should reload configuration if specified'); + }); +});