diff --git a/lib/appenders/loggly.js b/lib/appenders/loggly.js index f3ecdbd..c13682b 100644 --- a/lib/appenders/loggly.js +++ b/lib/appenders/loggly.js @@ -5,8 +5,46 @@ var layouts = require('../layouts') , passThrough = layouts.messagePassThroughLayout; +function isAnyObject(value) { + return value != null && (typeof value === 'object' || typeof value === 'function'); +} + +function numKeys(o) { + var res = 0; + for (var k in o) { + if (o.hasOwnProperty(k)) res++; + } + return res; +} + /** - * Loggly Appender. Sends logging events to Loggly using node-loggly + * @param msg - array of args for logging. + * @returns { deTaggedMsg: [], additionalTags: [] } + */ +function processTags(msgListArgs) { + var msgList = (msgListArgs.length === 1 ? [msgListArgs[0]] : Array.apply(null, msgListArgs)); + + return msgList.reduce(function (accum, element, currentIndex, array) { + if (isAnyObject(element) && Array.isArray(element.tags) && numKeys(element) == 1) { + accum.additionalTags = accum.additionalTags.concat(element.tags); + } else { + accum.deTaggedData.push(element); + } + return accum; + }, { deTaggedData: [], additionalTags: [] }); +} + +/** + * Loggly Appender. Sends logging events to Loggly using node-loggly, optionally adding tags. + * + * This appender will scan the msg from the logging event, and pull out any argument of the + * shape `{ tags: [] }` so that it's possibleto add tags in a normal logging call. + * + * For example: + * + * logger.info({ tags: ['my-tag-1', 'my-tag-2'] }, 'Some message', someObj, ...) + * + * And then this appender will remove the tags param and append it to the config.tags. * * @param config object with loggly configuration data * { @@ -21,13 +59,21 @@ function logglyAppender(config, layout) { if(!layout) layout = passThrough; return function(loggingEvent) { - var msg = layout(loggingEvent); + var result = processTags(loggingEvent.data); + var deTaggedData = result.deTaggedData; + var additionalTags = result.additionalTags; + + // Replace the data property with the deTaggedData + loggingEvent.data = deTaggedData; + + var msg = layout(loggingEvent); + client.log({ msg: msg, level: loggingEvent.level.levelStr, category: loggingEvent.categoryName, hostname: os.hostname().toString(), - }); + }, additionalTags); } } diff --git a/test/logglyAppender-test.js b/test/logglyAppender-test.js index 30937dc..688e43e 100644 --- a/test/logglyAppender-test.js +++ b/test/logglyAppender-test.js @@ -1,18 +1,18 @@ "use strict"; var vows = require('vows') -, assert = require('assert') -, log4js = require('../lib/log4js') -, sandbox = require('sandboxed-module') -; + , assert = require('assert') + , log4js = require('../lib/log4js') + , sandbox = require('sandboxed-module') + ; function setupLogging(category, options) { var msgs = []; - + var fakeLoggly = { - createClient: function (options) { + createClient: function(options) { return { config: options, - log: function (msg, tags) { + log: function(msg, tags) { msgs.push({ msg: msg, tags: tags @@ -50,7 +50,7 @@ function setupLogging(category, options) { }); log4js.addAppender(logglyModule.configure(options), category); - + return { logger: log4js.getLogger(category), loggly: fakeLoggly, @@ -61,22 +61,50 @@ function setupLogging(category, options) { } log4js.clearAppenders(); + +function setupTaggedLogging() { + return setupLogging('loggly', { + token: 'your-really-long-input-token', + subdomain: 'your-subdomain', + tags: ['loggly-tag1', 'loggly-tag2', 'loggly-tagn'] + }); +} + vows.describe('log4js logglyAppender').addBatch({ - 'minimal config': { + 'with minimal config': { topic: function() { - var setup = setupLogging('loggly', { - token: 'your-really-long-input-token', - subdomain: 'your-subdomain', - tags: ['loggly-tag1', 'loggly-tag2', 'loggly-tagn'] - }); - - setup.logger.log('trace', 'Log event #1'); + var setup = setupTaggedLogging(); + setup.logger.log('trace', 'Log event #1', 'Log 2', { tags: ['tag1', 'tag2'] }); return setup; }, - 'there should be one message only': function (topic) { - //console.log('topic', topic); + 'has a results.length of 1': function(topic) { assert.equal(topic.results.length, 1); + }, + 'has a result msg with both args concatenated': function(topic) { + assert.equal(topic.results[0].msg.msg, 'Log event #1 Log 2'); + }, + 'has a result tags with the arg that contains tags': function(topic) { + assert.deepEqual(topic.results[0].tags, ['tag1', 'tag2']); } } +}).addBatch({ + 'config with object with tags and other keys': { + topic: function() { + var setup = setupTaggedLogging(); + // ignore this tags object b/c there are 2 keys + setup.logger.log('trace', 'Log event #1', { other: 'other', tags: ['tag1', 'tag2'] }); + return setup; + }, + 'has a results.length of 1': function(topic) { + assert.equal(topic.results.length, 1); + }, + 'has a result msg with the args concatenated': function(topic) { + assert.equal(topic.results[0].msg.msg, + 'Log event #1 { other: \'other\', tags: [ \'tag1\', \'tag2\' ] }'); + }, + 'has a result tags with the arg that contains no tags': function(topic) { + assert.deepEqual(topic.results[0].tags, []); + } + } }).export(module);