You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

log4net RollingFileAppender多线程下异常创建嵌套目录问题求助

问题分析与解决方案

问题现象

多线程场景下使用log4net的RollingFileAppender,通过PropertyFilter和ThreadContext.Properties区分任务专属日志,任务启动时创建Appender、finally块中移除,但随机出现异常目录结构:

  • 生成类似SampleApp.log2022\11\Today\AppenderName.log的错误路径
  • 甚至出现多层嵌套目录:SampleApp.log2022\11\Today\SampleApp.log2022\11\Today\...\AppenderName.log

核心原因

  1. File与DatePattern配置冲突:当StaticLogFileName=false时,log4net会将DatePattern追加到File指定的路径后。若File包含完整文件名(而非纯目录),日期模式会被拼接到文件名尾部,导致原文件名被当作目录前缀,进而生成嵌套结构。
  2. Appender操作的线程安全缺失:log4net的Hierarchy和Root对象的添加/移除Appender操作并非线程安全,并发场景下会出现Appender状态混乱,引发路径解析异常。
  3. ThreadContext.Properties线程污染:任务线程被线程池复用后,若未清理ThreadContext.Properties中的标识,会导致日志被错误路由到其他Appender,间接触发路径异常。

解决方案

1. 修正File与DatePattern配置逻辑

File属性仅指定纯目录路径(确保结尾带路径分隔符),文件名定义在DatePattern中:

public void CreateRollingFileAppender
(
    string appenderName,
    string fileName,
    PropertyFilter filter = null,
    string logPath = ""
)
{
    if (logPath == "") logPath = _logConfiguration.LogPath;
    // 确保logPath是纯目录,若原路径带文件名则提取目录部分
    var logDir = Path.HasExtension(logPath) ? Path.GetDirectoryName(logPath) : logPath;
    // 保证目录路径结尾带路径分隔符
    logDir = Path.Combine(logDir, "");

    lock (_appenderLock)
    {
        if (Root.GetAppender(appenderName) != null) return;

        var patternLayout = new PatternLayout(_logConfiguration.Appender.PatternLayout);
        var denyAllFilter = new DenyAllFilter();
        var appender = new RollingFileAppender
        {
            Name = appenderName,
            File = logDir,
            StaticLogFileName = false,
            Layout = patternLayout,
            ImmediateFlush = true,
            AppendToFile = true,
            MaxSizeRollBackups = 5,
            MaximumFileSize = _logConfiguration.Appender.MaxSize,
            RollingStyle = RollingFileAppender.RollingMode.Date,
            // 直接在DatePattern中定义完整文件名格式
            DatePattern = $@"yyyy\\MM\\'Today'\\{fileName}.log",
            LockingModel = new FileAppender.MinimalLock()
        };

        if (filter != null)
        {
            appender.AddFilter(filter);
            appender.AddFilter(denyAllFilter);
        }

        appender.ActivateOptions();
        Root.AddAppender(appender);
        Repository.Configured = true;
    }
}

2. 为Appender操作添加线程锁

定义类级别锁对象,确保添加/移除Appender的操作线程安全:

// 类内部定义锁对象
private readonly object _appenderLock = new object();

public void RemoveAppender(string appenderName)
{
    lock (_appenderLock)
    {
        var appender = Root.GetAppender(appenderName);
        if (appender != null)
        {
            appender.Close();
            Root.RemoveAppender(appenderName);
        }
    }
}

3. 清理ThreadContext.Properties避免线程污染

在finally块中移除线程上下文属性,防止线程池复用导致的错误路由:

CreateRollingFileAppender("Test", "Test", CreateThreadFilter())

try
{
   ...
   logger.Info(message);
}
catch (Exception e)
{
   logger.Error(e);
}
finally
{
  // 清理线程上下文属性
  ThreadContext.Properties.Remove(Key);
  RemoveAppender("Test");
}

4. 替换BasicConfigurator.Configure调用

BasicConfigurator.Configure会重置整个日志配置,改为直接向Root添加Appender,避免影响已有配置:

// 替换原BasicConfigurator.Configure(Repository, appender);
Root.AddAppender(appender);
Repository.Configured = true;

验证建议

  • 模拟高并发任务场景,观察是否仍生成异常目录
  • 检查线程池复用后的日志路由是否正常
  • 确认Appender的添加/移除操作不会导致配置混乱

内容的提问来源于stack exchange,提问作者tsage080667

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 20:40:30