931 字
5 分钟
WPF 实战:构建企业级全局异常捕获与 log4net 日志监控系统
在生产环境中,未经处理的异常会导致程序直接闪退(Crash),严重影响用户体验。为了实现“程序崩溃但不闪退”以及“错误可溯源”,我们需要建立一套完善的全局异常捕获与日志记录机制。
一、 为什么选择 log4net?
在 .NET 生态中,log4net 凭借其成熟、稳定和极高的配置灵活性,依然是许多企业级项目的首选方案:
- 分级记录:支持 DEBUG, INFO, WARN, ERROR, FATAL 多个级别。
- 动态配置:无需重新编译,通过修改 XML 即可改变日志输出逻辑。
- 性能优异:内部通过异步缓冲机制,对 UI 线程的阻塞极小。
二、 全局异常捕获架构
在 WPF 中,异常可能来源于三个维度,必须针对性地进行“围堵”:
2.1 核心实现方案
在 App.xaml.cs 中注册全局事件,这是程序的“最后一道防线”。
public partial class App : Application{ protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e);
// 1. 初始化日志系统 ZLogHelper.InitLog4Net();
// 2. 注册全维度异常监听 RegisterEvents(); }
private void RegisterEvents() { // [维度1] UI主线程未捕获异常:通常是由按钮点击、界面渲染等触发 this.DispatcherUnhandledException += App_DispatcherUnhandledException;
// [维度2] 非UI线程未捕获异常:如子线程 Thread 抛出的错误 AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
// [维度3] Task任务异常:处理 async/await 且未被观察到的异常 TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; }
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) { HandleException(e.Exception); e.Handled = true; // 设为 true 可拦截异常,防止程序直接崩溃退出 }
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { if (e.ExceptionObject is Exception ex) HandleException(ex); }
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { HandleException(e.Exception); e.SetObserved(); // 标记异常已被处理,避免触发终结器导致的闪退 }
private static void HandleException(Exception ex) { if (ex == null) return;
// 记录错误日志 ZLogHelper.Logerror.Error("系统捕获到未处理的异常:", ex);
// 弹出友好提示 Current.Dispatcher.Invoke(() => { MessageBox.Show($"程序遇到意外错误:{ex.Message}\n详情请查看日志文件。", "系统异常", MessageBoxButton.OK, MessageBoxImage.Error); }); }}三、 log4net 配置深度解析
在项目的 log4net.config 中,我们可以使用 RollingFileAppender 来实现自动分片存储。
提示:请确保该配置文件的属性设置为 “如果较新则复制” 到输出目录。
<?xml version="1.0" encoding="utf-8" ?><log4net> <appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender"> <file value="Logs/Error/" /> <appendToFile value="true" /> <rollingStyle value="Date" /> <staticLogFileName value="false" /> <datePattern value="yyyy-MM-dd'.htm'" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="<HR COLOR=red>%n【异常时间】:%d [%t] <BR>%n【日志级别】:%-5p <BR>%n【堆栈信息】:%c <BR>%n【详情】:%m <BR>%n" /> </layout> <filter type="log4net.Filter.LevelRangeFilter"> <levelMin value="ERROR" /> <levelMax value="FATAL" /> </filter> </appender>
<logger name="logerror"> <level value="ALL" /> <appender-ref ref="ErrorAppender" /> </logger></log4net>四、 退出时的资源回收
在 OnExit 中,除了关闭日志,还可以清理运行产生的空文件,保持项目目录整洁。
protected override void OnExit(ExitEventArgs e){ ZLogHelper.StopLog(); // 确保缓冲区内容全部写入磁盘 CleanupEmptyLogFiles(); base.OnExit(e);}
private void CleanupEmptyLogFiles(){ try { string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); if (!Directory.Exists(path)) return;
var files = Directory.GetFiles(path, "*.htm", SearchOption.AllDirectories); foreach (var file in files) { var info = new FileInfo(file); if (info.Length == 0) info.Delete(); // 删除没有任何记录的空日志 } } catch { /* 忽略清理过程中的异常 */ }}五、 总结与最佳实践
- 异常拦截优先级:对于
DispatcherUnhandledException,如果异常不影响后续运行,设置e.Handled = true可以保住程序进程。 - 避免捕获器崩溃:在
HandleException内部的代码务必极度稳健,严禁再次抛出未捕获异常。 - 异步注意点:
async void方法中的异常无法被全局捕获,应尽量使用async Task。
通过这套方案,你可以将 WPF 应用从“一碰就碎”转化为“坚韧稳健”。你会发现,解决生产环境 Bug 的速度将因详细的日志记录而提升数倍。
WPF 实战:构建企业级全局异常捕获与 log4net 日志监控系统
https://sw.rscclub.website/posts/wpflog4neterror/