telex-server-monitor-sdk
Version:
SDK for Telex Server Monitor - A lightweight agent that collects and reports server CPU metrics to the Telex monitoring platform
306 lines (305 loc) • 14.7 kB
JavaScript
import { Subscriber, Publisher } from "zeromq";
import { logger } from "../utils/logger.js";
import { CollectorService } from "../metrics/collector.js";
import { AppConstants } from "../utils/constant.js";
import { isDevEnvironment } from "../config/config.js";
import { getStoreData, saveStoreData } from "../utils/store.js";
import os from "os";
export var IncomingMessageType;
(function (IncomingMessageType) {
IncomingMessageType["getAllMetrics"] = "getAllMetrics";
IncomingMessageType["getCpuMetrics"] = "getCpuMetrics";
IncomingMessageType["getCpuLoadAverages"] = "getCpuLoadAverages";
IncomingMessageType["getCpuUsagePerCore"] = "getCpuUsagePerCore";
IncomingMessageType["getMemoryStats"] = "getMemoryStats";
IncomingMessageType["getDiskMetrics"] = "getDiskMetrics";
IncomingMessageType["getProcessMetrics"] = "getProcessMetrics";
IncomingMessageType["getNetworkMetrics"] = "getNetworkMetrics";
IncomingMessageType["getSecurityMetrics"] = "getSecurityMetrics";
IncomingMessageType["ping"] = "ping";
})(IncomingMessageType || (IncomingMessageType = {}));
export var OutGoingMessageReplyType;
(function (OutGoingMessageReplyType) {
OutGoingMessageReplyType["getAllMetricsReply"] = "getAllMetricsReply";
OutGoingMessageReplyType["getCpuMetricsReply"] = "getCpuMetricsReply";
OutGoingMessageReplyType["getCpuLoadAveragesReply"] = "getCpuLoadAveragesReply";
OutGoingMessageReplyType["getCpuUsagePerCoreReply"] = "getCpuUsagePerCoreReply";
OutGoingMessageReplyType["getMemoryStatsReply"] = "getMemoryStatsReply";
OutGoingMessageReplyType["getDiskMetricsReply"] = "getDiskMetricsReply";
OutGoingMessageReplyType["getProcessMetricsReply"] = "getProcessMetricsReply";
OutGoingMessageReplyType["getNetworkMetricsReply"] = "getNetworkMetricsReply";
OutGoingMessageReplyType["getSecurityMetricsReply"] = "getSecurityMetricsReply";
OutGoingMessageReplyType["pingReply"] = "pingReply";
OutGoingMessageReplyType["cpuThresholdAlertReply"] = "cpuThresholdAlertReply";
OutGoingMessageReplyType["memoryThresholdAlertReply"] = "memoryThresholdAlertReply";
OutGoingMessageReplyType["securityAlertReply"] = "securityAlertReply";
})(OutGoingMessageReplyType || (OutGoingMessageReplyType = {}));
// Create a mapping of functions for metrics collection
const functionMap = {
getAllMetricsReply: CollectorService.getMetrics,
getCpuMetricsReply: CollectorService.getMetrics,
getCpuLoadAveragesReply: CollectorService.getMetrics,
getCpuUsagePerCoreReply: CollectorService.getCpuUsagePerCoreMetrics,
getMemoryStatsReply: CollectorService.getMetrics,
getDiskMetricsReply: CollectorService.getDiskMetrics,
getProcessMetricsReply: CollectorService.getProcessMetrics,
getNetworkMetricsReply: CollectorService.getNetworkMetrics,
getSecurityMetricsReply: CollectorService.getSecurityMetrics,
};
let subSocket = null;
let pubSocket = null;
// Connect to the integration server's publisher socket
export async function connectToIntegrationServer(channelId) {
try {
if (subSocket) {
logger.warn("ZeroMQ socket is already connected");
return;
}
// fetch the host and port from the integration server
const serverConfig = await getIntegrationServerHostAndPort();
if (!serverConfig?.serverUrl || !serverConfig?.serverPort) {
throw new Error("Failed to fetch integration server config");
}
const serverUrl = serverConfig?.serverUrl;
const serverPort = serverConfig?.serverPort + 1;
logger.info(`Integration server: ${serverUrl}:${serverPort}`);
// Set up subscriber socket
subSocket = new Subscriber();
const subSocketAddress = `tcp://${serverUrl}:${serverPort}`;
await subSocket.connect(subSocketAddress);
await subSocket.subscribe(channelId);
// Set up publisher socket for replies
pubSocket = new Publisher();
const pubPort = serverPort + 1; // Use next port for replies
const pubSocketAddress = `tcp://${serverUrl}:${pubPort}`;
await pubSocket.connect(pubSocketAddress);
logger.info(`Connected to integration server at ${subSocketAddress}`);
logger.info(`Reply socket connected to ${pubSocketAddress}`);
logger.info(`Subscribed to channel ${channelId}`);
// Start message handler
handleMessages(channelId);
}
catch (error) {
logger.error(`Failed to connect to integration server: ${error.message}`);
throw error;
}
}
// Send a reply back to the integration server
export async function sendReply(channelId, data, messageType) {
if (!pubSocket) {
throw new Error("Reply socket not connected");
}
try {
const reply = {
type: messageType,
channelId,
data,
timestamp: new Date().toISOString(),
};
await pubSocket.send([channelId, JSON.stringify(reply)]);
logger.info(`Sent ${messageType} to channel ${channelId}`);
}
catch (error) {
logger.error(`Failed to send reply: ${error.message}`);
}
}
/**
* Send metrics to the integration server
*/
export async function sendMetrics(channelId, messageType, userMessage) {
let metrics = await functionMap[messageType]();
logger.info(`Collected metrics for ${channelId}`);
await sendReply(channelId, { metrics, userMessage }, messageType);
}
// Send a Security alert to the integration server
export async function sendSecurityAlert(channelId, metrics, alerts, isCritical) {
const severityEmoji = isCritical ? "🔥" : "⚠️";
const severityText = isCritical ? "CRITICAL" : "WARNING";
const alertMessage = {
metrics,
severity: isCritical ? "critical" : "warning",
message: `${severityEmoji} ${severityText}: Security Alert ${severityEmoji}\n\n${alerts.join("\n")}\n\nServer: ${getStoreData()?.serverName || "Unknown"}\nTimestamp: ${new Date().toLocaleString()}`,
};
await sendReply(channelId, alertMessage, OutGoingMessageReplyType.securityAlertReply);
}
// Handle incoming messages from the integration server
async function handleMessages(channelId) {
if (!subSocket) {
throw new Error("ZeroMQ socket not connected");
}
try {
for await (const [topic, messageBuffer] of subSocket) {
try {
const message = JSON.parse(messageBuffer.toString());
logger.info(`Received message type: ${message.type}`);
if (topic.toString() !== channelId) {
continue;
}
// Store settings if provided
if (message.data?.settings && Array.isArray(message.data.settings)) {
const cpuThresholdSetting = message.data.settings.find((s) => s.label === "cpu_threshold");
if (cpuThresholdSetting) {
const thresholdValue = Number(cpuThresholdSetting.value || cpuThresholdSetting.default || 80);
saveStoreData({
cpuThreshold: thresholdValue,
});
logger.info(`Stored CPU threshold setting: ${thresholdValue}%`);
}
// Handle memory threshold settings
const memoryThresholdSetting = message.data.settings.find((s) => s.label === "memory_threshold");
if (memoryThresholdSetting) {
const memThresholdValue = Number(memoryThresholdSetting.value ||
memoryThresholdSetting.default ||
90);
saveStoreData({
memoryThreshold: memThresholdValue,
});
logger.info(`Stored Memory threshold setting: ${memThresholdValue}%`);
}
// Handle security settings
const securitySettings = {
failedLoginThreshold: 5, // Default value
monitorPortScanning: true,
monitorFirewall: true,
};
// Failed login threshold
const failedLoginSetting = message.data.settings.find((s) => s.label === "failed_login_threshold");
if (failedLoginSetting) {
securitySettings.failedLoginThreshold = Number(failedLoginSetting.value || failedLoginSetting.default || 5);
}
// Port scanning monitoring
const portScanSetting = message.data.settings.find((s) => s.label === "monitor_port_scanning");
if (portScanSetting) {
securitySettings.monitorPortScanning =
portScanSetting.value === "true" ||
portScanSetting.value === true ||
portScanSetting.default === "true" ||
portScanSetting.default === true;
}
// Firewall monitoring
const firewallSetting = message.data.settings.find((s) => s.label === "monitor_firewall");
if (firewallSetting) {
securitySettings.monitorFirewall =
firewallSetting.value === "true" ||
firewallSetting.value === true ||
firewallSetting.default === "true" ||
firewallSetting.default === true;
}
// Save all security settings at once
saveStoreData({ securitySettings });
logger.info(`Stored security settings: ${JSON.stringify(securitySettings)}`);
// Store server name if available
if (!getStoreData()?.serverName) {
saveStoreData({
serverName: `${os.hostname()}`,
});
}
}
// Process different types of requests
const incomingMessageType = message.type;
switch (incomingMessageType) {
case IncomingMessageType.getAllMetrics:
await sendMetrics(channelId, OutGoingMessageReplyType.getAllMetricsReply, message.data?.userMessage);
break;
case IncomingMessageType.getCpuMetrics:
await sendMetrics(channelId, OutGoingMessageReplyType.getCpuMetricsReply, message.data?.userMessage);
break;
case IncomingMessageType.getCpuLoadAverages:
await sendMetrics(channelId, OutGoingMessageReplyType.getCpuLoadAveragesReply, message.data?.userMessage);
break;
case IncomingMessageType.getCpuUsagePerCore:
await sendMetrics(channelId, OutGoingMessageReplyType.getCpuUsagePerCoreReply, message.data?.userMessage);
break;
case IncomingMessageType.getMemoryStats:
await sendMetrics(channelId, OutGoingMessageReplyType.getMemoryStatsReply, message.data?.userMessage);
break;
case IncomingMessageType.getDiskMetrics:
await sendMetrics(channelId, OutGoingMessageReplyType.getDiskMetricsReply, message.data?.userMessage);
break;
case IncomingMessageType.getProcessMetrics:
await sendMetrics(channelId, OutGoingMessageReplyType.getProcessMetricsReply, message.data?.userMessage);
break;
case IncomingMessageType.getNetworkMetrics:
await sendMetrics(channelId, OutGoingMessageReplyType.getNetworkMetricsReply, message.data?.userMessage);
break;
case IncomingMessageType.getSecurityMetrics:
await sendMetrics(channelId, OutGoingMessageReplyType.getSecurityMetricsReply, message.data?.userMessage);
break;
default:
logger.warn(`Unknown message type: ${incomingMessageType}`);
break;
}
}
catch (error) {
logger.error(`Error processing message: ${error.message}`);
}
}
}
catch (error) {
logger.error(`Error in message handler: ${error.message}`);
throw error;
}
}
/**
* Close the ZeroMQ socket connection
*/
export function closeSocket() {
if (subSocket) {
try {
subSocket.close();
subSocket = null;
logger.info("Subscriber socket closed");
}
catch (error) {
logger.error(`Error closing subscriber socket: ${error.message}`);
}
}
if (pubSocket) {
try {
pubSocket.close();
pubSocket = null;
logger.info("Publisher socket closed");
}
catch (error) {
logger.error(`Error closing publisher socket: ${error.message}`);
}
}
}
async function getIntegrationServerHostAndPort() {
try {
if (isDevEnvironment) {
return {
serverUrl: "0.0.0.0",
serverPort: 3002,
};
}
const fallBackServerHost = "49.12.208.6";
const fallBackServerPort = 18010;
try {
const response = await fetch(AppConstants.Package.GlobalConfigUrl);
if (!response.ok) {
logger.warn(`Failed to fetch global config: ${response.statusText}`);
return {
serverUrl: fallBackServerHost,
serverPort: fallBackServerPort,
};
}
const config = await response.json();
return {
serverUrl: config.serverUrl,
serverPort: config.serverPort,
};
}
catch (error) {
logger.warn(`Error fetching config, using default: ${error.message}`);
return {
serverUrl: fallBackServerHost,
serverPort: fallBackServerPort,
};
}
}
catch (error) {
logger.error(`Failed to get server config: ${error.message}`);
return null;
}
}