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]'; * *
- * 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