3d1c55468b
- Add detailed CPU core monitoring option for better performance control - Update monitoring settings in app configuration - Improve parallel task execution for resource usage monitoring - Modify Telegram notification service to skip alerts from svchost processes - Add "Memory %" column to process table in HTML and update related JavaScript - Create performance test scripts for API response time evaluation
225 lines
8.3 KiB
C#
225 lines
8.3 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using ResourceMonitorService.Configuration;
|
||
using ResourceMonitorService.Models;
|
||
using Telegram.Bot;
|
||
using Telegram.Bot.Exceptions;
|
||
using Telegram.Bot.Types.Enums;
|
||
|
||
namespace ResourceMonitorService.Services
|
||
{
|
||
public interface ITelegramNotificationService
|
||
{
|
||
Task SendAlertAsync(Alert alert);
|
||
Task SendAlertResolvedAsync(Alert alert);
|
||
Task<bool> IsEnabledAsync();
|
||
Task<bool> TestConnectionAsync();
|
||
}
|
||
|
||
public class TelegramNotificationService : ITelegramNotificationService
|
||
{
|
||
private readonly ILogger<TelegramNotificationService> _logger;
|
||
private readonly TelegramSettings _telegramSettings;
|
||
private readonly ITelegramBotClient? _botClient;
|
||
|
||
public TelegramNotificationService(
|
||
ILogger<TelegramNotificationService> logger,
|
||
IOptions<MonitoringSettings> settings)
|
||
{
|
||
_logger = logger;
|
||
_telegramSettings = settings.Value.Telegram;
|
||
|
||
if (_telegramSettings.IsEnabled && !string.IsNullOrEmpty(_telegramSettings.BotToken))
|
||
{
|
||
try
|
||
{
|
||
_botClient = new TelegramBotClient(_telegramSettings.BotToken);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to initialize Telegram bot client");
|
||
}
|
||
}
|
||
}
|
||
|
||
public async Task<bool> IsEnabledAsync()
|
||
{
|
||
return await Task.FromResult(_telegramSettings.IsEnabled && _botClient != null);
|
||
}
|
||
|
||
public async Task<bool> TestConnectionAsync()
|
||
{
|
||
if (_botClient == null || !_telegramSettings.IsEnabled)
|
||
return false;
|
||
|
||
try
|
||
{
|
||
var me = await _botClient.GetMe();
|
||
_logger.LogInformation("Telegram bot connected successfully: @{Username}", me.Username);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to connect to Telegram bot");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
public async Task SendAlertAsync(Alert alert)
|
||
{
|
||
if (_botClient == null || !_telegramSettings.IsEnabled)
|
||
return;
|
||
|
||
// Check if we should send this type of alert
|
||
if ((alert.Level == "Warning" && !_telegramSettings.SendWarningAlerts) ||
|
||
(alert.Level == "Critical" && !_telegramSettings.SendCriticalAlerts))
|
||
{
|
||
return;
|
||
}
|
||
|
||
// Ignore alerts from svchost processes
|
||
if (alert.Component.Contains("svchost", StringComparison.OrdinalIgnoreCase) ||
|
||
alert.Message.Contains("svchost", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
_logger.LogInformation("Skipping Telegram alert for svchost process: {Component} - {Message}",
|
||
alert.Component, alert.Message);
|
||
return;
|
||
}
|
||
|
||
var message = FormatAlertMessage(alert, _telegramSettings.MessageTemplate);
|
||
|
||
foreach (var chatId in _telegramSettings.ChatIds)
|
||
{
|
||
try
|
||
{
|
||
await _botClient.SendMessage(
|
||
chatId: chatId,
|
||
text: message,
|
||
parseMode: ParseMode.Markdown,
|
||
disableNotification: alert.Level == "Warning" // Don't ping for warnings
|
||
);
|
||
|
||
_logger.LogInformation("Telegram alert sent to chat {ChatId}: {AlertLevel} - {Component}",
|
||
chatId, alert.Level, alert.Component);
|
||
}
|
||
catch (ApiRequestException ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to send Telegram alert to chat {ChatId}: {ErrorCode} - {Description}",
|
||
chatId, ex.ErrorCode, ex.Message);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Unexpected error sending Telegram alert to chat {ChatId}", chatId);
|
||
}
|
||
}
|
||
}
|
||
|
||
public async Task SendAlertResolvedAsync(Alert alert)
|
||
{
|
||
if (_botClient == null || !_telegramSettings.IsEnabled || !_telegramSettings.SendResolutionNotifications)
|
||
return;
|
||
|
||
// Ignore alerts from svchost processes
|
||
if (alert.Component.Contains("svchost", StringComparison.OrdinalIgnoreCase) ||
|
||
alert.Message.Contains("svchost", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
_logger.LogInformation("Skipping Telegram resolution notification for svchost process: {Component} - {Message}",
|
||
alert.Component, alert.Message);
|
||
return;
|
||
}
|
||
|
||
var message = FormatAlertMessage(alert, _telegramSettings.ResolutionTemplate);
|
||
|
||
foreach (var chatId in _telegramSettings.ChatIds)
|
||
{
|
||
try
|
||
{
|
||
await _botClient.SendMessage(
|
||
chatId: chatId,
|
||
text: message,
|
||
parseMode: ParseMode.Markdown,
|
||
disableNotification: true // Don't ping for resolutions
|
||
);
|
||
|
||
_logger.LogInformation("Telegram resolution notification sent to chat {ChatId}: {Component}",
|
||
chatId, alert.Component);
|
||
}
|
||
catch (ApiRequestException ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to send Telegram resolution to chat {ChatId}: {ErrorCode} - {Description}",
|
||
chatId, ex.ErrorCode, ex.Message);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Unexpected error sending Telegram resolution to chat {ChatId}", chatId);
|
||
}
|
||
}
|
||
}
|
||
|
||
private string FormatAlertMessage(Alert alert, string template)
|
||
{
|
||
var levelIcon = alert.Level switch
|
||
{
|
||
"Critical" => "🔴",
|
||
"Warning" => "⚠️",
|
||
_ => "ℹ️"
|
||
};
|
||
|
||
var componentIcon = alert.Component switch
|
||
{
|
||
"CPU" => "🖥️",
|
||
"CPUTemp" => "🌡️",
|
||
"Memory" => "💾",
|
||
"GPU" => "🎮",
|
||
"GPUTemp" => "🌡️",
|
||
var disk when disk.StartsWith("Disk") => "💽",
|
||
var process when process.StartsWith("ProcessMemory") => "⚙️",
|
||
_ => "📊"
|
||
};
|
||
|
||
// Replace template placeholders
|
||
var message = template
|
||
.Replace("{Level}", alert.Level)
|
||
.Replace("{Component}", alert.Component)
|
||
.Replace("{Message}", EscapeMarkdown(alert.Message))
|
||
.Replace("{Timestamp}", alert.Timestamp.ToString("yyyy-MM-dd HH:mm:ss"))
|
||
.Replace("{CurrentValue}", alert.CurrentValue.ToString("F1"))
|
||
.Replace("{ThresholdValue}", alert.ThresholdValue.ToString("F1"));
|
||
|
||
if (alert.ResolvedAt.HasValue)
|
||
{
|
||
message = message.Replace("{ResolvedAt}", alert.ResolvedAt.Value.ToString("yyyy-MM-dd HH:mm:ss"));
|
||
}
|
||
|
||
// Add icons
|
||
message = $"{levelIcon} {componentIcon} {message}";
|
||
|
||
return message;
|
||
}
|
||
|
||
private static string EscapeMarkdown(string text)
|
||
{
|
||
// Escape special Markdown characters
|
||
return text
|
||
.Replace("_", "\\_")
|
||
.Replace("*", "\\*")
|
||
.Replace("[", "\\[")
|
||
.Replace("]", "\\]")
|
||
.Replace("(", "\\(")
|
||
.Replace(")", "\\)")
|
||
.Replace("~", "\\~")
|
||
.Replace("`", "\\`")
|
||
.Replace(">", "\\>")
|
||
.Replace("#", "\\#")
|
||
.Replace("+", "\\+")
|
||
.Replace("-", "\\-")
|
||
.Replace("=", "\\=")
|
||
.Replace("|", "\\|")
|
||
.Replace("{", "\\{")
|
||
.Replace("}", "\\}")
|
||
.Replace(".", "\\.")
|
||
.Replace("!", "\\!");
|
||
}
|
||
}
|
||
}
|