142 lines
4.8 KiB
C#
142 lines
4.8 KiB
C#
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using Serilog;
|
|
|
|
namespace ServerLib.Utils;
|
|
|
|
/// <summary>
|
|
/// 크래시 핸들러 (Windows / Linux 공통)
|
|
/// Register() 를 Program.cs 최상단에서 한 번 호출.
|
|
///
|
|
/// 덤프 생성은 CLR 환경변수로 처리 (스택 언와인드 전에 찍힘):
|
|
/// DOTNET_DbgEnableMiniDump=1
|
|
/// DOTNET_DbgMiniDumpType=4
|
|
/// DOTNET_DbgMiniDumpName=crashes/crash_%p_%t.dmp
|
|
///
|
|
/// 생성 파일 (crashes/ 폴더):
|
|
/// crash_YYYY-MM-DD_HH-mm-ss.log ← 항상 생성
|
|
/// crash_%p_%t.dmp ← CLR이 직접 생성 (정확한 크래시 위치)
|
|
/// </summary>
|
|
public static class CrashDumpHandler
|
|
{
|
|
private const string CRASH_DIR = "crashes";
|
|
private static int registered;
|
|
|
|
public static void Register()
|
|
{
|
|
// 중복 등록 방지
|
|
if (Interlocked.Exchange(ref registered, 1) != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
|
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
|
|
|
Log.Information("[CrashDump] 핸들러 등록 완료 (CrashDir={Dir})", Path.GetFullPath(CRASH_DIR));
|
|
}
|
|
|
|
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
|
{
|
|
Exception? ex = e.ExceptionObject as Exception;
|
|
string tag = $"[CrashDump] UnhandledException (IsTerminating={e.IsTerminating})";
|
|
WriteCrash(tag, ex);
|
|
}
|
|
|
|
private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
|
{
|
|
e.SetObserved(); // 프로세스 종료 방지
|
|
string tag = "[CrashDump] UnobservedTaskException";
|
|
WriteCrash(tag, e.Exception);
|
|
}
|
|
|
|
private static void WriteCrash(string tag, Exception? ex)
|
|
{
|
|
try
|
|
{
|
|
Directory.CreateDirectory(CRASH_DIR);
|
|
|
|
string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
|
string logPath = Path.Combine(CRASH_DIR, $"crash_{timestamp}.log");
|
|
|
|
WriteCrashLog(logPath, ex);
|
|
Log.Fatal("{Tag} → {Log}", tag, logPath);
|
|
}
|
|
catch (Exception writeEx)
|
|
{
|
|
Log.Error(writeEx, "[CrashDump] 로그 저장 실패");
|
|
}
|
|
finally
|
|
{
|
|
Log.CloseAndFlush();
|
|
}
|
|
}
|
|
|
|
private static void WriteCrashLog(string path, Exception? ex)
|
|
{
|
|
StringBuilder sb = new();
|
|
sb.AppendLine("═══════════════════════════════════════════════════");
|
|
sb.AppendLine($" CRASH REPORT {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
|
sb.AppendLine("═══════════════════════════════════════════════════");
|
|
sb.AppendLine();
|
|
|
|
sb.AppendLine("[Environment]");
|
|
sb.AppendLine($" OS : {RuntimeInformation.OSDescription}");
|
|
sb.AppendLine($" Runtime : {RuntimeInformation.FrameworkDescription}");
|
|
sb.AppendLine($" PID : {Environment.ProcessId}");
|
|
sb.AppendLine($" WorkDir : {Environment.CurrentDirectory}");
|
|
sb.AppendLine($" MachineName: {Environment.MachineName}");
|
|
sb.AppendLine();
|
|
|
|
sb.AppendLine("[Thread]");
|
|
sb.AppendLine($" ThreadId : {Environment.CurrentManagedThreadId}");
|
|
sb.AppendLine();
|
|
|
|
sb.AppendLine("[Exception]");
|
|
if (ex is null)
|
|
{
|
|
sb.AppendLine(" (예외 객체 없음)");
|
|
}
|
|
else
|
|
{
|
|
AppendException(sb, ex, 0);
|
|
}
|
|
|
|
File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
|
|
}
|
|
|
|
private static void AppendException(StringBuilder sb, Exception ex, int depth)
|
|
{
|
|
string indent = new(' ', depth * 2);
|
|
sb.AppendLine($"{indent} Type : {ex.GetType().FullName}");
|
|
sb.AppendLine($"{indent} Message : {ex.Message}");
|
|
sb.AppendLine($"{indent} Source : {ex.Source}");
|
|
sb.AppendLine();
|
|
sb.AppendLine($"{indent} StackTrace:");
|
|
|
|
if (ex.StackTrace is not null)
|
|
{
|
|
foreach (string line in ex.StackTrace.Split('\n'))
|
|
{
|
|
sb.AppendLine($"{indent} {line.TrimEnd()}");
|
|
}
|
|
}
|
|
|
|
if (ex is AggregateException agg)
|
|
{
|
|
foreach (Exception inner in agg.InnerExceptions)
|
|
{
|
|
sb.AppendLine();
|
|
sb.AppendLine($"{indent} [InnerException]");
|
|
AppendException(sb, inner, depth + 1);
|
|
}
|
|
}
|
|
else if (ex.InnerException is not null)
|
|
{
|
|
sb.AppendLine();
|
|
sb.AppendLine($"{indent} [InnerException]");
|
|
AppendException(sb, ex.InnerException, depth + 1);
|
|
}
|
|
}
|
|
}
|