Browse Source

Add runtime hints for Log4j Core 2

This change introduces runtime hints for Log4j Core 2 to support its
integration with Spring Boot native images.

Starting with Log4j 2.25.x, Log4j 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.

See gh-46410

Signed-off-by: Piotr P. Karwasz <piotr@github.copernik.eu>
pull/47436/head
Piotr P. Karwasz 5 months ago committed by Stéphane Nicoll
parent
commit
a2d4ecf8ac
  1. 3
      core/spring-boot/build.gradle
  2. 30
      core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java
  3. 67
      core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHints.java
  4. 1
      core/spring-boot/src/main/resources/META-INF/spring/aot.factories
  5. 73
      core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHintsTests.java

3
core/spring-boot/build.gradle

@ -71,6 +71,9 @@ dependencies { @@ -71,6 +71,9 @@ dependencies {
testImplementation("org.hibernate.validator:hibernate-validator")
testImplementation("org.jboss.logging:jboss-logging")
testImplementation("org.springframework.data:spring-data-r2dbc")
// Used in Log4J2RuntimeHintsTests
testRuntimeOnly("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
}
def syncJavaTemplates = tasks.register("syncJavaTemplates", Sync) {

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

@ -90,44 +90,50 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem { @@ -90,44 +90,50 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem {
private static final String OPTIONAL_PREFIX = "optional:";
private static final String LOG4J_BRIDGE_HANDLER = "org.apache.logging.log4j.jul.Log4jBridgeHandler";
/**
* JUL handler that routes messages to the Log4j API (optional dependency).
*/
static final String LOG4J_BRIDGE_HANDLER = "org.apache.logging.log4j.jul.Log4jBridgeHandler";
private static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager";
/**
* JUL LogManager that routes messages to the Log4j API as the backend.
*/
static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager";
/**
* JSON tree parser used by Log4j 2 (optional dependency).
*/
private static final String JSON_TREE_PARSER_V2 = "com.fasterxml.jackson.databind.ObjectMapper";
static final String JSON_TREE_PARSER_V2 = "com.fasterxml.jackson.databind.ObjectMapper";
/**
* JSON tree parser embedded in Log4j 3.
*/
private static final String JSON_TREE_PARSER_V3 = "org.apache.logging.log4j.kit.json.JsonReader";
static final String JSON_TREE_PARSER_V3 = "org.apache.logging.log4j.kit.json.JsonReader";
/**
* Configuration factory for properties files (Log4j 2).
*/
private static final String PROPS_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory";
static final String PROPS_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory";
/**
* Configuration factory for properties files (Log4j 3, optional dependency).
*/
private static final String PROPS_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.properties.JavaPropsConfigurationFactory";
static final String PROPS_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.properties.JavaPropsConfigurationFactory";
/**
* YAML tree parser used by Log4j 2 (optional dependency).
*/
private static final String YAML_TREE_PARSER_V2 = "com.fasterxml.jackson.dataformat.yaml.YAMLMapper";
static final String YAML_TREE_PARSER_V2 = "com.fasterxml.jackson.dataformat.yaml.YAMLMapper";
/**
* Configuration factory for YAML files (Log4j 2, embedded).
*/
private static final String YAML_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory";
static final String YAML_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory";
/**
* Configuration factory for YAML files (Log4j 3, optional dependency).
*/
private static final String YAML_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.yaml.YamlConfigurationFactory";
static final String YAML_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.yaml.YamlConfigurationFactory";
private static final SpringEnvironmentPropertySource propertySource = new SpringEnvironmentPropertySource();
@ -616,8 +622,10 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem { @@ -616,8 +622,10 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem {
@Order(0)
public static class Factory implements LoggingSystemFactory {
private static final boolean PRESENT = ClassUtils
.isPresent("org.apache.logging.log4j.core.impl.Log4jContextFactory", Factory.class.getClassLoader());
static final String LOG4J_CORE_CONTEXT_FACTORY = "org.apache.logging.log4j.core.impl.Log4jContextFactory";
private static final boolean PRESENT = ClassUtils.isPresent(LOG4J_CORE_CONTEXT_FACTORY,
Factory.class.getClassLoader());
@Override
public @Nullable LoggingSystem getLoggingSystem(ClassLoader classLoader) {

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

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
/*
* Copyright 2025-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.logging.log4j2;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.util.ClassUtils;
/**
* {@link RuntimeHintsRegistrar} implementation for {@link Log4J2LoggingSystem}.
*
* @author Piotr P. Karwasz
*/
class Log4J2RuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
if (!ClassUtils.isPresent(Log4J2LoggingSystem.Factory.LOG4J_CORE_CONTEXT_FACTORY, classLoader)) {
return;
}
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.Factory.LOG4J_CORE_CONTEXT_FACTORY);
// Register default Log4j2 configuration files
hints.resources().registerPattern("org/springframework/boot/logging/log4j2/log4j2.xml");
hints.resources().registerPattern("org/springframework/boot/logging/log4j2/log4j2-file.xml");
hints.resources().registerPattern("log4j2.springboot");
// Declares the types that Log4j2LoggingSystem checks for existence reflectively.
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V2);
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V3);
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V2);
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V3);
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_TREE_PARSER_V2);
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V2);
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V3);
// Register JUL to Log4j 2 bridge handler
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.LOG4J_BRIDGE_HANDLER);
registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.LOG4J_LOG_MANAGER);
// Don't need to register the custom Log4j 2 plugins,
// since they will be registered by the Log4j 2 `GraalvmPluginProcessor`.
}
/**
* Registers the type to prevent GraalVM from removing it during the native build.
* @param hints the runtime hints to register with
* @param classLoader the class loader to use for type resolution
* @param typeName the name of the type to register
*/
private void registerTypeForReachability(RuntimeHints hints, @Nullable ClassLoader classLoader, String typeName) {
hints.reflection().registerTypeIfPresent(classLoader, typeName);
}
}

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

@ -6,6 +6,7 @@ org.springframework.boot.context.config.ConfigDataLocationRuntimeHints,\ @@ -6,6 +6,7 @@ org.springframework.boot.context.config.ConfigDataLocationRuntimeHints,\
org.springframework.boot.context.config.ConfigDataPropertiesRuntimeHints,\
org.springframework.boot.env.PropertySourceRuntimeHints,\
org.springframework.boot.logging.java.JavaLoggingSystemRuntimeHints,\
org.springframework.boot.logging.log4j2.Log4J2RuntimeHints,\
org.springframework.boot.logging.logback.LogbackRuntimeHints,\
org.springframework.boot.logging.structured.ElasticCommonSchemaProperties$ElasticCommonSchemaPropertiesRuntimeHints,\
org.springframework.boot.logging.structured.GraylogExtendedLogFormatProperties$GraylogExtendedLogFormatPropertiesRuntimeHints,\

73
core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHintsTests.java

@ -0,0 +1,73 @@ @@ -0,0 +1,73 @@
/*
* Copyright 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.logging.log4j2;
import org.apache.logging.log4j.core.impl.Log4jContextFactory;
import org.apache.logging.log4j.jul.Log4jBridgeHandler;
import org.apache.logging.log4j.jul.LogManager;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Log4J2RuntimeHints}.
*
* @author Piotr P. Karwasz
*/
class Log4J2RuntimeHintsTests {
@Test
void registersHintsForTypesCheckedByLog4J2LoggingSystem() {
ReflectionHints reflection = registerHints();
// Once Log4j Core is reachable, GraalVM will automatically
// add reachability metadata embedded in the Log4j Core jar and extensions.
assertThat(reflection.getTypeHint(Log4jContextFactory.class)).isNotNull();
assertThat(reflection.getTypeHint(Log4jBridgeHandler.class)).isNotNull();
assertThat(reflection.getTypeHint(LogManager.class)).isNotNull();
}
/**
*
*/
@Test
void registersHintsForConfigurationFileParsers() {
ReflectionHints reflection = registerHints();
// JSON
assertThat(reflection.getTypeHint(TypeReference.of("com.fasterxml.jackson.databind.ObjectMapper"))).isNotNull();
// YAML
assertThat(reflection.getTypeHint(TypeReference.of("com.fasterxml.jackson.dataformat.yaml.YAMLMapper")))
.isNotNull();
}
@Test
void doesNotRegisterHintsWhenLog4jCoreIsNotAvailable() {
RuntimeHints hints = new RuntimeHints();
new Log4J2RuntimeHints().registerHints(hints, ClassLoader.getPlatformClassLoader());
assertThat(hints.reflection().typeHints()).isEmpty();
}
private ReflectionHints registerHints() {
RuntimeHints hints = new RuntimeHints();
new Log4J2RuntimeHints().registerHints(hints, getClass().getClassLoader());
return hints.reflection();
}
}
Loading…
Cancel
Save