`PluginBuilder` reads builder fields that `GraalVmProcessor` does not register, so `LevelMatchFilter`, `SystemPropertyArbiter` and others fail in GraalVM native images
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 35/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- java
調査の方向性
The runtime side is PluginBuilder.injectFields() and validateFields(), which currently walk every declared field via TypeUtil.getAllDeclaredFields. The metadata side is GraalVmProcessor and its Annotations helper, which need to handle @PluginAttribute. Start by reproducing the native-image failure with the issue's log4j2.xml and demo classes, then check that GraalVmProcessorTest gets a fixture with a @PluginAttribute builder field. Done means the native output matches the JVM output and the new PluginBuilderTest passes.
索引モデルが issue の本文から書いたものです。
説明
Description
I first reported this to Spring Boot (spring-projects/spring-boot#52046) because their SpringProfileArbiter broke in our native image. Andy pointed out that the metadata comes from GraalVmProcessor, so the fix belongs here. After digging in, it isn't Spring specific. On current GraalVM CE a plain Log4j 2.26.1 app hits it with Log4j's own filters and arbiters.
There are two gaps between what PluginBuilder does at runtime and what GraalVmProcessor registers.
-
PluginBuilder.injectFields()andPluginBuilder.validateFields()take every declared field in the builder hierarchy (TypeUtil.getAllDeclaredFields) and callsetAccessibleon all of them.validateFields()then callsField.get()on each one, including fields with no plugin annotation.GraalVmProcessoronly registers the annotated fields. On GraalVM CE 25.0.2 this works by accident, becausegetDeclaredFields()hides fields that aren't registered. On GraalVM CE 25.4.4.1.1 (JDK 25.0.4)getDeclaredFields()returns all of them, soField.get()on astatic finalconstant or a static logger throwsMissingReflectionRegistrationErrorand the plugin isn't created. I get the same failure on 25.0.2 as soon as anything registers the builder type in the newerreachability-metadata.jsonformat, even when it lists only the annotated fields. -
GraalVmProcessordoesn't handle@PluginAttribute.PluginBuilderaccepts it on builder fields (the visitor lookup is generic) andRoutes.BuilderandKafkaAppender.Builderuse it, but those fields get no metadata. In a native image the attribute is rejected as invalid and the builder keeps its defaults. This one also fails on 25.0.2.
These builders in log4j-core 2.26.1 have fields that the shipped metadata doesn't cover. I found them by comparing declared fields against the reflect-config.json files in the jar, and I ran LevelMatchFilter and SystemPropertyArbiter natively.
- Every builder that extends
AbstractFilter.AbstractFilterBuilder(staticATTR_ON_MATCH/ATTR_ON_MISMATCH), soLevelMatchFilter,BurstFilter,StringMatchFilter,MutableThreadContextMapFilter SystemPropertyArbiter,EnvironmentArbiter,ClassArbiter(staticATTR_*constants) andScriptArbiter(staticLOGGER)PatternMatch.Builder(serialVersionUID)PosixViewAttributeAction.Builder(subst,filePermissions) andJmsAppender.Builder(jmsManager), plain fields with no annotationRoutes.Builder(pattern) andKafkaAppender.Builder(all attributes), which use@PluginAttributeon fields, so<Routes pattern="...">in aRoutingAppenderloses its pattern
Outside Log4j, Spring Boot's SpringProfileArbiter.Builder (static statusLogger) and SpringBootTriggeringPolicyBuilder (@PluginAttribute fields) have the same problems.
It fails quietly. The status logger prints errors and the configuration carries on without the plugin, so a filter disappears and lets through events it should deny, or a <Select> gets dropped and the appender loses its layout.
Configuration
Version: 2.26.1 (same code on 2.x at d1e96de)
Operating system: Linux (Docker, toolchain from ghcr.io/graalvm/native-image-community:25)
JDK: GraalVM CE 25.4.4.1.1 (JDK 25.0.4.1.1), default native-image options. GraalVM CE 25.0.2 only shows the @PluginAttribute problem. We first saw this in a Spring Boot app with GraalVM CE and Liberica NIK on JDK 25.0.4.
Logs
Native run on GraalVM CE 25.4.4.1.1 with the reproducer below (trimmed):
main ERROR Could not create plugin of type class org.apache.logging.log4j.core.config.arbiters.SystemPropertyArbiter for element SystemPropertyArbiter: org.graalvm.nativeimage.MissingReflectionRegistrationError: Cannot reflectively read or write field 'public static final java.lang.String org.apache.logging.log4j.core.config.arbiters.SystemPropertyArbiter$Builder.ATTR_PROPERTY_NAME'. [...]
at [email protected]/java.lang.reflect.Field.get(Field.java:440)
at org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.validateFields(PluginBuilder.java:220)
at org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.injectFields(PluginBuilder.java:203)
at org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.build(PluginBuilder.java:123)
[...]
main ERROR Unable to invoke factory method in class org.apache.logging.log4j.core.config.arbiters.SystemPropertyArbiter for element SystemPropertyArbiter: java.lang.IllegalStateException: No factory method found for class org.apache.logging.log4j.core.config.arbiters.SystemPropertyArbiter
main ERROR Error capturing node data for node Console
main ERROR AttributeArbiter contains an invalid element or attribute "active"
main ERROR appender Console has no parameter that matches element Select
main ERROR Could not create plugin of type class org.apache.logging.log4j.core.filter.LevelMatchFilter for element LevelMatchFilter: org.graalvm.nativeimage.MissingReflectionRegistrationError: Cannot reflectively read or write field 'public static final java.lang.String org.apache.logging.log4j.core.filter.AbstractFilter$AbstractFilterBuilder.ATTR_ON_MISMATCH'. [...]
main ERROR Unable to invoke factory method in class org.apache.logging.log4j.core.filter.LevelMatchFilter for element LevelMatchFilter: java.lang.IllegalStateException: No factory method found for class org.apache.logging.log4j.core.filter.LevelMatchFilter
The No factory method found errors are just PluginBuilder falling back to a factory method after the builder fails. It's not #3871 again; the type-name fix from 2.25.3 is in place here.
Output on the JVM:
ARBITER INFO info message
CUSTOM INFO info message
ARBITER WARN warn message
FILTER WARN warn message
CUSTOM WARN warn message
Output in the native image (layout lost, filter gone, wrong <Select> branch):
info message
FILTER INFO info message
WRONG INFO info message
warn message
FILTER WARN warn message
WRONG WARN warn message
Reproduction
log4j2.xml:
<Configuration status="WARN">
<Appenders>
<Console name="Arbiter">
<Select>
<SystemPropertyArbiter propertyName="java.vm.name" propertyValue="does-not-match">
<PatternLayout pattern="WRONG %-5p %m%n"/>
</SystemPropertyArbiter>
<DefaultArbiter>
<PatternLayout pattern="ARBITER %-5p %m%n"/>
</DefaultArbiter>
</Select>
</Console>
<Console name="Filter">
<PatternLayout pattern="FILTER %-5p %m%n"/>
<LevelMatchFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
<Console name="Custom">
<Select>
<AttributeArbiter active="true">
<PatternLayout pattern="CUSTOM %-5p %m%n"/>
</AttributeArbiter>
<DefaultArbiter>
<PatternLayout pattern="WRONG %-5p %m%n"/>
</DefaultArbiter>
</Select>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Arbiter"/>
<AppenderRef ref="Filter"/>
<AppenderRef ref="Custom"/>
</Root>
</Loggers>
</Configuration>
demo/Main.java:
package demo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Main {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(Main.class);
logger.info("info message");
logger.warn("warn message");
}
}
demo/AttributeArbiter.java, a third-party plugin that uses @PluginAttribute on a builder field the way KafkaAppender.Builder does:
package demo;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.arbiters.Arbiter;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
@Plugin(name = "AttributeArbiter", category = Node.CATEGORY, elementType = Arbiter.ELEMENT_TYPE, deferChildren = true, printObject = true)
public final class AttributeArbiter implements Arbiter {
private final boolean active;
private AttributeArbiter(boolean active) {
this.active = active;
}
@Override
public boolean isCondition() {
return active;
}
@PluginBuilderFactory
public static Builder newBuilder() {
return new Builder();
}
public static final class Builder implements org.apache.logging.log4j.core.util.Builder<AttributeArbiter> {
@PluginAttribute("active")
private String active;
@Override
public AttributeArbiter build() {
return new AttributeArbiter(Boolean.parseBoolean(active));
}
}
}
Build and run:
CP=log4j-api-2.26.1.jar:log4j-core-2.26.1.jar
javac -proc:full -Alog4j.graalvm.groupId=demo -Alog4j.graalvm.artifactId=demo -cp $CP -d classes demo/*.java
cp log4j2.xml classes/
java -cp classes:$CP demo.Main
native-image --no-fallback -cp classes:$CP -o app demo.Main
./app
The generated metadata for the demo plugin has no entry for AttributeArbiter$Builder:
[{"name":"demo.AttributeArbiter","methods":[{"name":"newBuilder","parameterTypes":[]}],"fields":[]}]
Proposed fix
I have a patch ready and can open a PR against 2.x:
PluginBuilderonly uses fields that have an annotation meta-annotated with@PluginVisitorStrategyor@Constraint. Those are the only fields it injects or validates anyway, so the runtime matches what the processor registers and doesn't depend on how a GraalVM version treats unregistered fields.GraalVmProcessor(and itsAnnotationshelper) handle@PluginAttribute, so builder fields that use it get registered. Nothing changes for method parameters.
With the patch the reproducer prints the same output natively as on the JVM, on GraalVM CE 25.4.4.1.1. The PR adds a PluginBuilderTest for the field filter and a @PluginAttribute builder field to the GraalVmProcessorTest fixture.
The other way to fix the first part would be for GraalVmProcessor to register all declared fields of builder types. I went with the runtime change because it also covers base classes the processor never sees and keeps the metadata small, but I'm fine either way.
- 主要言語
- Java
- スター
- 3.6k
- フォーク
- 1.8k
- 平均マージ
- 5日 6時間
- マージ済み PR(30日)
- 24
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドなし
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
apache/logging-log4j2 のほかの issue
-
additivity attributeオープンwaiting-for-maintainer
難易度 2/5 1〜3時間 初心者へのやさしさ 63/100
apache/logging-log4j2#4475 ·
メンテナーはふだん 1 日以内に返信
-
Migrate `log4j-api-test` tests to JUnit 5再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープンapi priority: P3 tests waiting-for-maintainer
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
apache/logging-log4j2#4430 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
`Rfc5424Layout` does not escape the `newLineEscape` replacement in SD-PARAM-VALUE対応中かも @lunarbae628 が 5 日前に担当しました。 オープンbug layouts priority: P3
apache/logging-log4j2#4464 · コメント 2 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
configuration enhancement layouts
難易度 4/5 3〜5日 初心者へのやさしさ 55/100
apache/logging-log4j2#4463 ·
メンテナーはふだん 1 日以内に返信
-
`evaluateOnStartup="true"` saves logs under wrong file names after missing scheduled roll再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープンappenders:Rolling bug priority: P1 waiting-for-maintainer
難易度 3/5 1〜2日 初心者へのやさしさ 30/100
apache/logging-log4j2#4449 ·
メンテナーはふだん 1 日以内に返信
apache/logging-log4j2 の issue をすべて見る
似ている issue
-
untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
opensearch-project/opensearch-prometheus-exporter#680 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
メンテナーはふだん 2 日以内に返信
-
bug
難易度 1/5 1時間未満 初心者へのやさしさ 78/100
-
Good First Issue
難易度 1/5 1〜3時間 初心者へのやさしさ 88/100
hiero-ledger/hiero-sdk-java#3059 ·
メンテナーはふだん 1 日以内に返信
-
bug live-tv platform:desktop platform:mobile platform:tv
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
fk0u/cleanyourtv-tahoe#102 ·
メンテナーはふだん 1 日以内に返信