diff --git a/lib/cli.js b/lib/cli.js index bdb0bb18..fd4ebf17 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -617,6 +617,45 @@ program }) }) +program + .command('notify-slack') + .description('start Slack notifier') + .option('-t, --token ' + , 'Slack API token (or $SLACK_TOKEN)' + , String + , process.env.SLACK_TOKEN) + .option('-c, --channel #' + , 'Slack channel (or $SLACK_CHANNEL)' + , String + , process.env.SLACK_CHANNEL) + .option('-p, --priority ' + , 'minimum log level' + , Number + , logger.Level.IMPORTANT) + .option('-s, --connect-sub ' + , 'sub endpoint' + , cliutil.list) + .action(function(options) { + if (!options.token) { + this.missingArgument('--token') + } + if (!options.channel) { + this.missingArgument('--channel') + } + if (!options.connectSub) { + this.missingArgument('--connect-sub') + } + + require('./units/notify/slack')({ + token: options.token + , channel: options.channel + , priority: options.priority + , endpoints: { + sub: options.connectSub + } + }) + }) + program .command('log-rethinkdb') .description('start a rethinkdb log recorder') diff --git a/lib/units/notify/slack.js b/lib/units/notify/slack.js new file mode 100644 index 00000000..cef523e1 --- /dev/null +++ b/lib/units/notify/slack.js @@ -0,0 +1,81 @@ +var util = require('util') + +var WebClient = require('slack-client').WebClient +var Promise = require('bluebird') + +var logger = require('../../util/logger') +var wire = require('../../wire') +var wirerouter = require('../../wire/router') +var wireutil = require('../../wire/util') +var lifecycle = require('../../util/lifecycle') +var srv = require('../../util/srv') +var zmqutil = require('../../util/zmqutil') + + +module.exports = function(options) { + var log = logger.createLogger('notify-slack') + var client = new WebClient(options.token) + var buffer = [] + var timer + + // Input + var sub = zmqutil.socket('sub') + Promise.map(options.endpoints.sub, function(endpoint) { + return srv.resolve(endpoint).then(function(records) { + return srv.attempt(records, function(record) { + log.info('Receiving input from "%s"', record.url) + sub.connect(record.url) + return Promise.resolve(true) + }) + }) + }) + + // Establish always-on channels + ;[wireutil.global].forEach(function(channel) { + log.info('Subscribing to permanent channel "%s"', channel) + sub.subscribe(channel) + }) + + function push() { + buffer.splice(0).forEach(function(entry) { + var format = entry.message.indexOf('\n') === -1 ? '`%s`' : '```%s```' + var message = util.format(format, entry.message) + + client.chat.postMessage(options.channel, util.format( + '>>> *%s/%s* %d [*%s*] %s' + , logger.LevelLabel[entry.priority] + , entry.tag + , entry.pid + , entry.identifier + , message + ) + , { + username: 'STF' + , icon_url: 'https://openstf.io/favicon.png' + } + ) + }) + } + + sub.on('message', wirerouter() + .on(wire.DeviceLogMessage, function(channel, message) { + if (message.priority >= options.priority) { + buffer.push(message) + clearTimeout(timer) + timer = setTimeout(push, 1000) + } + }) + .handler()) + + log.info('Listening for %s (or higher) level log messages', + logger.LevelLabel[options.priority]) + + lifecycle.observe(function() { + try { + sub.close() + } + catch (err) { + // No-op + } + }) +} diff --git a/package.json b/package.json index 2b6eca74..e84d2085 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "semver": "^5.0.1", "serve-favicon": "^2.2.0", "serve-static": "^1.9.2", + "slack-client": "^2.0.0-beta.3", "socket.io": "1.4.4", "split": "^1.0.0", "stf-appstore-db": "^1.0.0",