using System; using System.Net.Http; using System.Net.Http.Json; using System.Text; using System.Threading.Tasks; using Serilog.Core; using Serilog.Events; namespace TaxBaik.Web.Logging; public class TelegramSink : ILogEventSink { private readonly string _botToken; private readonly string _chatId; private readonly HttpClient _httpClient; public TelegramSink(string botToken, string chatId) { _botToken = botToken; _chatId = chatId; _httpClient = new HttpClient(); } public void Emit(LogEvent logEvent) { if (logEvent.Level < LogEventLevel.Error) { return; } // Emit is a synchronous method, so we dispatch the network call asynchronously Task.Run(async () => { try { var timestamp = logEvent.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff zzz"); var level = logEvent.Level.ToString().ToUpper(); var message = logEvent.RenderMessage(); var exceptionDetails = logEvent.Exception?.ToString(); var sb = new StringBuilder(); sb.AppendLine($"🚨 [{level}] 에러 발생"); sb.AppendLine($"시간: {timestamp}"); sb.AppendLine($"메시지: {EscapeHtml(message)}"); if (!string.IsNullOrEmpty(exceptionDetails)) { var escapedException = EscapeHtml(exceptionDetails); if (escapedException.Length > 3000) { escapedException = escapedException.Substring(0, 3000) + "\n[이하 생략]"; } sb.AppendLine($"Exception 상세:\n
{escapedException}
"); } var url = $"https://api.telegram.org/bot{_botToken}/sendMessage"; var payload = new { chat_id = _chatId, text = sb.ToString(), parse_mode = "HTML" }; var response = await _httpClient.PostAsJsonAsync(url, payload); if (!response.IsSuccessStatusCode) { var errorResponse = await response.Content.ReadAsStringAsync(); Console.WriteLine($"[TelegramSink] Failed to send log to Telegram: {response.StatusCode} - {errorResponse}"); } } catch (Exception ex) { Console.WriteLine($"[TelegramSink] Error in TelegramSink: {ex.Message}"); } }); } private static string EscapeHtml(string text) { if (string.IsNullOrEmpty(text)) return text; return text.Replace("&", "&") .Replace("<", "<") .Replace(">", ">"); } }