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)