diff --git a/examples/slack-appender.js b/examples/slack-appender.js new file mode 100644 index 0000000..4bf1cc5 --- /dev/null +++ b/examples/slack-appender.js @@ -0,0 +1,26 @@ +//Note that hipchat appender needs hipchat-client to work. +//If you haven't got hipchat-client installed, you'll get cryptic +//"cannot find module" errors when using the hipchat appender +var log4js = require('../lib/log4js'); + +log4js.configure({ + "appenders": [ + { + "type" : "slack", + "token": 'TOKEN', + "channel_id": "#CHANNEL", + "username": "USERNAME", + "format": "text", + "category" : "slack", + "icon_url" : "ICON_URL" + } + ] +}); + +var logger = log4js.getLogger("slack"); +logger.warn("Test Warn message"); +logger.info("Test Info message"); +logger.debug("Test Debug Message"); +logger.trace("Test Trace Message"); +logger.fatal("Test Fatal Message"); +logger.error("Test Error Message"); diff --git a/lib/appenders/slack.js b/lib/appenders/slack.js new file mode 100644 index 0000000..da8a2c1 --- /dev/null +++ b/lib/appenders/slack.js @@ -0,0 +1,44 @@ +"use strict"; +var Slack = require('slack-node'); +var layouts = require('../layouts'); +var layout; + +var slack, config; + +function slackAppender(_config, _layout) { + + layout = _layout || layouts.basicLayout; + + return function (loggingEvent) { + + var data = { + channel_id: _config.channel_id, + text: layout(loggingEvent, _config.timezoneOffset), + icon_url: _config.icon_url, + username: _config.username + }; + + slack.api('chat.postMessage', { + channel: data.channel_id, + text: data.text, + icon_url: data.icon_url,username: data.username}, function (err, response) { + if (err) { throw err; } + }); + + }; +} + +function configure(_config) { + + if (_config.layout) { + layout = layouts.layout(_config.layout.type, _config.layout); + } + + slack = new Slack(_config.token); + + return slackAppender(_config, layout); +} + +exports.name = 'slack'; +exports.appender = slackAppender; +exports.configure = configure; diff --git a/package.json b/package.json index 2106e9d..86e2f98 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ }, "dependencies": { "readable-stream": "~1.0.2", - "semver": "~4.3.3" + "semver": "~4.3.3", + "slack-node": "^0.2.0" }, "devDependencies": { "sandboxed-module": "0.1.3", diff --git a/test/slackAppender-test.js b/test/slackAppender-test.js new file mode 100644 index 0000000..aa28f83 --- /dev/null +++ b/test/slackAppender-test.js @@ -0,0 +1,169 @@ +"use strict"; +var vows = require('vows'); +var assert = require('assert'); +var log4js = require('../lib/log4js'); +var sandbox = require('sandboxed-module'); + +function setupLogging(category, options) { + var msgs = []; + + var slackCredentials = { + token: options.token, + channel_id: options.channel_id, + username: options.username, + format: options.format, + icon_url: options.icon_url + }; + var fakeSlack = (function (key) { + function constructor() { + return { + options: key, + api: function (action, data, callback) { + msgs.push(data); + callback(false, {status: "sent"}); + } + } + } + + return constructor(key); + }); + + var fakeLayouts = { + layout: function (type, config) { + this.type = type; + this.config = config; + return log4js.layouts.messagePassThroughLayout; + }, + basicLayout: log4js.layouts.basicLayout, + coloredLayout: log4js.layouts.coloredLayout, + messagePassThroughLayout: log4js.layouts.messagePassThroughLayout + }; + + var fakeConsole = { + errors: [], + logs: [], + error: function (msg, value) { + this.errors.push({msg: msg, value: value}); + }, + log: function (msg, value) { + this.logs.push({msg: msg, value: value}); + } + }; + + + var slackModule = sandbox.require('../lib/appenders/slack', { + requires: { + 'slack-node': fakeSlack, + '../layouts': fakeLayouts + }, + globals: { + console: fakeConsole + } + }); + + + log4js.addAppender(slackModule.configure(options), category); + + return { + logger: log4js.getLogger(category), + mailer: fakeSlack, + layouts: fakeLayouts, + console: fakeConsole, + messages: msgs, + credentials: slackCredentials + }; +} + +function checkMessages(result) { + for (var i = 0; i < result.messages.length; ++i) { + assert.equal(result.messages[i].channel, '#CHANNEL'); + assert.equal(result.messages[i].username, 'USERNAME'); + assert.ok(new RegExp('.+Log event #' + (i + 1)).test(result.messages[i].text)); + } +} + +log4js.clearAppenders(); + +vows.describe('log4js slackAppender').addBatch({ + 'slack setup': { + topic: setupLogging('slack setup', { + token: 'TOKEN', + channel_id: "#CHANNEL", + username: "USERNAME", + format: "FORMAT", + icon_url: "ICON_URL" + }), + 'slack credentials should match': function (result) { + assert.equal(result.credentials.token, 'TOKEN'); + assert.equal(result.credentials.channel_id, '#CHANNEL'); + assert.equal(result.credentials.username, 'USERNAME'); + assert.equal(result.credentials.format, 'FORMAT'); + assert.equal(result.credentials.icon_url, 'ICON_URL'); + } + }, + + 'basic usage': { + topic: function () { + var setup = setupLogging('basic usage', { + token: 'TOKEN', + channel_id: "#CHANNEL", + username: "USERNAME", + format: "FORMAT", + icon_url: "ICON_URL", + }); + + setup.logger.info("Log event #1"); + return setup; + }, + 'there should be one message only': function (result) { + assert.equal(result.messages.length, 1); + }, + 'message should contain proper data': function (result) { + checkMessages(result); + } + }, + 'config with layout': { + topic: function () { + var setup = setupLogging('config with layout', { + layout: { + type: "tester" + } + }); + return setup; + }, + 'should configure layout': function (result) { + assert.equal(result.layouts.type, 'tester'); + } + }, + 'separate notification for each event': { + topic: function () { + var self = this; + var setup = setupLogging('separate notification for each event', { + token: 'TOKEN', + channel_id: "#CHANNEL", + username: "USERNAME", + format: "FORMAT", + icon_url: "ICON_URL", + }); + setTimeout(function () { + setup.logger.info('Log event #1'); + }, 0); + setTimeout(function () { + setup.logger.info('Log event #2'); + }, 500); + setTimeout(function () { + setup.logger.info('Log event #3'); + }, 1100); + setTimeout(function () { + self.callback(null, setup); + }, 3000); + }, + 'there should be three messages': function (result) { + assert.equal(result.messages.length, 3); + }, + 'messages should contain proper data': function (result) { + checkMessages(result); + } + } +}).export(module); +