-
Notifications
You must be signed in to change notification settings - Fork 73
/
slack-sender.js
171 lines (141 loc) · 5.55 KB
/
slack-sender.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
"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 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
*/