@kulakovdmitr/pm2-slack
Version:
A PM2 module to emit events to Slack and configure settings
171 lines (141 loc) • 5.7 kB
JavaScript
"use strict";
// Exports
module.exports = { sendToSlack };
// Dependency
const request = require('request');
const os = require('os');
// Constants
// The events that will trigger the color red
const redEvents = ['stop', 'exit', 'delete', 'error', 'kill', 'exception', 'restart overlimit', 'suppressed'];
const redColor = '#F44336';
const commonColor = '#2196F3';
/**
* Sends immediately the message(s) to Slack's Incoming Webhook.
*
* @param {Message[]) messages - List of messages, ready to send.
* This list can be trimmed and concated base on module configuration.
*/
function sendToSlack(messages, config) {
// If a Slack URL is not set, we do not want to continue and nofify the user that it needs to be set
if (!config.slack_url) {
return console.error("There is no Slack URL set, please set the Slack URL: 'pm2 set @kulakovdmitr/pm2-slack:slack_url https://slack_url'");
}
let limitedCountOfMessages;
if (config.queue_max > 0) {
// Limit count of messages for sending
limitedCountOfMessages = messages.splice(0, Math.min(config.queue_max, messages.length));
} else {
// Select all messages for sending
limitedCountOfMessages = messages;
}
// The JSON payload to send to the Webhook
let payload = {
username: config.username || config.servername || os.hostname(),
attachments: []
};
// Merge together all messages from same process and with same event
// Convert messages to Slack message's attachments
payload.attachments = convertMessagesToSlackAttachments(mergeSimilarMessages(limitedCountOfMessages));
// Because Slack`s notification text displays the fallback text of first attachment only,
// add list of message types to better overview about complex message in mobile notifications.
if (payload.attachments.length > 1) {
payload.text = payload.attachments
.map(function(/*SlackAttachment*/ attachment) { return attachment.title; })
.join(", ");
}
// Group together all messages with same title.
// payload.attachments = groupSameSlackAttachmentTypes(payload.attachments);
// Add warning, if some messages has been suppresed
if (messages.length > 0) {
let text = 'Next ' + messages.length + ' message' + (messages.length > 1 ? 's have ' : ' has ') + 'been suppressed.';
payload.attachments.push({
fallback: text,
// color: redColor,
title: 'message rate limitation',
text: text,
ts: Math.floor(Date.now() / 1000),
});
}
// Options for the post request
const requestOptions = {
method: 'post',
body: payload,
json: true,
url: config.slack_url,
};
// Finally, make the post request to the Slack Incoming Webhook
request(requestOptions, function(err, res, body) {
if (err) return console.error(err);
if (body !== 'ok') {
console.error('Error sending notification to Slack, verify that the Slack URL for incoming webhooks is correct. ' + messages.length + ' unsended message(s) lost.');
}
});
}
/**
* Merge together all messages from same process and with same event
*
* @param {Messages[]} messages
* @returns {Messages[]}
*/
function mergeSimilarMessages(messages) {
return messages.reduce(function(/*Message[]*/ finalMessages, /*Message*/ currentMessage) {
if (finalMessages.length > 0
&& finalMessages[finalMessages.length-1].name === currentMessage.name
&& finalMessages[finalMessages.length-1].event === currentMessage.event
) {
// Current message has same title as previous one. Concate it.
finalMessages[finalMessages.length-1].description += "\n" + currentMessage.description;
} else {
// Current message is different than previous one.
finalMessages.push(currentMessage);
}
return finalMessages;
}, []);
}
/**
* Converts messages to json format, that can be sent as Slack message's attachments.
*
* @param {Message[]) messages
* @returns {SlackAttachment[]}
*/
function convertMessagesToSlackAttachments(messages) {
return messages.reduce(function(slackAttachments, message) {
// The default color for events should be green
var color = commonColor;
// If the event is listed in redEvents, set the color to red
if (redEvents.indexOf(message.event) > -1) {
color = redColor;
}
var title = `${message.name} ${message.event}`;
var description = (message.description || '').trim();
var fallbackText = title + (description ? ': ' + description.replace(/[\r\n]+/g, ', ') : '');
slackAttachments.push({
fallback: escapeSlackText(fallbackText),
color: color,
title: escapeSlackText(title),
text: escapeSlackText(description),
ts: message.timestamp,
// footer: message.name,
});
return slackAttachments;
}, []);
}
/**
* Escapes the plain text before sending to Slack's Incoming webhook.
* @see https://api.slack.com/docs/message-formatting#how_to_escape_characters
*
* @param {string} text
* @returns {string}
*/
function escapeSlackText(text) {
return (text || '').replace('&', '&').replace('<', '<').replace('>', '>');
}
/**
* @typedef {Object} SlackAttachment
*
* @property {string} fallback
* @property {string} title
* @property {string} [color]
* @property {string} [text]
* @property {number} ts - Linux timestamp format
*/