WPF全局异常处理:开发环境正常,客户端无法写入日志排查
WPF发布后未处理异常无法生成日志的排查与解决
你遇到的这个问题太常见了——开发环境下异常日志正常输出,一发布到用户端就“哑火”,结合你贴的代码和事件查看器里的.NET Runtime 1026错误,我帮你梳理几个核心问题点和解决办法:
一、异常处理注册时机太晚
你现在是在Application_Startup事件里注册全局异常处理,但WPF应用启动时,在Startup触发前就可能抛出异常——比如App.xaml解析错误、静态构造函数里的异常,这些都会绕过你在Startup里注册的逻辑,直接导致崩溃且无日志。
解决办法:把注册逻辑移到静态构造函数
静态构造函数是App类最早执行的代码,能覆盖启动阶段的所有异常:
public partial class App : Application { // 静态构造函数,最先执行,早于任何实例代码 static App() { try { // 注册非UI线程的全局异常 AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; System.Windows.Forms.Application.ThreadException += Application_ThreadException; } catch (Exception exc) { // 这里绝对不能依赖你的Logger!因为此时Logger可能还没初始化 // 直接写入本地可写目录做兜底 var fallbackLogPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MyApp_Fallback.log"); Directory.CreateDirectory(Path.GetDirectoryName(fallbackLogPath)); File.WriteAllText(fallbackLogPath, $"启动阶段致命异常:{exc.Message}\r\n堆栈:{exc.StackTrace}"); } } private void Application_Startup(object sender, StartupEventArgs e) { // 注册UI线程的Dispatcher异常(因为Dispatcher需要Application实例化后才能获取) Application.Current.DispatcherUnhandledException += AppDispatcherUnhandledException; // 其他启动逻辑... } // 你的其他异常处理方法保持不变... }
二、Logger的初始化或配置问题
发布后无日志,90%的概率是Logger本身出问题了:
- 配置文件没跟着发布:比如你用NLog/log4net的话,对应的.config文件在Visual Studio里要设置“复制到输出目录”为「如果较新则复制」,不然发布后会缺失。
- 日志目录无写入权限:不要把日志写到Program Files目录(默认只读),改用用户专属的可写目录,比如:
var logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MyAppLogs"); Directory.CreateDirectory(logDir); - Logger初始化滞后:如果你的Logger是在Startup里初始化的,那启动阶段的异常就抓不到,最好把Logger初始化也移到静态构造里。
三、DispatcherUnhandledException的兜底逻辑不足
你的ShowUnhandledException方法里,如果Logger调用失败,虽然有catch块,但可能因为权限问题连MessageBox都弹不出来,导致完全没有反馈。建议在catch块里强制写入兜底日志:
void ShowUnhandledException(DispatcherUnhandledExceptionEventArgs e) { try { Logger.LogEntry(LogModel.Create(this).Fatal("Dispatcher try")); Logger.LogEntry(LogModel.Create(this).Fatal(e.Exception.Message)); Logger.LogEntry(LogModel.Create(this).Fatal(e.Exception.StackTrace)); e.Handled = true; } catch (Exception exc) { // 不管怎样,一定要把日志写出去 var crashLogPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "MyAppLogs", "Crash_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".log"); Directory.CreateDirectory(Path.GetDirectoryName(crashLogPath)); var logContent = $"[{DateTime.Now}] 处理UI异常时出错:{exc.Message}\r\n" + $"原异常信息:{e.Exception.Message}\r\n" + $"原异常堆栈:{e.Exception.StackTrace}\r\n"; File.WriteAllText(crashLogPath, logContent); MessageBox.Show($"应用发生致命错误,日志已保存到:\r\n{crashLogPath}", "错误"); } }
四、结合事件查看器的错误信息定位
事件查看器里的.NET Runtime 1026错误,展开详情后能看到具体的异常类型和堆栈,你可以先看这个信息,确认异常是发生在启动阶段还是运行阶段,这样能更快锁定问题。
最后测试建议
发布后,手动在主窗口加载时模拟一个空对象调用(比如var obj = null; obj.ToString();),然后去兜底日志目录看有没有日志生成,逐步排查是Logger的问题还是异常注册时机的问题。
内容的提问来源于stack exchange,提问作者Raj
相关产品推荐
相关产品推荐

