From f569211967dee28be922c4af34c5161fdd3c2385 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Mon, 29 Jun 2026 11:35:27 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Serilog=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=8B=A4=EC=8B=9C=EA=B0=84=20=ED=85=94=EB=A0=88=EA=B7=B8?= =?UTF-8?q?=EB=9E=A8=20=EC=97=90=EB=9F=AC=20=EC=95=8C=EB=A6=BC=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TaxBaik.Web/Logging/TelegramSink.cs | 85 +++++++++++++++++++++++++++++ TaxBaik.Web/Program.cs | 7 +++ 2 files changed, 92 insertions(+) create mode 100644 TaxBaik.Web/Logging/TelegramSink.cs diff --git a/TaxBaik.Web/Logging/TelegramSink.cs b/TaxBaik.Web/Logging/TelegramSink.cs new file mode 100644 index 0000000..10f5318 --- /dev/null +++ b/TaxBaik.Web/Logging/TelegramSink.cs @@ -0,0 +1,85 @@ +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(">", ">"); + } +} diff --git a/TaxBaik.Web/Program.cs b/TaxBaik.Web/Program.cs index 0cc60fb..64c20cc 100644 --- a/TaxBaik.Web/Program.cs +++ b/TaxBaik.Web/Program.cs @@ -38,6 +38,13 @@ builder.Host.UseSerilog((context, config) => outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message:lj}{NewLine}{Exception}") .Enrich.FromLogContext() .Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName); + + var botToken = context.Configuration["Telegram:BotToken"]; + var systemChatId = context.Configuration["Telegram:SystemChatId"] ?? context.Configuration["Telegram:ChatId"]; + if (!string.IsNullOrEmpty(botToken) && !string.IsNullOrEmpty(systemChatId)) + { + config.WriteTo.Sink(new TaxBaik.Web.Logging.TelegramSink(botToken, systemChatId), Serilog.Events.LogEventLevel.Error); + } }); // Controllers (API) -- 2.52.0