From 360ef75549ca944b89b8a24fe074068168bea5df Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Wed, 5 Sep 2012 13:04:18 +0200 Subject: [PATCH] Initial commit --- .gitignore | 4 + .npmignore | 5 + .travis.yml | 4 + LICENSE | 25 ++++ README.md | 3 + blammo.xml | 33 ++++++ example.js | 14 +++ index.js | 6 + lib/appender.js | 43 +++++++ lib/configuration_reader.js | 213 +++++++++++++++++++++++++++++++++++ lib/console_appender.js | 31 +++++ lib/dummy_encoder.js | 19 ++++ lib/file_appender.js | 33 ++++++ lib/filter_reply.js | 8 ++ lib/index.js | 20 ++++ lib/json_layout.js | 22 ++++ lib/level_filter.js | 24 ++++ lib/levels.js | 25 ++++ lib/logger.js | 154 +++++++++++++++++++++++++ lib/logger_factory.js | 52 +++++++++ lib/logging_event.js | 36 ++++++ lib/pattern_layout.js | 33 ++++++ lib/rolling_file_appender.js | 32 ++++++ lib/threshold_filter.js | 24 ++++ lib/utils.js | 13 +++ package.json | 36 ++++++ run_sonar.sh | 18 +++ sonar-project.properties | 22 ++++ test/level_filter.js | 25 ++++ test/logger.js | 188 +++++++++++++++++++++++++++++++ test/pattern_layout.js | 57 ++++++++++ test/test_appender.js | 22 ++++ test/threshold_filter.js | 25 ++++ 33 files changed, 1269 insertions(+) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 blammo.xml create mode 100644 example.js create mode 100644 index.js create mode 100644 lib/appender.js create mode 100644 lib/configuration_reader.js create mode 100644 lib/console_appender.js create mode 100644 lib/dummy_encoder.js create mode 100644 lib/file_appender.js create mode 100644 lib/filter_reply.js create mode 100644 lib/index.js create mode 100644 lib/json_layout.js create mode 100644 lib/level_filter.js create mode 100644 lib/levels.js create mode 100644 lib/logger.js create mode 100644 lib/logger_factory.js create mode 100644 lib/logging_event.js create mode 100644 lib/pattern_layout.js create mode 100644 lib/rolling_file_appender.js create mode 100644 lib/threshold_filter.js create mode 100644 lib/utils.js create mode 100644 package.json create mode 100755 run_sonar.sh create mode 100644 sonar-project.properties create mode 100644 test/level_filter.js create mode 100644 test/logger.js create mode 100644 test/pattern_layout.js create mode 100644 test/test_appender.js create mode 100644 test/threshold_filter.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9b23d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.swp +.sonar/ +coverage/ +node_modules/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..e71aa9c --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +node_modules/ +run_sonar.hs +sonar-runner.properties +coverage +.sonar diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..895dbd3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.6 + - 0.8 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2ca911c --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright 2011 Steven Looman. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY STEVEN LOOMAN ''AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL STEVEN LOOMAN OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the +authors and should not be interpreted as representing official policies, either expressed +or implied, of Steven Looman. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d96071c --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +Blammo +====== +Blammo - the Log-ger for NodeJS! diff --git a/blammo.xml b/blammo.xml new file mode 100644 index 0000000..8b3c490 --- /dev/null +++ b/blammo.xml @@ -0,0 +1,33 @@ + + + + %timestamp|%pid|%logger|%level|%message + + + + + %timestamp|%pid|%logger|%level|%message + + + /tmp/blammo.log + + + + + + + + + /tmp/blammo.json + + + + + + + + + + + + diff --git a/example.js b/example.js new file mode 100644 index 0000000..b32f8e3 --- /dev/null +++ b/example.js @@ -0,0 +1,14 @@ +var blammo = require('./'); + +function main() { + var logger1 = blammo.LoggerFactory.getLogger('logger1'); + var logger2 = blammo.LoggerFactory.getLogger('logger2'); + + logger1.debug('test debug message'); + logger2.debug('test debug message'); + + logger1.error('test error message'); + logger2.error('test error message'); +} + +main(); diff --git a/index.js b/index.js new file mode 100644 index 0000000..3e686a0 --- /dev/null +++ b/index.js @@ -0,0 +1,6 @@ +/** + * Export lib/ + * + */ + +module.exports = require('./lib'); diff --git a/lib/appender.js b/lib/appender.js new file mode 100644 index 0000000..159eb7f --- /dev/null +++ b/lib/appender.js @@ -0,0 +1,43 @@ +var FilterReply = require('./filter_reply'); + + +function Appender() { +} + + +Appender.prototype.addFilter = function(filter) { + this.filters.push(filter); +}; + +Appender.prototype.shouldAppend = function(le) { + var i; + var filter, reply; + for (i = 0; i < this.filters.length; ++i) { + filter = this.filters[i]; + reply = filter.decide(le); + + if (reply === FilterReply.ACCEPT) { + return true; + } else if (reply === FilterReply.DENY) { + return false; + } + } + + return true; +}; + +Appender.prototype.doAppend = function(le) { + if (!this.shouldAppend(le)) { + return; + } + + this.append(le); +}; + + +Appender.prototype.getName = function() { + return this.name; +}; + + +module.exports = Appender; diff --git a/lib/configuration_reader.js b/lib/configuration_reader.js new file mode 100644 index 0000000..6b33cda --- /dev/null +++ b/lib/configuration_reader.js @@ -0,0 +1,213 @@ +var fs = require('fs'); +var DOMParser = require('xmldom').DOMParser; + +var LoggerFactory = require('./logger_factory'); +var Logger = require('./logger'); +var Levels = require('./levels'); + + +var knownAppenders = { + 'console_appender' : require('./console_appender'), + 'file_appender' : require('./file_appender'), + 'rolling_file_appender': require('./rolling_file_appender') +}; + +var knownEncoders = { + 'dummy_encoder': require('./dummy_encoder') +}; + +var knownLayouts = { + 'pattern_layout': require('./pattern_layout'), + 'json_layout' : require('./json_layout') +}; + +var knownFilters = { + 'level_filter' : require('./level_filter'), + 'threshold_filter': require('./threshold_filter') +}; + + +/** + * Read the confugration file and construct the Appenders/Loggers + */ +function readConfiguration(filename) { + var xml = fs.readFileSync(filename).toString(); + var doc = new DOMParser().parseFromString(xml); + var root = doc.firstChild; + + return parseConfiguration(root); +} + + +function getChildrenByTagName(node, name) { + var children = []; + + var i; + var child; + for (i = 0; i < node.childNodes.length; ++i) { + child = node.childNodes.item(i); + if (child.tagName === name) { + children.push(child); + } + } + + return children; +} + +function getChildByTagName(node, name) { + var i; + var child; + for (i = 0; i < node.childNodes.length; ++i) { + child = node.childNodes.item(i); + if (child.tagName === name) { + return child; + } + } +} + + +function parseConfiguration(node) { + var appenderNodes = getChildrenByTagName(node, 'appender'); + var appenders = parseAppenders(appenderNodes); + + var loggerNodes = getChildrenByTagName(node, 'logger'); + var loggers = parseLoggers(loggerNodes, appenders); + + var rootNode = getChildByTagName(node, 'root'); + var root = parseRoot(rootNode, appenders); + + return root; +} + + +function parseAppenders(appenderNodes) { + var appenders = {}; + + appenderNodes.forEach(function(appenderNode) { + appender = parseAppender(appenderNode); + appenders[appender.name] = appender; + }); + + return appenders; +} + +function parseAppender(node) { + var name = node.getAttribute('name'); + var type = node.getAttribute('type'); + + var encoderNode = getChildByTagName(node, 'encoder'); + var encoder = parseEncoder(encoderNode); + + var layoutNode = getChildByTagName(node, 'layout'); + var layout = parseLayout(layoutNode); + + var filtersNode = getChildByTagName(node, 'filters'); + var filters = parseFilters(filtersNode); + + var configNode = getChildByTagName(node, 'config'); + var config = {}; + if (configNode) { + config = parseAppenderConfig(configNode); + } + + return new knownAppenders[type](name, encoder, layout, config); +} + +function parseEncoder(node) { + var type = node.getAttribute('type'); + + return new knownEncoders[type](); +} + +function parseLayout(node) { + var type = node.getAttribute('type'); + var pattern = node.textContent; + + return new knownLayouts[type](pattern); +} + +function parseFilters(node) { + if (!node) { + return []; + } + + var filterNodes = getChildrenByTagName(node, 'filter'); + + var filters = []; + filterNodes.forEach(function(filterNode) { + var filter = parseFilter(filterNode); + filters.append(filter); + }); + + return filters; +} + +function parseFilter(node) { + var type = node.getAttribute('type'); + var param = node.textContent; + + return new knownFilters[type](param); +} + +function parseAppenderConfig(node) { + var config = {}; + + var i, child, key, value; + for (i = 0; i < node.childNodes.length; ++i) { + child = node.childNodes.item(i); + + key = child.tagName; + value = child.textContent; + + if (key) { + config[key] = value; + } + } + + return config; +} + + +function parseLoggers(loggerNodes, appenders) { + loggerNodes.forEach(function(loggerNode) { + parseLogger(loggerNode, appenders); + }); +} + +function parseLogger(node, appenders) { + var name = node.getAttribute('name'); + var logger = LoggerFactory.getLogger(name); + + return parseLogger2(logger, node, appenders); +} + +function parseRoot(node, appenders) { + var root = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + + return parseLogger2(root, node, appenders); +} + +function parseLogger2(logger, node, appenders) { + var levelString = (node.getAttribute('level') || 'debug').toUpperCase(); + var level = Levels[levelString]; + logger.setLevel(level); + + var appenderRefs = getChildrenByTagName(node, 'appender-ref'); + appenderRefs.forEach(function(appenderRefNode) { + var ref = appenderRefNode.getAttribute('ref'); + var appender = appenders[ref]; + logger.addAppender(appender); + }); + + return logger; +} + + +function doRead() { + var configFile = process.env.BLAMMO_CONFIG || 'blammo.xml'; + if (fs.existsSync(configFile)) { + readConfiguration(configFile); + } +} + +doRead(); diff --git a/lib/console_appender.js b/lib/console_appender.js new file mode 100644 index 0000000..90a0f88 --- /dev/null +++ b/lib/console_appender.js @@ -0,0 +1,31 @@ +var util = require('util'); + +var Appender = require('./appender'); + + +/** + * Build a ConsoleAppender + * + * Appends to the console + */ +function ConsoleAppender(name, encoder, layout) { + this.name = name; + this.encoder = encoder; + this.layout = layout; + + this.filters = []; +} + + +util.inherits(ConsoleAppender, Appender); + + +ConsoleAppender.prototype.append = function(e) { + var str = this.layout.doLayout(e); + var encodedStr = this.encoder.doEncode(str); + + console.log(encodedStr); +}; + + +module.exports = ConsoleAppender; diff --git a/lib/dummy_encoder.js b/lib/dummy_encoder.js new file mode 100644 index 0000000..7fda83b --- /dev/null +++ b/lib/dummy_encoder.js @@ -0,0 +1,19 @@ +/** + * Build a dummy encoder + * + * Does nothing + */ +function DummyEncoder() { +} + + +DummyEncoder.prototype.doEncode = function(str) { + return this.encode(str); +}; + +DummyEncoder.prototype.encode = function(str) { + return str; +}; + + +module.exports = DummyEncoder; diff --git a/lib/file_appender.js b/lib/file_appender.js new file mode 100644 index 0000000..3768524 --- /dev/null +++ b/lib/file_appender.js @@ -0,0 +1,33 @@ +var util = require('util'); +var fs = require('fs'); + +var Appender = require('./appender'); + + +/** + * Build a FileAppender + * + * Appends to the configured filename + */ +function FileAppender(name, encoder, layout, config) { + this.name = name; + this.encoder = encoder; + this.layout = layout; + this.filename = config.filename; + + this.filters = []; +} + + +util.inherits(FileAppender, Appender); + + +FileAppender.prototype.append = function(e) { + var str = this.layout.doLayout(e); + var encodedStr = this.encoder.doEncode(str) + '\n'; + + fs.appendFileSync(this.filename, encodedStr); +}; + + +module.exports = FileAppender; diff --git a/lib/filter_reply.js b/lib/filter_reply.js new file mode 100644 index 0000000..8bad269 --- /dev/null +++ b/lib/filter_reply.js @@ -0,0 +1,8 @@ +var FilterReply = { + ACCEPT : 1, + NEUTRAL : 0, + DENY : -1 +}; + + +module.exports = FilterReply; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..3db53f9 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,20 @@ +module.exports.LoggerFactory = require('./logger_factory'); +module.exports.Levels = require('./levels'); +module.exports.Logger = require('./logger'); +module.exports.LoggingEvent = require('./logging_event'); + +module.exports.Appender = require('./appender'); +module.exports.ConsoleAppender = require('./console_appender'); +module.exports.FileAppender = require('./file_appender'); +module.exports.RollingFileAppender = require('./rolling_file_appender'); + +module.exports.DummyEncoder = require('./dummy_encoder'); + +module.exports.PatternLayout = require('./pattern_layout'); +module.exports.JsonLayout = require('./json_layout'); + +module.exports.FilterReply = require('./filter_reply'); +module.exports.LevelFilter = require('./level_filter'); +module.exports.ThresholdFilter = require('./threshold_filter'); + +require('./configuration_reader'); diff --git a/lib/json_layout.js b/lib/json_layout.js new file mode 100644 index 0000000..b5d434e --- /dev/null +++ b/lib/json_layout.js @@ -0,0 +1,22 @@ +/** + * JSON layout + */ +function JsonLayout() { +} + + +/** + * Copy the 'visible' parts of the LoggingEvent and stringify it + */ +JsonLayout.prototype.doLayout = function(le) { + var o = { + timestamp: le.timestamp, + logger: le.logger, + level: le.level, + message: le.message + }; + return JSON.stringify(o); +}; + + +module.exports = JsonLayout; diff --git a/lib/level_filter.js b/lib/level_filter.js new file mode 100644 index 0000000..6830eea --- /dev/null +++ b/lib/level_filter.js @@ -0,0 +1,24 @@ +var FilterReply = require('./filter_reply'); +var Levels = require('./levels'); + + +/** + * Build a Level Filter + * + * ACCEPTs any log message which is of the configured level + */ +function LevelFilter(level) { + this.level = level; +} + + +LevelFilter.prototype.decide = function(le) { + if (this.level === le.levelValue) { + return FilterReply.ACCEPT; + } + + return FilterReply.NEUTRAL; +}; + + +module.exports = LevelFilter; diff --git a/lib/levels.js b/lib/levels.js new file mode 100644 index 0000000..117b5d1 --- /dev/null +++ b/lib/levels.js @@ -0,0 +1,25 @@ +var LogLevels = { + 0 : 'ALL', + ALL : 0, + + 10 : 'TRACE', + TRACE : 10, + + 20 : 'DEBUG', + DEBUG : 20, + + 30 : 'INFO', + INFO : 30, + + 40 : 'WARN', + WARN : 40, + + 50 : 'ERROR', + ERROR : 50, + + 60 : 'OFF', + OFF : 60 +}; + + +module.exports = LogLevels; diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 0000000..6709179 --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,154 @@ +var LoggingEvent = require('./logging_event'); +var Levels = require('./levels'); + + +/** + * Construct a new logger + */ +function Logger(name, parent) { + this.name = name; + this.parent = parent; + + this.appenders = []; + this.additive = true; +} + + +Logger.ROOT_LOGGER_NAME = 'ROOT'; + + +/** + * Set the new effective level for this Logger + */ +Logger.prototype.setLevel = function(level) { + this.level = level; +}; + +/** + * Get the effective level for this logger + */ +Logger.prototype.getLevel = function() { + if (this.level === undefined) { + return this.parent.getLevel(); + } + + return this.level; +}; + + +/** + * Add an Appender to this Logger + */ +Logger.prototype.addAppender = function(appender) { + this.appenders.push(appender); +}; + +/** + * Detach a (single) Appender from this Logger + */ +Logger.prototype.detachAppender = function(appender) { + var index = this.appenders.indexOf(appender); + if (index !== -1) { + this.appenders.splice(index, 1); + } +}; + +/** + * Get an Appender by name + */ +Logger.prototype.getAppender = function(name) { + var namedAppenders = this.getNamedAppenders(); + return namedAppenders[name]; +}; + +/** + * Get the appenders for this Logger + */ +Logger.prototype.getAppenders = function() { + var namedAppenders = this.getNamedAppenders(); + var appenders = []; + + var name, appender; + for (name in namedAppenders) { + if (namedAppenders.hasOwnProperty(name)) { + appender = namedAppenders[name]; + appenders.push(appender); + } + } + + return appenders; +}; + +/** + * Get the appenders by name for this Logger + */ +Logger.prototype.getNamedAppenders = function() { + var appenders = {}; + + var p = this; + var f = function(appender) { + appenders[appender.name] = appender; + }; + while (p) { + p.appenders.forEach(f); + + if (p.additive) { + p = p.parent; + } else { + break; + } + } + + return appenders; +}; + + +Logger.prototype.log = function(level, message) { + if (this.getLevel() > level) { + return; + } + + var le = new LoggingEvent(this.name, level, message); + + this.getAppenders().forEach(function(appender) { + appender.doAppend(le); + }); +}; + +/** + * Log a TRACE message + */ +Logger.prototype.trace = function() { + this.log(Levels.TRACE, arguments); +}; + +/** + * Log a DEBUG message + */ +Logger.prototype.debug = function() { + this.log(Levels.DEBUG, arguments); +}; + +/* + * Log a INFO message + */ +Logger.prototype.info = function() { + this.log(Levels.INFO, arguments); +}; + +/** + * Log a WARN message + */ +Logger.prototype.warn = function() { + this.log(Levels.WARN, arguments); +}; + +/** + * Log a ERROR message + */ +Logger.prototype.error = function() { + this.log(Levels.ERROR, arguments); +}; + + +module.exports = Logger; diff --git a/lib/logger_factory.js b/lib/logger_factory.js new file mode 100644 index 0000000..b8af079 --- /dev/null +++ b/lib/logger_factory.js @@ -0,0 +1,52 @@ +var Logger = require('./logger'); + + +function LoggerFactory() { + throw new Error("No instances thank you"); +} + + +var loggers = {}; + + +/** + * Get a logger by its name + * + * If the logger does not exist, create it + */ +LoggerFactory.getLogger = function(name) { + if (!loggers[name]) { + // find parent + var parent = LoggerFactory.getParent(name); + + // duplicate parent, or duplicate root if no parent found + var logger = new Logger(name); + loggers[name] = logger; + logger.parent = parent; + } + + return loggers[name]; +}; + + +/** + * Get the first parent/ancestor from a logger by its name + * + * Name of the loggers should be in the form of: ancestor.parent.logger... + */ +LoggerFactory.getParent = function(name) { + var elements = name.split('.'); + + var i; + for (i = 1; i < elements.length; ++i) { + var parentName = elements.slice(0, -i).join('.'); + if (loggers[parentName]) { + return loggers[parentName]; + } + } + + return loggers[Logger.ROOT_LOGGER_NAME]; +}; + + +module.exports = LoggerFactory; diff --git a/lib/logging_event.js b/lib/logging_event.js new file mode 100644 index 0000000..d14fba1 --- /dev/null +++ b/lib/logging_event.js @@ -0,0 +1,36 @@ +var Logger = require('./logger'); +var Levels = require('./levels'); +var utils = require('./utils'); + + +/** + * Build a LoggingEvent + * + * Timestamp is added from current dat in the form of YYYY-MM-DD hh:mm:ss.ms + */ +function LoggingEvent(logger, level, message) { + this.logger = logger; + this.levelValue = level; + this.message = Array.prototype.slice.call(message).join(''); // XXX: HACKetyhack + + this._timestamp = new Date(); + this.timestamp = LoggingEvent.buildTimestampString(this._timestamp); + this.pid = process.pid; + + this.level = Levels[level]; +} + + + +LoggingEvent.buildTimestampString = function(timestamp) { + var str = ''; + + str += timestamp.getFullYear() + '-' + utils.fill(timestamp.getMonth() + 1, 2, '0') + '-' + utils.fill(timestamp.getDate(), 2, '0'); + str += ' '; + str += utils.fill(timestamp.getHours(), 2, '0') + ':' + utils.fill(timestamp.getMinutes(), 2, '0') + ':' + utils.fill(timestamp.getSeconds(), 2, '0') + '.' + utils.fill(timestamp.getMilliseconds(), 3, '0'); + + return str; +}; + + +module.exports = LoggingEvent; diff --git a/lib/pattern_layout.js b/lib/pattern_layout.js new file mode 100644 index 0000000..f492956 --- /dev/null +++ b/lib/pattern_layout.js @@ -0,0 +1,33 @@ +/** + * Build the Pattern Layout + * + * Possible tokens for the layout: + * - %timestamp + * - %logger + * - %level + * - %message + */ +function PatternLayout(pattern) { + this.layout = this.buildLayoutFunction(pattern); +} + + +/** + * Build the layout function + */ +PatternLayout.prototype.buildLayoutFunction = function(pattern) { + var getToken = function(match, name, offset) { + return '" + le[\'' + name + '\'] + "'; + }; + + var js = 'return "' + pattern.replace(/%(\w+)/g, getToken) + '";'; + return new Function('le', js); +}; + + +PatternLayout.prototype.doLayout = function(le) { + return this.layout(le); +}; + + +module.exports = PatternLayout; diff --git a/lib/rolling_file_appender.js b/lib/rolling_file_appender.js new file mode 100644 index 0000000..ba4e119 --- /dev/null +++ b/lib/rolling_file_appender.js @@ -0,0 +1,32 @@ +var util = require('util'); +var fs = require('fs'); + +var Appender = require('./appender'); +var utils = require('./utils'); + + +function RollingFileAppender(name, encoder, layout, config) { + this.name = name; + this.encoder = encoder; + this.layout = layout; + this.filename = config.filename; + + this.filters = []; +} + + +util.inherits(RollingFileAppender, Appender); + + +RollingFileAppender.prototype.append = function(e) { + var str = this.layout.doLayout(e); + var encodedStr = this.encoder.doEncode(str) + '\n'; + + var dateSuffix = e._timestamp.getFullYear() + '-' + utils.fill(e._timestamp.getMonth() + 1, 2, '0') + '-' + utils.fill(e._timestamp.getDate(), 2, '0'); + var filename = this.filename + '_' + dateSuffix; + + fs.appendFileSync(filename, encodedStr); +}; + + +module.exports = RollingFileAppender; diff --git a/lib/threshold_filter.js b/lib/threshold_filter.js new file mode 100644 index 0000000..00cc944 --- /dev/null +++ b/lib/threshold_filter.js @@ -0,0 +1,24 @@ +var FilterReply = require('./filter_reply'); +var Levels = require('./levels'); + + +/** + * Build a Threshold Filter + * + * DENYs all mesasges which are above the configured level + */ +function ThresholdFilter(level) { + this.level = level; +} + + +ThresholdFilter.prototype.decide = function(le) { + if (this.level > le.levelValue) { + return FilterReply.DENY; + } + + return FilterReply.NEUTRAL; +}; + + +module.exports = ThresholdFilter; diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..ec435d2 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,13 @@ +module.exports.fill = function(val, width, filler) { + if (!filler) { + filler = ' '; + } + + width -= val.toString().length; + + if (width > 0) { + return new Array(width + (/\./.test(val) ? 2 : 1)).join(filler) + val; + } + + return val.toString(); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..d7320f6 --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "blammo", + "description": "Blammo! Logger for NodeJS - built after LogBack for Java", + "version": "0.4.0", + "author": "Steven Looman ", + "keywords": [ "logger", "logging", "log" ], + "licenses" : [ + { + "type": "2-clause BSD", + "url": "https://raw.github.com/StevenLooman/blammo/master/LICENSE" + } + ], + + "dependencies": { + "xmldom": "0.1.11" + }, + "devDependencies": { + "mocha": "1.2.2", + "mocha-lcov-reporter": "0.0.1" + }, + "directories": { + "lib": "./lib", + "test": "./test" + }, + "main": "index.js", + "engines": { + "node": ">= 0.6.0" + }, + "scripts": { + "test": "mocha test/*" + }, + "repository": { + "type": "git", + "url": "git://github.com/StevenLooman/blammo.git" + } +} diff --git a/run_sonar.sh b/run_sonar.sh new file mode 100755 index 0000000..c269666 --- /dev/null +++ b/run_sonar.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +rm -rf coverage +rm -rf lib-cov + +mkdir coverage + +echo Running mocha +node-jscoverage lib lib-cov +mv lib lib-orig +mv lib-cov lib +mocha -R mocha-lcov-reporter > coverage/coverage.lcov +mocha -R xunit > coverage/TEST-all.xml +rm -rf lib +mv lib-orig lib + +echo Running sonar-runner +sonar-runner diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..b9b819a --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,22 @@ +# project metadata (required) +sonar.projectKey=js.blammo +sonar.projectName=Blammo +sonar.projectVersion=0.4.0 + +# path to source directories (required) +sources=lib + +# path to test source directories (optional) +tests=test + +# The value of the property must be the key of the language. +sonar.language=js + +# Advanced parameters +sonar.javascript.jstestdriver.reportsfolder=coverage +sonar.javascript.jstestdriver.coveragefile=coverage.lcov +sonar.dynamicAnalysis=reuseReports + +# SCM plugin +sonar.scm.enabled=true +sonar.scm.url=scm:git:git://github.com/StevenLooman/blammo.git diff --git a/test/level_filter.js b/test/level_filter.js new file mode 100644 index 0000000..3c12921 --- /dev/null +++ b/test/level_filter.js @@ -0,0 +1,25 @@ +var blammo = require('..'); +var assert = require('assert'); + + +describe('LevelFilter', function() { + describe('#decide()', function() { + it('should reply neutral when the log level is not equal to the set level', function() { + var filter = new blammo.LevelFilter(blammo.Levels.TRACE); + var le = new blammo.LoggingEvent('test', blammo.Levels.DEBUG, 'dummy message'); + + var r = filter.decide(le); + + assert.equal(r, blammo.FilterReply.NEUTRAL); + }); + + it('should reply accept when the log level is equal to the set level', function() { + var filter = new blammo.LevelFilter(blammo.Levels.WARN); + var le = new blammo.LoggingEvent('test', blammo.Levels.WARN, 'dummy message'); + + var r = filter.decide(le); + + assert.equal(r, blammo.FilterReply.ACCEPT); + }); + }); +}); diff --git a/test/logger.js b/test/logger.js new file mode 100644 index 0000000..af91cad --- /dev/null +++ b/test/logger.js @@ -0,0 +1,188 @@ +var blammo = require('..'); +var assert = require('assert'); + +var TestAppender = require('./test_appender'); + + +describe('Logger', function() { + describe('#trace()', function() { + it('should log when the level is low enough', function() { + var logger = new blammo.Logger('test'); + var appender = new TestAppender(); + logger.addAppender(appender); + logger.setLevel(blammo.Levels.TRACE); + + logger.trace('test message'); + + assert.ok(appender.events.length === 1); + }); + + it('should not log when the level is too high', function() { + var logger = new blammo.Logger('test'); + var appender = new TestAppender(); + logger.addAppender(appender); + logger.setLevel(blammo.Levels.OFF); + + logger.trace('test message'); + + assert.ok(appender.events.length === 0); + }); + }); + + describe('#debug()', function() { + it('should log when the level is low enough', function() { + var logger = new blammo.Logger('test'); + var appender = new TestAppender(); + logger.addAppender(appender); + logger.setLevel(blammo.Levels.DEBUG); + + logger.debug('test message'); + + assert.ok(appender.events.length === 1); + }); + + it('should not log when the level is too high', function() { + var logger = new blammo.Logger('test'); + var appender = new TestAppender(); + logger.addAppender(appender); + logger.setLevel(blammo.Levels.OFF); + + logger.debug('test message'); + + assert.ok(appender.events.length === 0); + }); + }); + + describe('#info()', function() { + it('should log when the level is low enough', function() { + var logger = new blammo.Logger('test'); + var appender = new TestAppender(); + logger.addAppender(appender); + logger.setLevel(blammo.Levels.INFO); + + logger.info('test message'); + + assert.ok(appender.events.length === 1); + }); + + it('should not log when the level is too high', function() { + var logger = new blammo.Logger('test'); + var appender = new TestAppender(); + logger.addAppender(appender); + logger.setLevel(blammo.Levels.OFF); + + logger.info('test message'); + + assert.ok(appender.events.length === 0); + }); + }); + + describe('#warn()', function() { + it('should log when the level is low enough', function() { + var logger = new blammo.Logger('test'); + var appender = new TestAppender(); + logger.addAppender(appender); + logger.setLevel(blammo.Levels.WARN); + + logger.warn('test message'); + + assert.ok(appender.events.length === 1); + }); + + it('should not log when the level is too high', function() { + var logger = new blammo.Logger('test'); + var appender = new TestAppender(); + logger.addAppender(appender); + logger.setLevel(blammo.Levels.OFF); + + logger.warn('test message'); + + assert.ok(appender.events.length === 0); + }); + }); + + describe('#error()', function() { + it('should log when the level is low enough', function() { + var logger = new blammo.Logger('test'); + var appender = new TestAppender(); + logger.addAppender(appender); + logger.setLevel(blammo.Levels.ERROR); + + logger.error('test message'); + + assert.ok(appender.events.length === 1); + }); + + it('should not log when the level is too high', function() { + var logger = new blammo.Logger('test'); + var appender = new TestAppender(); + logger.addAppender(appender); + logger.setLevel(blammo.Levels.OFF); + + logger.error('test message'); + + assert.ok(appender.events.length === 0); + }); + }); + + describe('#getAppender()', function() { + it('should get an added appender by name', function() { + var logger = new blammo.Logger('test'); + var appender = new TestAppender('test'); + logger.addAppender(appender); + + var r = logger.getAppender('test'); + assert.equal(r.name, appender.name); + }); + }); + + describe('log level inheritance', function() { + it('should inherit the parents level if no level is set (effective level)', function() { + var logger1 = new blammo.Logger('a'); + logger1.setLevel(blammo.Levels.WARN); + var logger2 = new blammo.Logger('a.b'); + logger2.parent = logger1; + + assert.equal(logger2.getLevel(), blammo.Levels.WARN); + }); + + it('should use its own level if it is set', function() { + var logger1 = new blammo.Logger('a'); + logger1.setLevel(blammo.Levels.WARN); + var logger2 = new blammo.Logger('a.b'); + logger2.parent = logger1; + logger2.setLevel(blammo.Levels.ERROR); + + assert.equal(logger2.getLevel(), blammo.Levels.ERROR); + }); + }); + + + describe('appender additivity', function() { + it('should include its parents appenders', function() { + var appender = new TestAppender('test_appender'); + var logger1 = new blammo.Logger('a'); + logger1.setLevel(blammo.Levels.DEBUG); + logger1.addAppender(appender); + var logger2 = new blammo.Logger('a.b'); + logger2.parent = logger1; + + var appenders = logger2.getAppenders(); + assert.ok(appenders.length === 1); + }); + + it('should not include its parents appenders when the logger is not additive', function() { + var appender = new TestAppender('test_appender'); + var logger1 = new blammo.Logger('a'); + logger1.setLevel(blammo.Levels.DEBUG); + logger1.addAppender(appender); + var logger2 = new blammo.Logger('a.b'); + logger2.parent = logger1; + logger2.additive = false; + + var appenders = logger2.getAppenders(); + assert.ok(appenders.length === 0); + }); + }); +}); + diff --git a/test/pattern_layout.js b/test/pattern_layout.js new file mode 100644 index 0000000..e63a2a8 --- /dev/null +++ b/test/pattern_layout.js @@ -0,0 +1,57 @@ +var blammo = require('..'); +var assert = require('assert'); + +var TestAppender = require('./test_appender'); + + +describe('PatternLayout', function() { + describe('doLayout()', function() { + it('should layout an acceptable layout', function() { + var layout = new blammo.PatternLayout('%timestamp|%logger|%level|%message'); + var le = new blammo.LoggingEvent('blammo', blammo.Levels.TRACE, 'test message'); + + var result = layout.doLayout(le); + assert.ok(result.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}\|blammo\|TRACE\|test message/)); + }); + + it('should layout timestamp', function() { + var layout = new blammo.PatternLayout('%timestamp'); + var le = new blammo.LoggingEvent('blammo', blammo.Levels.TRACE, 'test message'); + + var result = layout.doLayout(le); + assert.ok(result.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}/)); + }); + + it('should layout pid', function() { + var layout = new blammo.PatternLayout('%pid'); + var le = new blammo.LoggingEvent('blammo', blammo.Levels.TRACE, 'test message'); + + var result = layout.doLayout(le); + assert.ok(result.match(/\d+/)); + }); + + it('should layout level', function() { + var layout = new blammo.PatternLayout('%level'); + var le = new blammo.LoggingEvent('blammo', blammo.Levels.TRACE, 'test message'); + + var result = layout.doLayout(le); + assert.equal(result, 'TRACE'); + }); + + it('should layout messages', function() { + var layout = new blammo.PatternLayout('%message'); + var le = new blammo.LoggingEvent('blammo', blammo.Levels.TRACE, 'test message'); + + var result = layout.doLayout(le); + assert.equal(result, 'test message'); + }); + + it('should layout logger', function() { + var layout = new blammo.PatternLayout('%logger'); + var le = new blammo.LoggingEvent('blammo', blammo.Levels.TRACE, 'test message'); + + var result = layout.doLayout(le); + assert.equal(result, 'blammo'); + }); + }); +}); diff --git a/test/test_appender.js b/test/test_appender.js new file mode 100644 index 0000000..b9c76ae --- /dev/null +++ b/test/test_appender.js @@ -0,0 +1,22 @@ +function TestAppender(name) { + if (name) { + this.name = name; + } else { + this.name = 'TestAppender'; + } + + this.events = []; +} + + +TestAppender.prototype.getName = function() { + return this.name; +}; + + +TestAppender.prototype.doAppend = function(e) { + this.events.push(e); +}; + + +module.exports = TestAppender; diff --git a/test/threshold_filter.js b/test/threshold_filter.js new file mode 100644 index 0000000..99465bc --- /dev/null +++ b/test/threshold_filter.js @@ -0,0 +1,25 @@ +var blammo = require('..'); +var assert = require('assert'); + + +describe('ThresholdFilter', function() { + describe('#decide()', function() { + it('should reply neutral when the log level is high enough', function() { + var filter = new blammo.ThresholdFilter(blammo.Levels.TRACE); + var le = new blammo.LoggingEvent('test', blammo.Levels.DEBUG, 'dummy message'); + + var r = filter.decide(le); + + assert.equal(r, blammo.FilterReply.NEUTRAL); + }); + + it('should reply deny when the log level is too low', function() { + var filter = new blammo.ThresholdFilter(blammo.Levels.WARN); + var le = new blammo.LoggingEvent('test', blammo.Levels.DEBUG, 'dummy message'); + + var r = filter.decide(le); + + assert.equal(r, blammo.FilterReply.DENY); + }); + }); +});