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(">", ">");
}
}