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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 22:22:31