Merge branch 'feature-slow-response-notification-cards' into feature-slow-response-notification

This commit is contained in:
Stephen Papierski 2024-01-16 15:05:03 -07:00
commit e4b79139ff
No known key found for this signature in database
5 changed files with 168 additions and 29 deletions

View file

@ -1458,17 +1458,38 @@ class Monitor extends BeanModel {
* Send a slow response notification about a monitor
* @param {Monitor} monitor The monitor to send a notificaton about
* @param {Bean} bean Status information about monitor
* @param {string} msg Notification text to be sent
* @param {object} slowStats Slow response information
* @returns {void}
*/
static async sendSlowResponseNotification(monitor, bean, msg) {
static async sendSlowResponseNotification(monitor, bean, slowStats) {
// Send notification
const notificationList = await Monitor.getNotificationList(monitor);
let text;
if (bean.pingStatus === NOMINAL) {
text = "🚀 Nominal";
} else {
text = "🐌 Slow";
}
let msg = `[${monitor.name}] [${text}] ${bean.pingMsg}`;
for (let notification of notificationList) {
try {
log.debug("monitor", `[${this.name}] Sending to ${notification.name}`);
await Notification.send(JSON.parse(notification.config), msg);
const heartbeatJSON = bean.toJSON();
// Override status with SLOW/NOMINAL, add slowStats
heartbeatJSON["status"] = bean.pingStatus;
heartbeatJSON["calculatedResponse"] = slowStats.calculatedResponse;
heartbeatJSON["calculatedThreshold"] = slowStats.calculatedThreshold;
heartbeatJSON["slowFor"] = slowStats.slowFor;
// Also provide the time in server timezone
heartbeatJSON["timezone"] = await UptimeKumaServer.getInstance().getTimezone();
heartbeatJSON["timezoneOffset"] = UptimeKumaServer.getInstance().getTimezoneOffset();
heartbeatJSON["localDateTime"] = dayjs.utc(heartbeatJSON["time"]).tz(heartbeatJSON["timezone"]).format(SQL_DATETIME_FORMAT);
await Notification.send(JSON.parse(notification.config), msg, await monitor.toJSON(false), heartbeatJSON);
} catch (e) {
log.error("monitor", `[${this.name}] Cannot send slow response notification to ${notification.name}`);
log.error("monitor", e);
@ -1564,11 +1585,6 @@ class Monitor extends BeanModel {
return;
}
// Create stats to append to messages/logs
const methodDescription = [ "average", "max" ].includes(method) ? `${method} of last ${windowDuration}s` : method;
let msgStats = `Response: ${actualResponseTime}ms (${methodDescription}) | Threshold: ${threshold}ms (${thresholdDescription})`;
let pingMsg = `${actualResponseTime}ms resp. (${methodDescription})`;
// Verify valid response time was calculated
if (actualResponseTime === 0 || !Number.isInteger(actualResponseTime)) {
log.debug("monitor", `[${this.name}] Failed to calculate valid response time`);
@ -1581,6 +1597,15 @@ class Monitor extends BeanModel {
return;
}
// Create stats to append to messages/logs
const methodDescription = [ "average", "max" ].includes(method) ? `${method} of last ${windowDuration}s` : method;
let msgStats = `Response: ${actualResponseTime}ms (${methodDescription}) | Threshold: ${threshold}ms (${thresholdDescription})`;
const slowStats = {
calculatedResponse: `${actualResponseTime}ms (${methodDescription})`,
calculatedThreshold: `${threshold}ms (${thresholdDescription})`,
slowFor: `${bean.slowResponseCount * monitor.interval}s`,
};
bean.pingThreshold = threshold;
// Responding normally
@ -1591,13 +1616,12 @@ class Monitor extends BeanModel {
} else {
msgStats += ` | Slow for: ${bean.slowResponseCount * monitor.interval}s`;
log.debug("monitor", `[${this.name}] Returned to normal response time | ${msgStats}`);
let msg = `[${this.name}] Returned to Normal Response Time \n${msgStats}`;
Monitor.sendSlowResponseNotification(monitor, bean, msg);
// Mark important (SLOW -> NOMINAL)
pingMsg += ` < ${threshold}ms`;
bean.pingImportant = true;
bean.pingMsg = pingMsg;
bean.pingMsg = `Returned to Normal Response Time \n${msgStats}`;
Monitor.sendSlowResponseNotification(monitor, bean, slowStats);
}
// Reset slow response count
@ -1610,28 +1634,29 @@ class Monitor extends BeanModel {
// Always send first notification
if (bean.slowResponseCount === 1) {
log.debug("monitor", `[${this.name}] Responded slowly, sending notification | ${msgStats}`);
let msg = `[${this.name}] Responded Slowly \n${msgStats}`;
Monitor.sendSlowResponseNotification(monitor, bean, msg);
log.debug("monitor", `[${this.name}] Responded slow, sending notification | ${msgStats}`);
// Mark important (NOMINAL -> SLOW)
pingMsg += ` > ${threshold}ms`;
bean.pingImportant = true;
bean.pingMsg = pingMsg;
bean.pingMsg = `Responded Slow \n${msgStats}`;
Monitor.sendSlowResponseNotification(monitor, bean, slowStats);
// Send notification every x times
} else if (this.slowResponseNotificationResendInterval > 0) {
if (((bean.slowResponseCount) % this.slowResponseNotificationResendInterval) === 0) {
// Send notification again, because we are still responding slow
msgStats += ` | Slow for: ${bean.slowResponseCount * monitor.interval}s`;
log.debug("monitor", `[${this.name}] Still responding slowly, sendSlowResponseNotification again | ${msgStats}`);
let msg = `[${this.name}] Still Responding Slowly \n${msgStats}`;
Monitor.sendSlowResponseNotification(monitor, bean, msg);
log.debug("monitor", `[${this.name}] Still responding slow, sendSlowResponseNotification again | ${msgStats}`);
bean.pingMsg = `Still Responding Slow \n${msgStats}`;
Monitor.sendSlowResponseNotification(monitor, bean, slowStats);
} else {
log.debug("monitor", `[${this.name}] Still responding slowly, waiting for resend interal | ${msgStats}`);
log.debug("monitor", `[${this.name}] Still responding slow, waiting for resend interal | ${msgStats}`);
}
} else {
log.debug("monitor", `[${this.name}] Still responding slowly, but resend is disabled | ${msgStats}`);
log.debug("monitor", `[${this.name}] Still responding slow, but resend is disabled | ${msgStats}`);
}
}
}

View file

@ -1,10 +1,11 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
const { DOWN, UP, SLOW, NOMINAL } = require("../../src/util");
class Discord extends NotificationProvider {
name = "discord";
supportSlowNotifications = true;
/**
* @inheritdoc
@ -115,12 +116,93 @@ class Discord extends NotificationProvider {
await axios.post(notification.discordWebhookUrl, discordupdata);
return okMsg;
} else if (heartbeatJSON["status"] === SLOW) {
let discordslowdata = {
username: discordDisplayName,
embeds: [{
title: "🐌 Your service " + monitorJSON["name"] + " responded slow. 🐌",
color: 16761095,
timestamp: heartbeatJSON["time"],
fields: [
{
name: "Service Name",
value: monitorJSON["name"],
},
{
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
},
{
name: `Time (${heartbeatJSON["timezone"]})`,
value: heartbeatJSON["localDateTime"],
},
{
name: "Ping",
value: heartbeatJSON["calculatedResponse"],
},
{
name: "Threshold",
value: heartbeatJSON["calculatedThreshold"],
},
],
}],
};
if (notification.discordPrefixMessage) {
discordslowdata.content = notification.discordPrefixMessage;
}
await axios.post(notification.discordWebhookUrl, discordslowdata);
return okMsg;
} else if (heartbeatJSON["status"] === NOMINAL) {
let discordnominaldata = {
username: discordDisplayName,
embeds: [{
title: "🚀 Your service " + monitorJSON["name"] + " is responding normally! 🚀",
color: 65280,
timestamp: heartbeatJSON["time"],
fields: [
{
name: "Service Name",
value: monitorJSON["name"],
},
{
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
value: monitorJSON["type"] === "push" ? "Heartbeat" : address,
},
{
name: `Time (${heartbeatJSON["timezone"]})`,
value: heartbeatJSON["localDateTime"],
},
{
name: "Ping",
value: heartbeatJSON["calculatedResponse"],
},
{
name: "Threshold",
value: heartbeatJSON["calculatedThreshold"],
},
{
name: "Slow For",
value: heartbeatJSON["slowFor"],
},
],
}],
};
if (notification.discordPrefixMessage) {
discordnominaldata.content = notification.discordPrefixMessage;
}
await axios.post(notification.discordWebhookUrl, discordnominaldata);
return okMsg;
} else {
this.throwGeneralAxiosError("Not sure why we're here");
}
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = Discord;

View file

@ -6,6 +6,12 @@ class NotificationProvider {
*/
name = undefined;
/**
* Does the notification provider support slow response notifications?
* @type {boolean}
*/
supportSlowNotifications = false;
/**
* Send a notification
* @param {BeanModel} notification Notification to send

View file

@ -1,11 +1,12 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { setSettings, setting } = require("../util-server");
const { getMonitorRelativeURL, UP } = require("../../src/util");
const { getMonitorRelativeURL, UP, DOWN, NOMINAL, SLOW } = require("../../src/util");
class Slack extends NotificationProvider {
name = "slack";
supportSlowNotifications = true;
/**
* Deprecated property notification.slackbutton
@ -50,6 +51,23 @@ class Slack extends NotificationProvider {
}
const textMsg = "Uptime Kuma Alert";
let color;
switch (heartbeatJSON["status"]) {
case UP:
case NOMINAL:
color = "#2eb886";
break;
case SLOW:
color = "#ffc107";
break;
case DOWN:
color = "#e01e5a";
break;
default:
color = "#0dcaf0";
}
let data = {
"text": `${textMsg}\n${msg}`,
"channel": notification.slackchannel,
@ -57,7 +75,7 @@ class Slack extends NotificationProvider {
"icon_emoji": notification.slackiconemo,
"attachments": [
{
"color": (heartbeatJSON["status"] === UP) ? "#2eb886" : "#e01e5a",
"color": color,
"blocks": [
{
"type": "header",

View file

@ -1,5 +1,5 @@
const { R } = require("redbean-node");
const { log } = require("../src/util");
const { log, SLOW, NOMINAL } = require("../src/util");
const Alerta = require("./notification-providers/alerta");
const AlertNow = require("./notification-providers/alertnow");
const AliyunSms = require("./notification-providers/aliyun-sms");
@ -149,7 +149,15 @@ class Notification {
*/
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
if (this.providerList[notification.type]) {
if ((heartbeatJSON?.status === SLOW || heartbeatJSON?.status === NOMINAL) && !this.providerList[notification.type].supportSlowNotifications) {
// This is a SLOW/NOMINAL notification where the provider does NOT support card notificatons yet
// TODO Ideally, this goes away once all the notification providers support slow response notification cards
log.debug("notification", `${notification.type} does not support card notifications for SLOW/NOMINAL events yet. Sending plain text message.`);
return this.providerList[notification.type].send(notification, msg);
} else {
return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON);
}
} else {
throw new Error("Notification type is not supported");
}