feat: Serilog Telegram Integration for Alert Notifications

Add automatic Telegram notifications for ERROR and FATAL level logs.

Features:
- TelegramSink: Custom Serilog sink for Telegram API integration
- Conditional logging: Only ERROR and FATAL levels trigger alerts
- Environment variables: TELEGRAM_BOT and CHAT_ID from Gitea Secrets
- Non-blocking: Telegram failures don't crash application

Configuration:
- Reads TELEGRAM_BOT and CHAT_ID from environment
- Formatted messages with emoji, timestamp, and exception details
- Markdown parsing for better Telegram presentation

This enables real-time alerting for critical issues during:
- Gate 3 Shadow Run execution
- Production deployments
- System errors and exceptions

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 14:56:16 +09:00
parent 2b48f37ca8
commit e35f744e4c
2 changed files with 126 additions and 5 deletions
@@ -0,0 +1,106 @@
using System.Net.Http;
using Serilog;
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
namespace KArtSell.Host.Infrastructure;
/// <summary>
/// Serilog sink for sending critical logs to Telegram
/// Triggers on ERROR and FATAL events
/// </summary>
public sealed class TelegramSink : ILogEventSink
{
private readonly string _telegramBotToken;
private readonly string _telegramChatId;
private readonly HttpClient _httpClient;
private readonly object _syncRoot = new();
public TelegramSink(string telegramBotToken, string telegramChatId, HttpClient? httpClient = null)
{
_telegramBotToken = telegramBotToken;
_telegramChatId = telegramChatId;
_httpClient = httpClient ?? new HttpClient();
}
public void Emit(LogEvent logEvent)
{
// Only send critical logs (Error and Fatal)
if (logEvent.Level < LogEventLevel.Error)
return;
try
{
lock (_syncRoot)
{
SendTelegramMessage(logEvent);
}
}
catch
{
// Silently ignore Telegram errors to prevent logging loops
}
}
private void SendTelegramMessage(LogEvent logEvent)
{
var emoji = logEvent.Level == LogEventLevel.Fatal ? "🔴" : "⚠️";
var levelName = logEvent.Level.ToString().ToUpperInvariant();
var message = $@"{emoji} *{levelName}* - K-ArtSell Aegis
{logEvent.MessageTemplate.Render(logEvent.Properties)}
_Timestamp: {logEvent.Timestamp:O}_";
if (logEvent.Exception != null)
{
message += $@"
```
{logEvent.Exception.GetType().Name}: {logEvent.Exception.Message}
```";
}
SendMessage(message);
}
private void SendMessage(string message)
{
var url = $"https://api.telegram.org/bot{_telegramBotToken}/sendMessage";
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "chat_id", _telegramChatId },
{ "text", message },
{ "parse_mode", "Markdown" }
});
try
{
var response = _httpClient.PostAsync(url, content).GetAwaiter().GetResult();
response.EnsureSuccessStatusCode();
}
catch
{
// Silently fail - don't want logging to break application
}
}
}
/// <summary>
/// Serilog extension for adding Telegram sink
/// </summary>
public static class TelegramSinkExtensions
{
public static LoggerConfiguration Telegram(
this LoggerSinkConfiguration loggerConfiguration,
string telegramBotToken,
string telegramChatId,
HttpClient? httpClient = null)
{
return loggerConfiguration.Sink(
new TelegramSink(telegramBotToken, telegramChatId, httpClient));
}
}