一、为什么要在WPF中整合Log4net

开发过WPF应用的朋友都知道,当程序在客户现场出现问题时,最头疼的就是找不到日志。控制台输出在正式环境中根本看不见,自己写文本日志又容易遇到性能问题。这时候就需要一个成熟的日志框架——Log4net。

Log4net是Apache推出的.NET日志记录组件,它支持多种输出方式(文件、数据库、控制台等),能根据日志级别灵活过滤信息,还能自动按日期或大小分割日志文件。在WPF中整合它,相当于给应用装了个"黑匣子",发生异常时就能快速定位问题。

二、Log4net的安装与基础配置

首先通过NuGet安装Log4net包:

Install-Package log4net

接着在App.xaml.cs中初始化配置。建议将配置文件独立为log4net.config,这样修改时无需重新编译:

// App.xaml.cs
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        // 加载配置文件(确保文件属性设置为"始终复制")
        var configFile = new FileInfo("log4net.config");
        XmlConfigurator.ConfigureAndWatch(configFile);
        
        // 测试日志输出
        ILog logger = LogManager.GetLogger(typeof(App));
        logger.Info("应用程序启动完成");
    }
}

对应的log4net.config示例:

<!-- 日志输出到文件和控制台 -->
<log4net>
    <root>
        <level value="ALL" />
        <appender-ref ref="RollingFileAppender" />
        <appender-ref ref="ConsoleAppender" />
    </root>
    
    <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
        <file value="Logs/Application.log" />
        <appendToFile value="true" />
        <rollingStyle value="Composite" />
        <maxSizeRollBackups value="10" />
        <maximumFileSize value="10MB" />
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
        </layout>
    </appender>
    
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
        </layout>
    </appender>
</log4net>

三、在MVVM模式中的实战应用

在WPF的MVVM架构中,推荐通过依赖注入方式使用日志。以下是结合Prism的示例:

// 注册Log4net服务
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterInstance<ILog>(LogManager.GetLogger(typeof(App)));
}

// ViewModel中使用
public class MainViewModel : BindableBase
{
    private readonly ILog _logger;
    
    public MainViewModel(ILog logger)
    {
        _logger = logger;
        _logger.Debug("ViewModel初始化开始");
        
        try {
            // 业务逻辑代码...
        } 
        catch (Exception ex) {
            _logger.Error("业务逻辑异常", ex);
        }
    }
}

关键技巧:

  1. 对敏感信息使用Debug级别,生产环境关闭该级别
  2. 异常日志一定要包含完整堆栈(logger.Error(message, exception)
  3. 耗时操作用using (LogExtension.TimeLog("操作名称"))封装

四、高级功能与性能优化

4.1 动态调整日志级别

通过配置文件热更新实现动态调整:

var repo = LogManager.GetRepository();
var level = LoggerManager.GetLogger(repo.Name).Level;
level = Level.Debug;  // 运行时切换级别
repo.Threshold = level;
repo.RaiseConfigurationChanged();

4.2 异步日志记录

使用AsyncAppender避免阻塞UI线程:

<appender name="AsyncForwarder" type="Log4Net.Async.AsyncForwardingAppender">
    <appender-ref ref="RollingFileAppender" />
</appender>

4.3 日志上下文增强

通过ThreadContext添加环境信息:

using(ThreadContext.Stacks["NDC"].Push("用户操作流")) {
    _logger.Info("用户点击保存按钮");
}

五、典型问题排查指南

场景1:日志文件未生成

  • 检查配置文件路径是否正确
  • 确认日志目录有写入权限
  • 在代码中添加log4net.Util.LogLog.InternalDebugging = true开启内部调试

场景2:日志内容不完整

  • 检查<filter>标签是否过滤了某些级别
  • 确认没有多个log4net.config文件冲突

场景3:性能下降明显

  • 避免在循环中记录Debug日志
  • 对高频日志改用BufferingForwardingAppender

六、技术方案对比

方案 优点 缺点
Log4net 功能全面,社区成熟 配置稍复杂
NLog 配置简单,性能优异 高级功能较少
Serilog 结构化日志支持好 依赖较多扩展包

七、总结与最佳实践

经过实际项目验证,推荐以下实践方案:

  1. 开发阶段使用DEBUG级别+控制台输出
  2. 生产环境采用INFO级别+按日分割文件
  3. 关键业务流程添加TRACE级别详细日志
  4. 使用<filter>过滤敏感信息日志

完整的示例项目结构建议:

/App
  /Logs         # 日志目录
  /Configs
    log4net.config  # 日志配置
  /Services
    LoggerService.cs # 日志服务封装