fix : 도커 의존성 추가, 윈도우용 미니덤프 제거 Dotnet-dump사용
This commit is contained in:
@@ -5,7 +5,9 @@ WORKDIR /app
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||||
ARG BUILD_CONFIGURATION=Release
|
ARG BUILD_CONFIGURATION=Release
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
# ServerLib 의존성 포함해서 restore (캐시 레이어 최적화)
|
||||||
COPY ["MMOserver/MMOserver.csproj", "MMOserver/"]
|
COPY ["MMOserver/MMOserver.csproj", "MMOserver/"]
|
||||||
|
COPY ["ServerLib/ServerLib.csproj", "ServerLib/"]
|
||||||
RUN dotnet restore "MMOserver/MMOserver.csproj"
|
RUN dotnet restore "MMOserver/MMOserver.csproj"
|
||||||
COPY . .
|
COPY . .
|
||||||
WORKDIR "/src/MMOserver"
|
WORKDIR "/src/MMOserver"
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
<LangVersion>13</LangVersion>
|
<LangVersion>13</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<DebugType>portable</DebugType>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="LiteNetLib" Version="2.0.2" />
|
<PackageReference Include="LiteNetLib" Version="2.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.3" />
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@@ -6,12 +5,17 @@ using Serilog;
|
|||||||
namespace ServerLib.Utils;
|
namespace ServerLib.Utils;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 릴리즈 빌드 크래시 덤프 핸들러
|
/// 크래시 핸들러 (Windows / Linux 공통)
|
||||||
/// Register() 를 Program.cs 최상단에서 한 번 호출.
|
/// Register() 를 Program.cs 최상단에서 한 번 호출.
|
||||||
|
///
|
||||||
|
/// 덤프 생성은 CLR 환경변수로 처리 (스택 언와인드 전에 찍힘):
|
||||||
|
/// DOTNET_DbgEnableMiniDump=1
|
||||||
|
/// DOTNET_DbgMiniDumpType=4
|
||||||
|
/// DOTNET_DbgMiniDumpName=crashes/crash_%p_%t.dmp
|
||||||
|
///
|
||||||
/// 생성 파일 (crashes/ 폴더):
|
/// 생성 파일 (crashes/ 폴더):
|
||||||
/// Debug : crash_YYYY-MM-DD_HH-mm-ss.log
|
/// crash_YYYY-MM-DD_HH-mm-ss.log ← 항상 생성
|
||||||
/// Release : crash_YYYY-MM-DD_HH-mm-ss.log
|
/// crash_%p_%t.dmp ← CLR이 직접 생성 (정확한 크래시 위치)
|
||||||
/// crash_YYYY-MM-DD_HH-mm-ss.dmp ← 메모리 덤프 추가
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CrashDumpHandler
|
public static class CrashDumpHandler
|
||||||
{
|
{
|
||||||
@@ -27,7 +31,7 @@ public static class CrashDumpHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||||
|
|
||||||
Log.Information("[CrashDump] 핸들러 등록 완료 (CrashDir={Dir})", Path.GetFullPath(CRASH_DIR));
|
Log.Information("[CrashDump] 핸들러 등록 완료 (CrashDir={Dir})", Path.GetFullPath(CRASH_DIR));
|
||||||
}
|
}
|
||||||
@@ -36,52 +40,35 @@ public static class CrashDumpHandler
|
|||||||
|
|
||||||
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
// 예외 컨텍스트가 살아있는 지금 즉시 캡처
|
|
||||||
IntPtr exceptionPointers = Marshal.GetExceptionPointers();
|
|
||||||
Exception? ex = e.ExceptionObject as Exception;
|
Exception? ex = e.ExceptionObject as Exception;
|
||||||
|
|
||||||
string tag = $"[CrashDump] UnhandledException (IsTerminating={e.IsTerminating})";
|
string tag = $"[CrashDump] UnhandledException (IsTerminating={e.IsTerminating})";
|
||||||
WriteCrash(tag, ex, exceptionPointers);
|
WriteCrash(tag, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
// Task 예외는 파이널라이저 스레드에서 통보되므로 포인터가 없을 수 있음
|
|
||||||
IntPtr exceptionPointers = Marshal.GetExceptionPointers();
|
|
||||||
e.SetObserved(); // 프로세스 종료 방지
|
e.SetObserved(); // 프로세스 종료 방지
|
||||||
|
|
||||||
string tag = "[CrashDump] UnobservedTaskException";
|
string tag = "[CrashDump] UnobservedTaskException";
|
||||||
WriteCrash(tag, e.Exception, exceptionPointers);
|
WriteCrash(tag, e.Exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── 핵심 처리 ───────────────────────────────────────────────────────
|
// ─── 핵심 처리 ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
private static void WriteCrash(string tag, Exception? ex, IntPtr exceptionPointers)
|
private static void WriteCrash(string tag, Exception? ex)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(CRASH_DIR);
|
Directory.CreateDirectory(CRASH_DIR);
|
||||||
|
|
||||||
string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||||
string basePath = Path.Combine(CRASH_DIR, $"crash_{timestamp}");
|
string logPath = Path.Combine(CRASH_DIR, $"crash_{timestamp}.log");
|
||||||
|
|
||||||
// 1. 크래시 로그 작성
|
|
||||||
string logPath = $"{basePath}.log";
|
|
||||||
WriteCrashLog(logPath, ex);
|
WriteCrashLog(logPath, ex);
|
||||||
|
|
||||||
Log.Fatal("{Tag} → {Log}", tag, logPath);
|
Log.Fatal("{Tag} → {Log}", tag, logPath);
|
||||||
|
|
||||||
#if !DEBUG
|
|
||||||
// 2. 메모리 덤프 작성 (Release only)
|
|
||||||
string dmpPath = $"{basePath}.dmp";
|
|
||||||
WriteDumpFile(dmpPath, exceptionPointers);
|
|
||||||
Log.Fatal("[CrashDump] 덤프 파일 저장 완료 → {Dmp}", dmpPath);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
catch (Exception writeEx)
|
catch (Exception writeEx)
|
||||||
{
|
{
|
||||||
// 덤프 저장 실패는 무시 (이미 크래시 중이므로)
|
Log.Error(writeEx, "[CrashDump] 로그 저장 실패");
|
||||||
Log.Error(writeEx, "[CrashDump] 덤프 저장 실패");
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -97,21 +84,18 @@ public static class CrashDumpHandler
|
|||||||
sb.AppendLine("═══════════════════════════════════════════════════");
|
sb.AppendLine("═══════════════════════════════════════════════════");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
|
|
||||||
// 환경 정보
|
|
||||||
sb.AppendLine("[Environment]");
|
sb.AppendLine("[Environment]");
|
||||||
sb.AppendLine($" OS : {RuntimeInformation.OSDescription}");
|
sb.AppendLine($" OS : {RuntimeInformation.OSDescription}");
|
||||||
sb.AppendLine($" Runtime : {RuntimeInformation.FrameworkDescription}");
|
sb.AppendLine($" Runtime : {RuntimeInformation.FrameworkDescription}");
|
||||||
sb.AppendLine($" PID : {Environment.ProcessId}");
|
sb.AppendLine($" PID : {Environment.ProcessId}");
|
||||||
sb.AppendLine($" WorkDir : {Environment.CurrentDirectory}");
|
sb.AppendLine($" WorkDir : {Environment.CurrentDirectory}");
|
||||||
sb.AppendLine($" MachineName: {Environment.MachineName}");
|
sb.AppendLine($" MachineName: {Environment.MachineName}");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
|
|
||||||
// 스레드 정보
|
|
||||||
sb.AppendLine("[Thread]");
|
sb.AppendLine("[Thread]");
|
||||||
sb.AppendLine($" ThreadId : {Environment.CurrentManagedThreadId}");
|
sb.AppendLine($" ThreadId : {Environment.CurrentManagedThreadId}");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
|
|
||||||
// 예외 정보
|
|
||||||
sb.AppendLine("[Exception]");
|
sb.AppendLine("[Exception]");
|
||||||
if (ex is null)
|
if (ex is null)
|
||||||
{
|
{
|
||||||
@@ -158,75 +142,4 @@ public static class CrashDumpHandler
|
|||||||
AppendException(sb, ex.InnerException, depth + 1);
|
AppendException(sb, ex.InnerException, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !DEBUG
|
|
||||||
// ─── Windows P/Invoke ────────────────────────────────────────────────
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
|
||||||
private struct MinidumpExceptionInformation
|
|
||||||
{
|
|
||||||
public uint ThreadId;
|
|
||||||
public IntPtr ExceptionPointers;
|
|
||||||
public int ClientPointers; // 0 = 서버 자신의 주소 공간
|
|
||||||
}
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll")]
|
|
||||||
private static extern uint GetCurrentThreadId();
|
|
||||||
|
|
||||||
[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 MINI_DUMP_WITH_FULL_MEMORY = 0x00000002;
|
|
||||||
|
|
||||||
private static void WriteDumpFile(string path, IntPtr exceptionPointers)
|
|
||||||
{
|
|
||||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
||||||
{
|
|
||||||
Log.Warning("[CrashDump] 덤프 생성은 Windows만 지원");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using Process process = Process.GetCurrentProcess();
|
|
||||||
using FileStream fs = new(path, FileMode.Create, FileAccess.Write, FileShare.None);
|
|
||||||
|
|
||||||
IntPtr exInfoPtr = IntPtr.Zero;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (exceptionPointers != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
MinidumpExceptionInformation exInfo = new()
|
|
||||||
{
|
|
||||||
ThreadId = GetCurrentThreadId(),
|
|
||||||
ExceptionPointers = exceptionPointers,
|
|
||||||
ClientPointers = 0
|
|
||||||
};
|
|
||||||
exInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(exInfo));
|
|
||||||
Marshal.StructureToPtr(exInfo, exInfoPtr, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool success = MiniDumpWriteDump(
|
|
||||||
process.Handle,
|
|
||||||
(uint)process.Id,
|
|
||||||
fs.SafeFileHandle.DangerousGetHandle(),
|
|
||||||
MINI_DUMP_WITH_FULL_MEMORY,
|
|
||||||
exInfoPtr, // 크래시 원인 스레드 & 예외 정보
|
|
||||||
IntPtr.Zero, IntPtr.Zero);
|
|
||||||
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
int err = Marshal.GetLastWin32Error();
|
|
||||||
Log.Error("[CrashDump] MiniDumpWriteDump 실패 (Win32 Error={Err})", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (exInfoPtr != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(exInfoPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,10 @@
|
|||||||
ports:
|
ports:
|
||||||
- "9050:9050/udp" # LiteNetLib UDP 포트
|
- "9050:9050/udp" # LiteNetLib UDP 포트
|
||||||
- "9500:9500/udp" # LiteNetLib UDP 포트
|
- "9500:9500/udp" # LiteNetLib UDP 포트
|
||||||
|
environment:
|
||||||
|
- DOTNET_DbgEnableMiniDump=1 # 크래시 시 덤프 자동 생성
|
||||||
|
- DOTNET_DbgMiniDumpType=4 # 4 = Full dump
|
||||||
|
- DOTNET_DbgMiniDumpName=/app/crashes/crash_%p_%t.dmp
|
||||||
|
volumes:
|
||||||
|
- ./crashes:/app/crashes # 덤프/로그 로컬 마운트
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
Reference in New Issue
Block a user