diff --git a/README.md b/README.md new file mode 100644 index 0000000..29a7e59 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# log4js-node + +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. 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). + +## installation + +npm install log4js + +## tests + +Tests now use [vows](http://vowsjs.org), run with `vows test/logging.js`. + +## usage + +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 + +See example.js: + + var log4js = require('log4js')(); //note the need to call the function + log4js.addAppender(log4js.consoleAppender()); + log4js.addAppender(log4js.fileAppender('logs/cheese.log'), 'cheese'); + + var logger = log4js.getLogger('cheese'); + logger.setLevel('ERROR'); + + logger.trace('Entering cheese testing'); + logger.debug('Got cheese.'); + logger.info('Cheese is Gouda.'); + logger.warn('Cheese is quite smelly.'); + logger.error('Cheese is too ripe!'); + logger.fatal('Cheese was breeding ground for listeria.'); + +Output + [2010-01-17 11:43:37.987] [ERROR] cheese - Cheese is too ripe! + [2010-01-17 11:43:37.990] [FATAL] cheese - Cheese was breeding ground for listeria. + + +## 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`. 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 + +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. + +## author (of this node version) + +Gareth Jones (csausdev - gareth.jones@sensis.com.au) + +## License + +The original log4js was distributed under the Apache 2.0 License, and so is this. I've tried to +keep the original copyright and author credits in place, except in sections that I have rewritten +extensively. diff --git a/example.js b/example.js new file mode 100644 index 0000000..66d9ce4 --- /dev/null +++ b/example.js @@ -0,0 +1,13 @@ +var log4js = require('./lib/log4js')(); +log4js.addAppender(log4js.consoleAppender()); +log4js.addAppender(log4js.fileAppender('cheese.log'), 'cheese'); + +var logger = log4js.getLogger('cheese'); +logger.setLevel('ERROR'); + +logger.trace('Entering cheese testing'); +logger.debug('Got cheese.'); +logger.info('Cheese is Gouda.'); +logger.warn('Cheese is quite smelly.'); +logger.error('Cheese is too ripe!'); +logger.fatal('Cheese was breeding ground for listeria.'); diff --git a/lib/log4js.js b/lib/log4js.js new file mode 100644 index 0000000..3785b84 --- /dev/null +++ b/lib/log4js.js @@ -0,0 +1,731 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*jsl:option explicit*/ + +/** + * @fileoverview log4js is a library to log in JavaScript in similar manner + * than in log4j for Java. The API should be nearly the same. + * + * This file contains all log4js code and is the only file required for logging. + * + *
+ * 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
+ * logging.addAppender(logging.fileAppender("file.log"), "some-category");
+ * //get a logger
+ * var log = logging.getLogger("some-category");
+ * log.setLevel(logging.levels.TRACE); //set the Level
+ *
+ * ...
+ *
+ * //call the log
+ * log.trace("trace me" );
+ *
+ *
+ * @version 1.0
+ * @author Stephan Strittmatter - http://jroller.com/page/stritti
+ * @author Seth Chisamore - http://www.chisamore.com
+ * @since 2005-05-20
+ * @static
+ * Website: http://log4js.berlios.de
+ */
+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);
+ },
+ "console": function(config, fileAppender, consoleAppender) {
+ var layout;
+ if (config.layout) {
+ layout = layoutMakers[config.layout.type](config.layout);
+ }
+ return consoleAppender(layout);
+ },
+ "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];
+}
+
+/**
+ * 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) {
+ 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);
+ }
+ });
+ 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 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];
+ }
+ }
+ 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
+ */
+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; +} + +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; + }; + +}; + + +module.exports = function (fileSystem, standardOutput, configPaths) { + var fs = fileSystem || require('fs'), + standardOutput = standardOutput || sys.puts, + configPaths = configPaths || require.paths; + + + function consoleAppender (layout) { + layout = layout || colouredLayout; + return function(loggingEvent) { + standardOutput(layout(loggingEvent)); + }; + } + + /** + * 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, 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); + + 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) { + //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) + 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 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() { + //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 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); + + } + + //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, + getDefaultLogger: getDefaultLogger, + + addAppender: addAppender, + clearAppenders: clearAppenders, + configure: configure, + + levels: levels, + + consoleAppender: consoleAppender, + fileAppender: fileAppender, + logLevelFilter: logLevelFilter, + + basicLayout: basicLayout, + messagePassThroughLayout: messagePassThroughLayout, + patternLayout: patternLayout, + colouredLayout: colouredLayout, + coloredLayout: colouredLayout + }; +} + + +Date.ISO8601_FORMAT = "yyyy-MM-dd hh:mm:ss.SSS"; +Date.ISO8601_WITH_TZ_OFFSET_FORMAT = "yyyy-MM-ddThh:mm:ssO"; +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; + + 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) + .replace(/hh/g, vHour) + .replace(/mm/g, vMinute) + .replace(/ss/g, vSecond) + .replace(/SSS/g, vMillisecond) + .replace(/O/g, vTimeZone); + return formatted; + + function padWithZeros(vNumber, width) { + var numAsString = vNumber + ""; + while (numAsString.length < width) { + numAsString = "0" + numAsString; + } + return numAsString; + } + + 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; + } +}; + diff --git a/lib/log4js.json b/lib/log4js.json new file mode 100644 index 0000000..7b6d3e7 --- /dev/null +++ b/lib/log4js.json @@ -0,0 +1,7 @@ +{ + "appenders": [ + { + "type": "console" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..04df4df --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "log4js", + "version": "0.2.4", + "description": "Port of Log4js to work with node.", + "keywords": [ + "logging", + "log", + "log4j", + "node" + ], + "main": "./lib/log4js", + "author": "Gareth Jones