[spring-projects/spring-boot]在本机映像中支持 Janino

2024-06-26 756 views
4

使用 Spring Boot Maven 插件构建原生镜像时,依赖于 Janino 的 Logback 配置会导致错误。

例如,如果我们向文件添加logback-spring.xml以下 if 语句:

        <if condition='isDefined("ENV")'>
            <then>
                <appender-ref ref="CONSOLE_JSON"/>
            </then>
            <else>
                <appender-ref ref="CONSOLE_LOCAL"/>
            </else>
        </if>

然后运行本机映像会导致此错误:

Logging system failed to initialize using configuration from 'null'
java.util.EmptyStackException
    at java.base@17.0.5/java.util.Stack.peek(Stack.java:101)
    at ch.qos.logback.core.model.processor.ModelInterpretationContext.peekModel(ModelInterpretationContext.java:84)
    at ch.qos.logback.core.model.processor.conditional.ElseModelHandler.handle(ElseModelHandler.java:45)
    at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:241)
    at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:253)
    at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:253)
    at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:253)
    at ch.qos.logback.core.model.processor.DefaultProcessor.traversalLoop(DefaultProcessor.java:90)
    at ch.qos.logback.core.model.processor.DefaultProcessor.process(DefaultProcessor.java:106)
    at ch.qos.logback.core.joran.GenericXMLConfigurator.processModel(GenericXMLConfigurator.java:200)
    at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.processModel(SpringBootJoranConfigurator.java:122)
    at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.configureUsingAotGeneratedArtifacts(SpringBootJoranConfigurator.java:115)
    at org.springframework.boot.logging.logback.LogbackLoggingSystem.initializeFromAotGeneratedArtifactsIfPossible(LogbackLoggingSystem.java:210)
    at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:187)
    at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:332)
    at org.springframework.boot.context.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:298)
    at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:246)
    at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:223)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131)
    at org.springframework.boot.context.event.EventPublishingRunListener.multicastInitialEvent(EventPublishingRunListener.java:136)
    at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:81)
    at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:64)
    at java.base@17.0.5/java.lang.Iterable.forEach(Iterable.java:75)
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118)
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112)
    at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:63)
    at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:352)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291)

请注意,运行 fat-jar 对-Dspring.aot.enabled=true重现该问题没有帮助。

如果能对原生图像有这样的支持那就太好了。

回答

8

在 stackoverflow 问题的示例中,如果没有添加元数据,我也会收到原始帖子中的异常src/main/resources/META-INF/native-image

2

不幸的是,支持 Janino 并非易事。它依赖于ch.qos.logback.core.joran.conditional.Condition在运行时生成实现,而 Graal 并不支持这一点。Spring Native 使用替换解决了这个问题。我们没有这个选项。我们不会在 Boot 中使用替换,因为 Graal 团队已经明确表示不应使用它们

我们可以探索的一个选项是Condition在构建时生成实现,将它们包含在本机映像中,然后在运行时重用这些预生成的类。这就是 Framework 对基于 CGLib 的代理所做的。如果没有必要的挂钩点,如果不更改 Logback,这可能就无法实现。

5

我已经在这个分支中制作了一些东西的原型。虽然代码不多,但我并不满意AotIfModelHandler。它重复了 Logback 中的许多逻辑IfModelHandler,可能会出现不同步的风险。我想与团队的其他成员讨论我们的选择。我的感觉是,如果不对 Logback 进行一些更改,我们将无法在这里做任何事情。

0

@wilkinsona 感谢您如此迅速地实施 #34315!来自 stackoverflow 问题的示例应用程序现在提前失败并记录了一个有意义的异常。我已将其添加到 SO 问题中。

4

您是否有任何解决方法,可以通过编程编写等效程序并有机会使其与 spring native 一起工作?

1

@ceki 你会考虑做一些改变来IfModelHandler让它更适合 Graal 吗?我们希望能够捕获它创建的类文件,以便将它们贡献给 GraalVM 原生镜像。然后,当执行镜像时,我们希望能够将这些类提供给处理程序,而不是再次编译它们。

3

@wilkinsona当然,我愿意接受改变。我已经看到了你使用 GraalVM 支持 logback 的努力,这令人印象深刻。

供您参考,我已经开始探索支持 GraalVM 的途径,您可能会感兴趣:

注意:1.4.9 中的实验性变化(提交 bd11e72017941331f2c3ba5101429359aced5a47 尚未发布)

  • JoranConfigurator 通过反射调用,不再作为服务。
  • JoranConfigurator 现在支持<serializeModel file="...."/>logback.xml 中的指令,将其创建的模型转储/序列化到带有 .smo 扩展名的 ObjectOutputStream 文件中。
  • SerializedModelConfigurator 搜索序列化模型文件(.smo 扩展名),如果找到 logback.smo 或 logback-test.smo 文件,则配置 logback。
  • SerializedModelConfigurator 作为配置器服务被调用。这发生在通过序列化调用 JoranConfigurator 之前。

无论如何,我仍在进行实验,并且不确定结果,即这是否可以与 GraalVM 一起使用而不需要 XML 解析器。

回到你最初的问题,我明白上面概述的实验方法与 Janino 提出的问题不同。

更新:上述策略已被验证可以在 GraalVM 本机映像中工作,至少在简单的场景中是如此。

1

@wilkinsona 从提交 c440e08b开始,logback 支持加载模型配置文件,而无需加载 JoranConfigurator,也无需在本机映像中包含 java.xml。我已验证此功能可与 GraalVM 配合使用。

0

听起来不错。谢谢,@ceki。Logback 快照在任何地方发布过吗?我想尝试一下新的序列化支持,看看我们是否可以删除我写的东西。

3

@wilkinsona Logback 快照未发布。您需要从 github 源中签出并使用 JDK 11+ 和 Maven 在您的终端进行构建。

我将很快记录使用序列化模型 (SMO) 文件的过程。唯一“棘手”的部分是记得 <serializeModel file="${aVariablePointingToADestinationFile}"/>在 logback.xml 中添加指令。

请注意,这项工作仍在进行中。公开发布前还需要进行更多测试。

今天早上(6 月 24 日)刚刚修复了一个愚蠢的错误。

4

嘿,文件的使用是否scmo允许有条件地使用 janino 库?我目前有一个logback-spring.xml看起来像这样的:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">

    <serializeModel file="src/main/resources/logback.scmo"/>

    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>

    <property name="LOGGING_LEVEL" value="${LOGGING_LEVEL:-INFO}"/>
    <property name="CONSOLE_LOG_THRESHOLD" value="${CONSOLE_LOG_THRESHOLD:-INFO}"/>

    <springProfile name="!dev">
        <if condition='p("LOGGING_LAYOUT").toUpperCase().equals("TEXT")'>
            <then>
                <!-- use the default TEXT layout -->
            </then>
            <else>
                <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
                    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                        <level>${CONSOLE_LOG_THRESHOLD}</level>
                    </filter>
                </appender>
                <logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
                    <appender-ref ref="CONSOLE"/>
                </logger>
            </else>
        </if>
    </springProfile>

    <root level="${LOGGING_LEVEL}">
        <appender-ref ref="CONSOLE"/>
    </root>

</configuration>

1-当使用scmo文件时,我EmptyStackException在运行本机可执行文件(以下gradle nativeCompile命令)时继续使用。

2- 使用该文件时scmo,出现以下错误:

InvalidClassException: Unauthorized deserialization attempt; org.springframework.boot.logging.logback.SpringProfileModel
InvalidClassException: Unauthorized deserialization attempt; ch.qos.logback.core.model.conditional.IfModel

该错误来自ch.qos.logback.classic.joran.SerializedModelConfigurator以下指令:

HardenedModelInputStream hmis = new HardenedModelInputStream(is);
Model model = (Model) hmis.readObject();

我无法确定我的本机配置是否有问题,或者是否ch.qos.logback.classic.net.server.HardenedModelInputStream.getWhitelist()与不包含这两个类的方法有关