Skip to content

Commit 06fc66c

Browse files
committed
Add AOT runtime hints for Log4j Core 2
This change introduces AOT runtime hints for Log4j Core 2 to support its integration with Spring Boot native images. Starting with version `2.25.0`, Log4j Core 2 includes built-in GraalVM reachability metadata, allowing native image generation without requiring additional manual configuration. This contribution complements that by adding Spring Boot–specific metadata: * Registers default Spring Boot configuration files. * Registers classes referenced via `ClassUtils.isPresent(...)` checks. Fixes #42273. > [!NOTE] > This change should be reviewed in conjunction with #46334, which configures Log4j Core’s `GraalVmProcessor` to generate reachability metadata for Spring Boot’s custom Log4j plugins. Signed-off-by: Piotr P. Karwasz <piotr@github.copernik.eu>
1 parent 4420c92 commit 06fc66c

File tree

5 files changed

+163
-11
lines changed

5 files changed

+163
-11
lines changed

core/spring-boot/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ dependencies {
7171
testImplementation("org.hibernate.validator:hibernate-validator")
7272
testImplementation("org.jboss.logging:jboss-logging")
7373
testImplementation("org.springframework.data:spring-data-r2dbc")
74+
75+
// Used in Log4J2RuntimeHintsTests
76+
testRuntimeOnly("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
7477
}
7578

7679
def syncJavaTemplates = tasks.register("syncJavaTemplates", Sync) {

core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -90,44 +90,50 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem {
9090

9191
private static final String OPTIONAL_PREFIX = "optional:";
9292

93-
private static final String LOG4J_BRIDGE_HANDLER = "org.apache.logging.log4j.jul.Log4jBridgeHandler";
93+
/**
94+
* JUL handler that routes messages to the Log4j API (optional dependency).
95+
*/
96+
static final String LOG4J_BRIDGE_HANDLER = "org.apache.logging.log4j.jul.Log4jBridgeHandler";
9497

95-
private static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager";
98+
/**
99+
* JUL LogManager that routes messages to the Log4j API as the backend.
100+
*/
101+
static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager";
96102

97103
/**
98104
* JSON tree parser used by Log4j 2 (optional dependency).
99105
*/
100-
private static final String JSON_TREE_PARSER_V2 = "com.fasterxml.jackson.databind.ObjectMapper";
106+
static final String JSON_TREE_PARSER_V2 = "com.fasterxml.jackson.databind.ObjectMapper";
101107

102108
/**
103109
* JSON tree parser embedded in Log4j 3.
104110
*/
105-
private static final String JSON_TREE_PARSER_V3 = "org.apache.logging.log4j.kit.json.JsonReader";
111+
static final String JSON_TREE_PARSER_V3 = "org.apache.logging.log4j.kit.json.JsonReader";
106112

107113
/**
108114
* Configuration factory for properties files (Log4j 2).
109115
*/
110-
private static final String PROPS_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory";
116+
static final String PROPS_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory";
111117

112118
/**
113119
* Configuration factory for properties files (Log4j 3, optional dependency).
114120
*/
115-
private static final String PROPS_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.properties.JavaPropsConfigurationFactory";
121+
static final String PROPS_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.properties.JavaPropsConfigurationFactory";
116122

117123
/**
118124
* YAML tree parser used by Log4j 2 (optional dependency).
119125
*/
120-
private static final String YAML_TREE_PARSER_V2 = "com.fasterxml.jackson.dataformat.yaml.YAMLMapper";
126+
static final String YAML_TREE_PARSER_V2 = "com.fasterxml.jackson.dataformat.yaml.YAMLMapper";
121127

122128
/**
123129
* Configuration factory for YAML files (Log4j 2, embedded).
124130
*/
125-
private static final String YAML_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory";
131+
static final String YAML_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory";
126132

127133
/**
128134
* Configuration factory for YAML files (Log4j 3, optional dependency).
129135
*/
130-
private static final String YAML_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.yaml.YamlConfigurationFactory";
136+
static final String YAML_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.yaml.YamlConfigurationFactory";
131137

132138
private static final SpringEnvironmentPropertySource propertySource = new SpringEnvironmentPropertySource();
133139

@@ -616,8 +622,10 @@ protected String getDefaultLogCorrelationPattern() {
616622
@Order(0)
617623
public static class Factory implements LoggingSystemFactory {
618624

619-
private static final boolean PRESENT = ClassUtils
620-
.isPresent("org.apache.logging.log4j.core.impl.Log4jContextFactory", Factory.class.getClassLoader());
625+
static final String LOG4J_CORE_CONTEXT_FACTORY = "org.apache.logging.log4j.core.impl.Log4jContextFactory";
626+
627+
private static final boolean PRESENT = ClassUtils.isPresent(LOG4J_CORE_CONTEXT_FACTORY,
628+
Factory.class.getClassLoader());
621629

622630
@Override
623631
public @Nullable LoggingSystem getLoggingSystem(ClassLoader classLoader) {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.logging.log4j2;
18+
19+
import org.jspecify.annotations.Nullable;
20+
21+
import org.springframework.aot.hint.RuntimeHints;
22+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
23+
import org.springframework.util.ClassUtils;
24+
25+
/**
26+
* {@link RuntimeHintsRegistrar} implementation for {@link Log4J2LoggingSystem}.
27+
*
28+
* @author Piotr P. Karwasz
29+
*/
30+
class Log4J2RuntimeHints implements RuntimeHintsRegistrar {
31+
32+
@Override
33+
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
34+
if (!ClassUtils.isPresent(Log4J2LoggingSystem.Factory.LOG4J_CORE_CONTEXT_FACTORY, classLoader)) {
35+
return;
36+
}
37+
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.Factory.LOG4J_CORE_CONTEXT_FACTORY);
38+
// Register default Log4j2 configuration files
39+
hints.resources().registerPattern("org/springframework/boot/logging/log4j2/log4j2.xml");
40+
hints.resources().registerPattern("org/springframework/boot/logging/log4j2/log4j2-file.xml");
41+
hints.resources().registerPattern("log4j2.springboot");
42+
// Declares the types that Log4j2LoggingSystem checks for existence reflectively.
43+
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V2);
44+
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V3);
45+
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V2);
46+
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V3);
47+
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_TREE_PARSER_V2);
48+
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V2);
49+
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V3);
50+
// Register JUL to Log4j 2 bridge handler
51+
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.LOG4J_BRIDGE_HANDLER);
52+
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.LOG4J_LOG_MANAGER);
53+
// Don't need to register the custom Log4j 2 plugins,
54+
// since they will be registered by the Log4j 2 `GraalvmPluginProcessor`.
55+
}
56+
57+
/**
58+
* Registers the type to prevent GraalVM from removing it during the native build.
59+
* @param hints the runtime hints to register with
60+
* @param classLoader the class loader to use for type resolution
61+
* @param typeName the name of the type to register
62+
*/
63+
private void registerTypeForReachability(RuntimeHints hints, @Nullable ClassLoader classLoader, String typeName) {
64+
hints.reflection().registerTypeIfPresent(classLoader, typeName);
65+
}
66+
67+
}

core/spring-boot/src/main/resources/META-INF/spring/aot.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ org.springframework.boot.context.config.ConfigDataLocationRuntimeHints,\
66
org.springframework.boot.context.config.ConfigDataPropertiesRuntimeHints,\
77
org.springframework.boot.env.PropertySourceRuntimeHints,\
88
org.springframework.boot.logging.java.JavaLoggingSystemRuntimeHints,\
9+
org.springframework.boot.logging.log4j2.Log4J2RuntimeHints,\
910
org.springframework.boot.logging.logback.LogbackRuntimeHints,\
1011
org.springframework.boot.logging.structured.ElasticCommonSchemaProperties$ElasticCommonSchemaPropertiesRuntimeHints,\
1112
org.springframework.boot.logging.structured.GraylogExtendedLogFormatProperties$GraylogExtendedLogFormatPropertiesRuntimeHints,\
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.logging.log4j2;
18+
19+
import org.apache.logging.log4j.core.impl.Log4jContextFactory;
20+
import org.apache.logging.log4j.jul.Log4jBridgeHandler;
21+
import org.apache.logging.log4j.jul.LogManager;
22+
import org.junit.jupiter.api.Test;
23+
24+
import org.springframework.aot.hint.ReflectionHints;
25+
import org.springframework.aot.hint.RuntimeHints;
26+
import org.springframework.aot.hint.TypeReference;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
/**
31+
* Tests for {@link Log4J2RuntimeHints}.
32+
*
33+
* @author Piotr P. Karwasz
34+
*/
35+
class Log4J2RuntimeHintsTests {
36+
37+
@Test
38+
void registersHintsForTypesCheckedByLog4J2LoggingSystem() {
39+
ReflectionHints reflection = registerHints();
40+
// Once Log4j Core is reachable, GraalVM will automatically
41+
// add reachability metadata embedded in the Log4j Core jar and extensions.
42+
assertThat(reflection.getTypeHint(Log4jContextFactory.class)).isNotNull();
43+
assertThat(reflection.getTypeHint(Log4jBridgeHandler.class)).isNotNull();
44+
assertThat(reflection.getTypeHint(LogManager.class)).isNotNull();
45+
}
46+
47+
/**
48+
*
49+
*/
50+
@Test
51+
void registersHintsForConfigurationFileParsers() {
52+
ReflectionHints reflection = registerHints();
53+
// JSON
54+
assertThat(reflection.getTypeHint(TypeReference.of("com.fasterxml.jackson.databind.ObjectMapper"))).isNotNull();
55+
// YAML
56+
assertThat(reflection.getTypeHint(TypeReference.of("com.fasterxml.jackson.dataformat.yaml.YAMLMapper")))
57+
.isNotNull();
58+
}
59+
60+
@Test
61+
void doesNotRegisterHintsWhenLog4jCoreIsNotAvailable() {
62+
RuntimeHints hints = new RuntimeHints();
63+
new Log4J2RuntimeHints().registerHints(hints, ClassLoader.getPlatformClassLoader());
64+
assertThat(hints.reflection().typeHints()).isEmpty();
65+
}
66+
67+
private ReflectionHints registerHints() {
68+
RuntimeHints hints = new RuntimeHints();
69+
new Log4J2RuntimeHints().registerHints(hints, getClass().getClassLoader());
70+
return hints.reflection();
71+
}
72+
73+
}

0 commit comments

Comments
 (0)