Quarkus Uber Jar模式下Log4J上下文初始化异常求助
Quarkus 2.16.7.Final Uber-Jar模式下Log4J上下文初始化问题解决方案
问题背景
基于Quarkus 2.16.7.Final迁移原J2EE项目,通过自定义LoggingContext类初始化Log4J上下文,核心代码如下:
public synchronized void initialize(ApplicationContext currentApplicationContext) throws LoggingContextException { if (!isInitialized()) { File configurationFile = null; configurationFile = new File(getConfigurationContext().getLog4jFilePath()); if (!configurationFile.exists()) { throw new LoggingContextException("Could not find LOG4J configuration file: \"" + getConfigurationContext().getLog4jFilePath() + "\"."); } this.loggingId = currentApplicationContext.getApplicationName(); try (FileInputStream fis = new FileInputStream(configurationFile);) { this.loggerContext = LoggerContext.getContext(getConfigurationContext().getClassLoader(), false, null); loggerContextOwnership = initLoggerContext(this.loggerContext, configurationFile); if (loggerContextOwnership) { System.out.println(loggingId + " - Logging Context INITIALIZED (LOG4J File Path: \"" + this.loggerContext.getConfiguration().getConfigurationSource().getFile() + "\"). <<<"); } else { File configFile = loggerContext.getConfiguration().getConfigurationSource().getFile(); if (!configFile.getAbsolutePath().equals(configurationFile.getAbsolutePath())) { throw new LoggingContextException("Could not acquire LOG4J context ownership."); } } } catch (IOException e) { throw new LoggingContextException("Could not read LOG4J configuration file", e); } this.initialized = true; super.initialize(currentApplicationContext); } } private boolean initLoggerContext(LoggerContext loggerContext, File log4jConfigurationFile) { boolean hasOwnership = false; Configuration xmlConfig = ConfigurationFactory.getInstance().getConfiguration(null, "Application Framework LOG4J configuration", log4jConfigurationFile.toURI()); File configFile = loggerContext.getConfiguration().getConfigurationSource().getFile(); String key = getConfigurationContext().getLog4jFilePath(); if (configFile == null) { loggerContext.setConfiguration(xmlConfig); hasOwnership = true; synchronized (log4jContextMap) { List<LOG4JContext> list = log4jContextMap.get(getConfigurationContext().getClassLoader()).computeIfAbsent(key, k -> { return new LinkedList<>(); }); list.add(this); } } else { if (!configFile.getAbsolutePath().equals(log4jConfigurationFile.getAbsolutePath())) { System.out.println(loggingId + " - WARNING: LOG4J Context already initialized (file: \"" + configFile.getAbsolutePath() + "\")."); LoggingOwnershipMode loggingOwnershipMode = getConfigurationContext().getLoggingOwnershipMode(); switch (loggingOwnershipMode) { case DEFAULT: break; case FORCE: loggerContext.setConfiguration(xmlConfig); hasOwnership = true; System.out.println(loggingId + " - WARNING: LOG4j Context has been reconfigured."); break; default: break; } } else { hasOwnership = true; synchronized (log4jContextMap) { List<LOG4JContext> list = log4jContextMap.get(getConfigurationContext().getClassLoader()).computeIfAbsent(key, k -> { return new LinkedList<>(); }); list.add(this); } } } return hasOwnership; }
当前仅Fast-Jar模式可正常初始化Log4J上下文,切换至Quarkus语义下的Uber-Jar/legacy-jar模式时出现异常,排除使用JBoss Logging,问题根源指向类加载机制差异。
核心分析
Quarkus两种打包模式的类加载逻辑存在本质区别:
- Fast-Jar采用分层类加载,应用类与依赖类分离,Log4J上下文可被自定义类加载器正确关联
- Uber-Jar将所有类打包至单一Jar,Quarkus会提前通过自身类加载器初始化核心组件,可能导致Log4J上下文被抢先创建,自定义代码无法获取所有权;同时Log4j1-2桥接包在Uber-Jar中的加载顺序易出现冲突,引发初始化异常。
解决建议
1. 对齐Log4J上下文的类加载器
在获取LoggerContext时,使用Quarkus官方提供的应用类加载器替代自定义类加载器,确保上下文与当前应用类加载器绑定:
// 替换原getConfigurationContext().getClassLoader() ClassLoader appClassLoader = io.quarkus.runtime.QuarkusClassLoader.getAppClassLoader(); this.loggerContext = LoggerContext.getContext(appClassLoader, false, null);
Uber-Jar模式下,Quarkus应用类加载器是唯一负责加载应用代码的加载器,可避免因加载器不一致导致的上下文归属错误。
2. 禁止Quarkus自动初始化日志
在application.properties中添加以下配置,关闭Quarkus自带的日志管理器与自动初始化逻辑,优先执行自定义Log4J初始化:
quarkus.log.enable-jboss-logging-manager=false quarkus.log.init-at-startup=false
3. 优化上下文所有权判断逻辑
在initLoggerContext方法中新增类加载器一致性检查,避免同一配置文件路径但上下文归属不同加载器的情况:
// 在路径判断分支中补充加载器检查 } else { // 新增:检查上下文类加载器是否与应用类加载器一致 if (loggerContext.getClassLoader().equals(getConfigurationContext().getClassLoader())) { hasOwnership = true; synchronized (log4jContextMap) { List<LOG4JContext> list = log4jContextMap.get(getConfigurationContext().getClassLoader()).computeIfAbsent(key, k -> { return new LinkedList<>(); }); list.add(this); } } else { // 加载器不一致时,强制重新配置并获取所有权 loggerContext.setConfiguration(xmlConfig); hasOwnership = true; System.out.println(loggingId + " - WARNING: LOG4J Context reconfigured due to classloader mismatch."); synchronized (log4jContextMap) { List<LOG4JContext> list = log4jContextMap.get(getConfigurationContext().getClassLoader()).computeIfAbsent(key, k -> { return new LinkedList<>(); }); list.add(this); } } }
4. 确保Log4J依赖的优先级
在pom.xml中排除Quarkus自带的JBoss Logging依赖,并明确Log4J版本与打包范围:
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-core</artifactId> <exclusions> <exclusion> <groupId>org.jboss.logging</groupId> <artifactId>jboss-logging</artifactId> </exclusion> <exclusion> <groupId>org.jboss.logmanager</groupId> <artifactId>jboss-logmanager</artifactId> </exclusion> </exclusions> </dependency> <!-- 指定与Quarkus 2.16.7.Final兼容的Log4J 2版本 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.17.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.17.1</version> </dependency> <!-- 若使用Log4j1-2桥接,添加以下依赖 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-1.2-api</artifactId> <version>2.17.1</version> </dependency>
5. 调试类加载器问题
在初始化代码中添加类加载器日志,定位加载器不一致问题:
// 在initialize方法中添加 ClassLoader appCL = getConfigurationContext().getClassLoader(); ClassLoader loggerCL = this.loggerContext.getClassLoader(); System.out.println("App ClassLoader: " + appCL.getClass().getName() + "@" + Integer.toHexString(appCL.hashCode())); System.out.println("LoggerContext ClassLoader: " + loggerCL.getClass().getName() + "@" + Integer.toHexString(loggerCL.hashCode()));
内容的提问来源于stack exchange,提问作者Lazarus
相关产品推荐
相关产品推荐

