feat: Add enhanced Discord webhook alerts with timestamps and downtime (#6745)
Co-authored-by: SID <158349177+0xsid0703@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Frank Elsinga <frank@elsinga.de>
This commit is contained in:
parent
bf9b734f6c
commit
999c09d818
@ -1505,8 +1505,6 @@ class Monitor extends BeanModel {
|
|||||||
|
|
||||||
let msg = `[${monitor.name}] [${text}] ${bean.msg}`;
|
let msg = `[${monitor.name}] [${text}] ${bean.msg}`;
|
||||||
|
|
||||||
for (let notification of notificationList) {
|
|
||||||
try {
|
|
||||||
const heartbeatJSON = await bean.toJSONAsync({ decodeResponse: true });
|
const heartbeatJSON = await bean.toJSONAsync({ decodeResponse: true });
|
||||||
const monitorData = [{ id: monitor.id, active: monitor.active, name: monitor.name }];
|
const monitorData = [{ id: monitor.id, active: monitor.active, name: monitor.name }];
|
||||||
const preloadData = await Monitor.preparePreloadData(monitorData);
|
const preloadData = await Monitor.preparePreloadData(monitorData);
|
||||||
@ -1523,6 +1521,30 @@ class Monitor extends BeanModel {
|
|||||||
.tz(heartbeatJSON["timezone"])
|
.tz(heartbeatJSON["timezone"])
|
||||||
.format(SQL_DATETIME_FORMAT);
|
.format(SQL_DATETIME_FORMAT);
|
||||||
|
|
||||||
|
// Calculate downtime tracking information when service comes back up
|
||||||
|
// This makes downtime information available to all notification providers
|
||||||
|
if (bean.status === UP && monitor.id) {
|
||||||
|
try {
|
||||||
|
const lastDownHeartbeat = await R.getRow(
|
||||||
|
"SELECT time FROM heartbeat WHERE monitor_id = ? AND status = ? ORDER BY time DESC LIMIT 1",
|
||||||
|
[monitor.id, DOWN]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (lastDownHeartbeat && lastDownHeartbeat.time) {
|
||||||
|
heartbeatJSON["lastDownTime"] = lastDownHeartbeat.time;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// If we can't calculate downtime, just continue without it
|
||||||
|
// Silently fail to avoid disrupting notification sending
|
||||||
|
log.debug(
|
||||||
|
"monitor",
|
||||||
|
`[${monitor.name}] Could not calculate downtime information: ${error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let notification of notificationList) {
|
||||||
|
try {
|
||||||
await Notification.send(
|
await Notification.send(
|
||||||
JSON.parse(notification.config),
|
JSON.parse(notification.config),
|
||||||
msg,
|
msg,
|
||||||
|
|||||||
@ -56,6 +56,8 @@ class Discord extends NotificationProvider {
|
|||||||
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||||
let addess = this.extractAddress(monitorJSON);
|
let addess = this.extractAddress(monitorJSON);
|
||||||
if (heartbeatJSON["status"] === DOWN) {
|
if (heartbeatJSON["status"] === DOWN) {
|
||||||
|
const wentOfflineTimestamp = Math.floor(new Date(heartbeatJSON["time"]).getTime() / 1000);
|
||||||
|
|
||||||
let discorddowndata = {
|
let discorddowndata = {
|
||||||
username: discordDisplayName,
|
username: discordDisplayName,
|
||||||
embeds: [
|
embeds: [
|
||||||
@ -76,6 +78,11 @@ class Discord extends NotificationProvider {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
{
|
||||||
|
name: "Went Offline",
|
||||||
|
// F for full date/time
|
||||||
|
value: `<t:${wentOfflineTimestamp}:F>`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
value: heartbeatJSON["localDateTime"],
|
value: heartbeatJSON["localDateTime"],
|
||||||
@ -104,6 +111,14 @@ class Discord extends NotificationProvider {
|
|||||||
await axios.post(webhookUrl.toString(), discorddowndata, config);
|
await axios.post(webhookUrl.toString(), discorddowndata, config);
|
||||||
return okMsg;
|
return okMsg;
|
||||||
} else if (heartbeatJSON["status"] === UP) {
|
} else if (heartbeatJSON["status"] === UP) {
|
||||||
|
const backOnlineTimestamp = Math.floor(new Date(heartbeatJSON["time"]).getTime() / 1000);
|
||||||
|
let downtimeDuration = null;
|
||||||
|
let wentOfflineTimestamp = null;
|
||||||
|
if (heartbeatJSON["lastDownTime"]) {
|
||||||
|
wentOfflineTimestamp = Math.floor(new Date(heartbeatJSON["lastDownTime"]).getTime() / 1000);
|
||||||
|
downtimeDuration = this.formatDuration(backOnlineTimestamp - wentOfflineTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
let discordupdata = {
|
let discordupdata = {
|
||||||
username: discordDisplayName,
|
username: discordDisplayName,
|
||||||
embeds: [
|
embeds: [
|
||||||
@ -124,10 +139,23 @@ class Discord extends NotificationProvider {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
...(wentOfflineTimestamp
|
||||||
|
? [
|
||||||
{
|
{
|
||||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
name: "Went Offline",
|
||||||
value: heartbeatJSON["localDateTime"],
|
// F for full date/time
|
||||||
|
value: `<t:${wentOfflineTimestamp}:F>`,
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(downtimeDuration
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: "Downtime Duration",
|
||||||
|
value: downtimeDuration,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
...(heartbeatJSON["ping"] != null
|
...(heartbeatJSON["ping"] != null
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
@ -162,6 +190,32 @@ class Discord extends NotificationProvider {
|
|||||||
this.throwGeneralAxiosError(error);
|
this.throwGeneralAxiosError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format duration as human-readable string (e.g., "1h 23m", "45m 30s")
|
||||||
|
* TODO: Update below to `Intl.DurationFormat("en", { style: "short" }).format(duration)` once we are on a newer node version
|
||||||
|
* @param {number} timeInSeconds The time in seconds to format a duration for
|
||||||
|
* @returns {string} The formatted duration
|
||||||
|
*/
|
||||||
|
formatDuration(timeInSeconds) {
|
||||||
|
const hours = Math.floor(timeInSeconds / 3600);
|
||||||
|
const minutes = Math.floor((timeInSeconds % 3600) / 60);
|
||||||
|
const seconds = timeInSeconds % 60;
|
||||||
|
|
||||||
|
const durationParts = [];
|
||||||
|
if (hours > 0) {
|
||||||
|
durationParts.push(`${hours}h`);
|
||||||
|
}
|
||||||
|
if (minutes > 0) {
|
||||||
|
durationParts.push(`${minutes}m`);
|
||||||
|
}
|
||||||
|
if (seconds > 0 && hours === 0) {
|
||||||
|
// Only show seconds if less than an hour
|
||||||
|
durationParts.push(`${seconds}s`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return durationParts.length > 0 ? durationParts.join(" ") : "0s";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Discord;
|
module.exports = Discord;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user