Log4j FileAppender间歇性FileNotFoundException问题求助
多线程下Log4j 1.1.3间歇性FileNotFoundException问题解决
问题根源
- getLogger()方法非线程安全:多线程并发调用时,多个线程会同时进入Logger初始化分支,重复执行文件创建与Appender添加操作,引发资源竞争。
- FileAppender父目录创建竞争:Log4j 1.1.3的
setFile方法中,当日志文件父目录不存在时,多个线程会同时触发父目录创建逻辑。后执行的线程会因mkdirs()返回false(父目录已被前序线程创建),直接抛出原始的FileNotFoundException。
解决方案
1. 同步Logger初始化逻辑
给getLogger()的初始化部分添加线程安全控制,确保同一时间只有一个线程执行Logger创建与配置:
public Logger getLogger() { if (this.logger != null) { return this.logger; } // 双重检查锁避免不必要的同步 synchronized (this.getClass()) { if (this.logger != null) { return this.logger; } String outputAttr = getAttributeValue(OUTPUT_FILE_ATTR_QNAME); if (outputAttr != null) { try { File file = new File(outputAttr); String outputFile = file.getCanonicalPath(); // 提前创建父目录,规避FileAppender内部的竞争逻辑 File parentDir = file.getParentFile(); if (parentDir != null && !parentDir.exists()) { parentDir.mkdirs(); } this.logger = Logger.getLogger(outputFile); this.logger.setAdditivity(false); Layout layout = new PatternLayout(ProcessingNode.logPattern); FileAppender fileAppender = new FileAppender(layout, outputFile, false); fileAppender.setEncoding("UTF-8"); this.logger.addAppender(fileAppender); this.logger.setLevel(Level.INFO); return this.logger; } catch (Exception e) { e.printStackTrace(System.err); } } else if (this.parent != null) { return this.parent.getLogger(); } } return null; }
2. 预创建日志父目录
在测试套件启动前,统一创建所有测试用例可能用到的日志文件父目录,避免运行时动态创建的竞争:
@BeforeSuite public void initLogDirectories() { // 替换为实际获取所有测试日志路径的逻辑 List<String> logFilePaths = getAllTestLogPaths(); for (String path : logFilePaths) { File parentDir = new File(path).getParentFile(); if (parentDir != null && !parentDir.exists()) { parentDir.mkdirs(); } } }
3. 升级Log4j版本(推荐)
Log4j 1.x已停止维护,存在大量线程安全与安全漏洞问题。升级至Log4j 2.x后,其内部实现了更完善的线程安全控制,文件创建逻辑的竞争问题已被修复。
4. 全局缓存Logger实例
用线程安全的缓存容器复用同一文件路径的Logger,避免重复创建:
private static final Map<String, Logger> LOGGER_CACHE = new ConcurrentHashMap<>(); public Logger getLogger() { if (this.logger != null) { return this.logger; } String outputAttr = getAttributeValue(OUTPUT_FILE_ATTR_QNAME); if (outputAttr != null) { try { File file = new File(outputAttr); String outputFile = file.getCanonicalPath(); // 利用ConcurrentHashMap的原子方法确保单例初始化 this.logger = LOGGER_CACHE.computeIfAbsent(outputFile, key -> { Logger logger = Logger.getLogger(key); logger.setAdditivity(false); Layout layout = new PatternLayout(ProcessingNode.logPattern); try { File parentDir = file.getParentFile(); if (parentDir != null && !parentDir.exists()) { parentDir.mkdirs(); } FileAppender fileAppender = new FileAppender(layout, key, false); fileAppender.setEncoding("UTF-8"); logger.addAppender(fileAppender); logger.setLevel(Level.INFO); } catch (Exception e) { e.printStackTrace(System.err); } return logger; }); return this.logger; } catch (Exception e) { e.printStackTrace(System.err); } } else if (this.parent != null) { return this.parent.getLogger(); } return null; }
内容的提问来源于stack exchange,提问作者singh.indolia
相关产品推荐
相关产品推荐

