Files
a301_mmo_game_server/ClientTester/EchoClientTester/Utils/CrashDumpHandler.cs

197 lines
6.8 KiB
C#

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using Serilog;
namespace ClientTester.Utils;
/// <summary>
/// 릴리즈 빌드 크래시 덤프 핸들러
///
/// Register() 를 Program.cs 최상단에서 한 번 호출.
///
/// 생성 파일 (crashes/ 폴더):
/// Debug : crash_YYYY-MM-DD_HH-mm-ss.log
/// Release : crash_YYYY-MM-DD_HH-mm-ss.log
/// crash_YYYY-MM-DD_HH-mm-ss.dmp ← 메모리 덤프 추가
/// </summary>
public static class CrashDumpHandler
{
private const string CRASH_DIR = "crashes";
private static int registered = 0;
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;
bool isTerminating = e.IsTerminating;
string tag = $"[CrashDump] UnhandledException (IsTerminating={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 basePath = Path.Combine(CRASH_DIR, $"crash_{timestamp}");
// 1. 크래시 로그 작성
string logPath = $"{basePath}.log";
WriteCrashLog(logPath, ex);
Log.Fatal("{Tag} → {Log}", tag, logPath);
#if !DEBUG
// 2. 메모리 덤프 작성 (Release only)
string dmpPath = $"{basePath}.dmp";
WriteDumpFile(dmpPath);
Log.Fatal("[CrashDump] 덤프 파일 저장 완료 → {Dmp}", dmpPath);
#endif
}
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, depth: 0);
}
File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
}
private static void AppendException(StringBuilder sb, Exception ex, int depth)
{
string indent = new string(' ', 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);
}
}
#if !DEBUG
// Windows MiniDumpWriteDump P/Invoke
[DllImport("dbghelp.dll", SetLastError = true)]
private static extern bool MiniDumpWriteDump(
IntPtr hProcess, uint processId, IntPtr hFile,
uint dumpType, IntPtr exceptionParam,
IntPtr userStreamParam, IntPtr callbackParam);
private const uint MiniDumpWithFullMemory = 0x00000002;
private static void WriteDumpFile(string path)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Log.Warning("[CrashDump] 덤프 생성은 Windows만 지원");
return;
}
using Process process = Process.GetCurrentProcess();
using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
bool success = MiniDumpWriteDump(
process.Handle,
(uint)process.Id,
fs.SafeFileHandle.DangerousGetHandle(),
MiniDumpWithFullMemory,
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (!success)
{
int err = Marshal.GetLastWin32Error();
Log.Error("[CrashDump] MiniDumpWriteDump 실패 (Win32 Error={Err})", err);
}
}
#endif
}