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
核心原因
File与DatePattern配置冲突:当StaticLogFileName=false时,log4net会将DatePattern追加到File指定的路径后。若File包含完整文件名(而非纯目录),日期模式会被拼接到文件名尾部,导致原文件名被当作目录前缀,进而生成嵌套结构。- Appender操作的线程安全缺失:log4net的
Hierarchy和Root对象的添加/移除Appender操作并非线程安全,并发场景下会出现Appender状态混乱,引发路径解析异常。 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
相关产品推荐
相关产品推荐

