-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
1,030 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
slack_url=https://hooks.slack.com/services/aaa/aaa/aaa | ||
log=true | ||
error=true | ||
kill=true | ||
exception=false | ||
restart=false | ||
reload=true | ||
delete=false | ||
stop=false | ||
exit=true | ||
start=true | ||
online=true | ||
buffer=true | ||
buffer_seconds=2 | ||
queue_max=100 | ||
buffer_max_seconds=20 | ||
SLACK_SIGNING_SECRET=change | ||
SLACK_BOT_TOKEN=xoxb-aaa-aaa-aaa | ||
SLACK_PORT=6666 | ||
SLACK_ADMINS='aaa aaa' | ||
SLACK_APP_TOKEN=xapp-a-aaa-aaa-aaa | ||
LOGS_PATH='/aaa/aaa/aaa/aaa/aaa/aaa/aaa-out.log' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules | ||
.DS_Store | ||
.jshintrc | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2022 DMYTRO KULAKOV | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,101 @@ | ||
# binary-slack | ||
# binary-slack | ||
|
||
This is a PM2 Module for sending events & logs from your PM2 processes to Slack. | ||
|
||
## Install | ||
|
||
To install and setup pm2-slack, run the following commands: | ||
|
||
``` | ||
pm2 install pm2-slack | ||
pm2 set pm2-slack:slack_url https://slack_url | ||
``` | ||
|
||
To get the Slack URL, you need to setup an Incoming Webhook. More details on how to set this up can be found here: https://api.slack.com/incoming-webhooks | ||
|
||
## Events subscription configuration | ||
|
||
The following events can be subscribed to: | ||
|
||
- `log` - All standard out logs from your processes. Default: false | ||
- `error` - All error logs from your processes. Default: true | ||
- `kill` - Event fired when PM2 is killed. Default: true | ||
- `exception` - Any exceptions from your processes. Default: true | ||
- `restart` - Event fired when a process is restarted. Default: false | ||
- `reload` - Event fired when a cluster is reloaded. Default: false | ||
- `delete` - Event fired when a process is removed from PM2. Default: false | ||
- `stop` - Event fired when a process is stopped. Default: false | ||
- `restart overlimit` - Event fired when a process is reaches the max amount of times it can restart. Default: true | ||
- `exit` - Event fired when a process is exited. Default: false | ||
- `start` - Event fired when a process is started. Default: false | ||
- `online` - Event fired when a process is online. Default: false | ||
|
||
You can simply turn these on and off by setting them to true or false using the PM2 set command. | ||
|
||
``` | ||
pm2 set pm2-slack:log true | ||
pm2 set pm2-slack:error false | ||
``` | ||
|
||
## Options | ||
|
||
The following options are available: | ||
|
||
- `slack_url` (string) - Slack Incomming Webhook URL. | ||
- `servername` / `username` (string) - Set the custom username for Slack messages (visible in message headers). Default: server hostname | ||
- `buffer` (bool) - Enable/Disable buffering of messages. Messages that occur in short time will be concatenated together and posted as a single slack message. Default: true | ||
- `buffer_seconds` (int) - If buffering is enables, all messages are stored for this interval. If no new messages comes in this interval, buffered message(s) are sended to Slack. If new message comes in this interval, the "timer" will be reseted and buffer starts waiting for the new interval for a new next message. *Note: Puspose is reduction of push notifications on Slack clients.* Default: 2 | ||
- `buffer_max_seconds` (int) - If time exceed this time, the buffered messages are always sent to Slack, even if new messages are still comming in interval (property `buffer_seconds`). Default: 20 | ||
- `queue_max` (int) - Maximum number of messages, that can be send in one Slack message (in one bufferring round). When the queue exceeds this maximum, next messages are suppresesed and replaced with message "*Next XX messages have been suppressed.*". Default: 100 | ||
|
||
Set these options in the same way as subscribing to events. | ||
|
||
|
||
###### Example | ||
|
||
The following configuration options will enable message buffering, and set the buffer duration to 5 seconds. All messages that occur within maximum 5 seconds delay between two neighboring messages will be concatenated into a single slack message. | ||
|
||
``` | ||
pm2 set pm2-slack:slack_url https://hooks.slack.com/services/123456789/123456789/aaaaaaa | ||
pm2 set pm2-slack:buffer_seconds 5 | ||
``` | ||
|
||
Note: In this example, the maximum total delay for messages is still 20 seconds (default value for `buffer_max_seconds`). After this time, the buffer will be flushed | ||
everytime and all messages will be sent. | ||
|
||
### Process based custom options | ||
|
||
By default, all options are defined for all processes globally. | ||
But you can separately define custom options to each PM2 process. | ||
Use format `pm2-slack:optionName-processName` to process based custom options. | ||
|
||
If no custom options is defined, the global `pm2-slack:propertyName` will be used. | ||
|
||
Note: By this way, all custom options can be used for specific process, but events subsciptions configuration is always global only. | ||
|
||
###### Example | ||
|
||
We have many processes, includes process `foo` and process `bar`. | ||
For this two processes will have to define separate Slack URL channel and separate server name. | ||
Same buffer options will be used for all processed. | ||
|
||
``` | ||
# Define global options for all processes. | ||
pm2 set pm2-slack:buffer_seconds 5 | ||
# Define global options for all processes. | ||
# (for process `foo` and `bar` the values will be overridden below). | ||
pm2 set pm2-slack:slack_url https://hooks.slack.com/services/123456789/123456789/aaaaaaa | ||
pm2 set pm2-slack:servername Orion | ||
# Define custom Slack Incomming Webhoook for `foo` process. | ||
pm2 set pm2-slack:slack_url-foo https://hooks.slack.com/services/123456789/123456789/bbbbbbb | ||
pm2 set pm2-slack:servername-foo Foo-server | ||
# Note: The `pm2-slack:buffer_seconds`=5 will be used from global options for this process. | ||
# Define custom Slack Incomming Webhoook for `bar` process | ||
pm2 set pm2-slack:slack_url-bar https://hooks.slack.com/services/123456789/123456789/ccccccc | ||
pm2 set pm2-slack:servername-foo Bar-server | ||
# Note: The `pm2-slack:buffer_seconds`=5 will be used from global options for this process. | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
'use strict'; | ||
|
||
// Dependency | ||
// for local tests: | ||
require('dotenv').config(); | ||
const pm2 = require('pm2'); | ||
const pmx = require('pmx').initModule(); | ||
const app = require('./slack-receiver'); | ||
const MessageQueue = require('./message-queue'); | ||
const { parseIncommingLog, parseProcessName } = require('./utils'); | ||
|
||
/** | ||
* Get the configuration from PM2 | ||
* | ||
* @type {Object} | ||
* @property {boolean} exception | ||
*/ | ||
|
||
|
||
|
||
// const process.env = pmx.initModule(); | ||
|
||
const slackUrlRouter = { | ||
/** | ||
* Keys are slackUrls, values are instances of MessageQueue | ||
* | ||
* @typedef {Object.<string, MessageQueue>} | ||
*/ | ||
messageQueues: {}, | ||
|
||
|
||
/** | ||
* Add the message to appropriate message queue (each Slack URL has own independent message enqueing). | ||
* | ||
* @param {Message} message | ||
*/ | ||
addMessage: function (message) { | ||
const processName = message.name; | ||
const slackUrl = process.env['slack_url-' + processName] || process.env['slack_url']; | ||
|
||
if (!slackUrl) { | ||
return; | ||
} | ||
|
||
if (!this.messageQueues[slackUrl]) { | ||
// Init new messageQueue to different Slack URL. | ||
|
||
// Resolve configuration parameters. | ||
const configProperties = ['username', 'servername', 'buffer', 'slack_url', 'buffer_seconds', 'buffer_max_seconds', 'queue_max']; | ||
const config = {}; | ||
configProperties.map((configPropertyName) => { | ||
// Use process based custom configuration values if exist, else use the global configuration values. | ||
config[configPropertyName] = process.env[configPropertyName + '-' + processName] || process.env[configPropertyName]; | ||
}); | ||
|
||
this.messageQueues[slackUrl] = new MessageQueue(config); | ||
} | ||
|
||
this.messageQueues[slackUrl].addMessageToQueue(message); | ||
|
||
} | ||
}; | ||
|
||
|
||
// ----- APP INITIALIZATION ----- | ||
|
||
// Start listening on the PM2 BUS | ||
pm2.launchBus(function (err, bus) { | ||
|
||
// Listen for process logs | ||
if (process.env.log) { | ||
bus.on('log:out', function (data) { | ||
// if (data.process.name === 'pm2-slack-plus') { return; } // Ignore messages of own module. | ||
const parsedLog = parseIncommingLog(data.data); | ||
slackUrlRouter.addMessage({ | ||
name: parseProcessName(data.process), | ||
event: 'log', | ||
description: parsedLog.description, | ||
timestamp: parsedLog.timestamp, | ||
}); | ||
}); | ||
} | ||
|
||
// Listen for process errors | ||
if (process.env.error) { | ||
bus.on('log:err', function (data) { | ||
|
||
|
||
if (data.data.includes('DeprecationWarning')) return; | ||
// if (data.process.name === 'pm2-slack-plus') { return; } // Ignore messages of own module. | ||
const parsedLog = parseIncommingLog(data.data); | ||
slackUrlRouter.addMessage({ | ||
name: parseProcessName(data.process), | ||
event: 'error', | ||
description: parsedLog.description, | ||
timestamp: parsedLog.timestamp, | ||
}); | ||
}); | ||
} | ||
|
||
// Listen for PM2 kill | ||
if (process.env.kill) { | ||
bus.on('pm2:kill', function (data) { | ||
slackUrlRouter.addMessage({ | ||
name: 'PM2', | ||
event: 'kill', | ||
description: data.msg, | ||
timestamp: Math.floor(Date.now() / 1000), | ||
}); | ||
}); | ||
} | ||
|
||
// Listen for process exceptions | ||
if (process.env.exception) { | ||
bus.on('process:exception', function (data) { | ||
if (data.process.name === 'pm2-slack-plus') { return; } // Ignore messages of own module. | ||
|
||
// If it is instance of Error, use it. If type is unknown, stringify it. | ||
const description = (data.data && data.data.message) ? (data.data.code || '') + data.data.message : JSON.stringify(data.data); | ||
slackUrlRouter.addMessage({ | ||
name: parseProcessName(data.process), | ||
event: 'exception', | ||
description: description, | ||
timestamp: Math.floor(Date.now() / 1000), | ||
}); | ||
}); | ||
} | ||
|
||
// Listen for PM2 events | ||
bus.on('process:event', function (data) { | ||
if (!process.env[data.event] && data.event !== 'restart overlimit') { return; } // This event type is disabled by configuration. | ||
if (data.process.name === 'pm2-slack-plus') { return; } // Ignore messages of own module. | ||
|
||
let description = null; | ||
let interactive = []; | ||
switch (data.event) { | ||
case 'start': | ||
case 'online': | ||
description = `${data.process.name} started`; | ||
break; | ||
case 'stop': | ||
description = 'App stopped.'; | ||
break; | ||
case 'restart': | ||
description = 'App restarted.'; | ||
break; | ||
case 'exit': | ||
description = 'App closed.'; | ||
break; | ||
case 'restart overlimit': | ||
description = 'Process has been stopped. Check and fix the issue.'; | ||
break; | ||
|
||
} | ||
slackUrlRouter.addMessage({ | ||
name: parseProcessName(data.process), | ||
event: data.event, | ||
description: description, | ||
interactive: interactive, | ||
timestamp: Math.floor(Date.now() / 1000), | ||
}); | ||
}); | ||
}); | ||
|
||
if (process.env["SLACK_SIGNING_SECRET"] && process.env["SLACK_BOT_TOKEN"] && process.env["SLACK_PORT"]) { | ||
(async () => { | ||
|
||
await app.start(); | ||
|
||
console.log(`Slack bot is ready. Listening on ${process.env["SLACK_PORT"]} port.`); | ||
})(); | ||
} | ||
|
||
/** | ||
* @typedef {Object} Message | ||
* | ||
* @property {string} name - Process name | ||
* @property {string} event - `start`|`stop`|`restart`|`error`|`exception`|`restart overlimit`| ... | ||
* @property {string} description | ||
* @property {number} timestamp - Linux timestamp format | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
"use strict"; | ||
|
||
// Dependency | ||
const Scheduler = require('./scheduler'); | ||
const slackSender = require('./slack-sender'); | ||
|
||
/** | ||
* | ||
* @param {Object} config | ||
* @param {boolean} config.buffer | ||
* @param {number} config.buffer_seconds | ||
* @param {number} config.buffer_max_seconds | ||
* @param {number} config.queue_max | ||
* @param {number} config.slack_url | ||
* @constructor | ||
*/ | ||
function MessageQueue(config) { | ||
this.config = config; | ||
this.messageQueue = []; | ||
this.scheduler = new Scheduler(config); | ||
} | ||
|
||
|
||
/** | ||
* Sends the message to Slack's Incoming Webhook. | ||
* If buffer is enabled, the message is added to queue and sending is postponed for couple of seconds. | ||
* | ||
* @param {Message} message | ||
*/ | ||
MessageQueue.prototype.addMessageToQueue = function(message) { | ||
const self = this; | ||
|
||
if (!this.config.buffer || !(this.config.buffer_seconds > 0)) { | ||
// No sending buffer defined. Send directly to Slack. | ||
slackSender.sendToSlack([message], self.config); | ||
} else { | ||
// Add message to buffer | ||
this.messageQueue.push(message); | ||
// Plan send the enqueued messages | ||
this.scheduler.schedule(async function() { | ||
// Remove waiting messages from global queue | ||
const messagesToSend = self.messageQueue.splice(0, self.messageQueue.length); | ||
|
||
slackSender.sendToSlack(messagesToSend, self.config); | ||
}); | ||
} | ||
|
||
} | ||
|
||
|
||
module.exports = MessageQueue; |
Oops, something went wrong.