Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support optional: prefix with logging.log4j2.config.override #44488

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,5 @@ To configure Log4j 2 to use an alternative configuration file format, add the ap
Log4j 2 has support for combining multiple configuration files into a single composite configuration.
To use this support in Spring Boot, configure configprop:logging.log4j2.config.override[] with the locations of one or more secondary configuration files.
The secondary configuration files will be merged with the primary configuration, whether the primary's source is Spring Boot's defaults, a standard location such as `log4j.xml`, or the location configured by the configprop:logging.config[] property.

NOTE: Log4j2 override configuration file locations can be prefixed with `optional:`, for example, `optional:classpath:log4j2-override.xml`, to indicate that the location is optional and should only be loaded if the resource exists.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.springframework.boot.logging.log4j2;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
Expand Down Expand Up @@ -69,6 +70,7 @@
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
Expand All @@ -86,6 +88,8 @@
*/
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";

private static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager";
Expand Down Expand Up @@ -270,21 +274,42 @@ protected void loadConfiguration(String location, LogFile logFile, List<String>
try {
List<Configuration> configurations = new ArrayList<>();
LoggerContext context = getLoggerContext();
configurations.add(load(location, context));
ResourceLoader resourceLoader = ApplicationResourceLoader.get();
configurations.add(loadConfiguration(resourceLoader, location, context));
for (String override : overrides) {
configurations.add(load(override, context));
Configuration overrideConfiguration = loadOptionalConfiguration(resourceLoader, override, context);
if (overrideConfiguration != null) {
configurations.add(overrideConfiguration);
}
}
Configuration configuration = (configurations.size() > 1) ? createComposite(configurations)
: configurations.iterator().next();
context.start(configuration);
context.start(createCompositeConfigurationIfNecessary(configurations));
}
catch (Exception ex) {
throw new IllegalStateException("Could not initialize Log4J2 logging from " + location, ex);
}
}

private Configuration load(String location, LoggerContext context) throws IOException {
Resource resource = ApplicationResourceLoader.get().getResource(location);
private Configuration loadOptionalConfiguration(ResourceLoader resourceLoader, String location,
LoggerContext context) throws IOException {
if (location.startsWith(OPTIONAL_PREFIX)) {
Resource resource = resourceLoader.getResource(location.substring(OPTIONAL_PREFIX.length()));
try {
return (resource.exists()) ? loadConfiguration(resource, context) : null;
}
catch (FileNotFoundException ex) {
return null;
}
}
return loadConfiguration(resourceLoader, location, context);
}

private Configuration loadConfiguration(ResourceLoader resourceLoader, String location, LoggerContext context)
throws IOException {
Resource resource = resourceLoader.getResource(location);
return loadConfiguration(resource, context);
}

private Configuration loadConfiguration(Resource resource, LoggerContext context) throws IOException {
ConfigurationFactory factory = ConfigurationFactory.getInstance();
if (resource.isFile()) {
try (InputStream inputStream = resource.getInputStream()) {
Expand All @@ -304,7 +329,10 @@ private Configuration load(String location, LoggerContext context) throws IOExce
}
}

private CompositeConfiguration createComposite(List<Configuration> configurations) {
private Configuration createCompositeConfigurationIfNecessary(List<Configuration> configurations) {
if (configurations.size() == 1) {
return configurations.iterator().next();
}
return new CompositeConfiguration(configurations.stream().map(AbstractConfiguration.class::cast).toList());
}

Expand All @@ -322,19 +350,21 @@ protected void reinitialize(LoggingInitializationContext initializationContext)

private void reinitializeWithOverrides(List<String> overrides) {
LoggerContext context = getLoggerContext();
Configuration base = context.getConfiguration();
List<AbstractConfiguration> configurations = new ArrayList<>();
configurations.add((AbstractConfiguration) base);
List<Configuration> configurations = new ArrayList<>();
configurations.add(context.getConfiguration());
ResourceLoader resourceLoader = ApplicationResourceLoader.get();
for (String override : overrides) {
try {
configurations.add((AbstractConfiguration) load(override, context));
Configuration overrideConfiguration = loadOptionalConfiguration(resourceLoader, override, context);
if (overrideConfiguration != null) {
configurations.add(overrideConfiguration);
}
}
catch (IOException ex) {
throw new RuntimeException("Failed to load overriding configuration from '" + override + "'", ex);
}
}
CompositeConfiguration composite = new CompositeConfiguration(configurations);
context.reconfigure(composite);
context.reconfigure(createCompositeConfigurationIfNecessary(configurations));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.apache.logging.log4j.core.config.Reconfigurable;
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry;
import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
import org.apache.logging.log4j.jul.Log4jBridgeHandler;
import org.apache.logging.log4j.status.StatusListener;
Expand Down Expand Up @@ -453,6 +454,48 @@ void shutdownHookIsDisabled() {
.isFalse();
}

@Test
@WithNonDefaultXmlResource
void loadOptionalOverrideConfigurationWhenDoesNotExist() {
this.environment.setProperty("logging.log4j2.config.override", "optional:classpath:override.xml");
this.loggingSystem.initialize(this.initializationContext, "classpath:nondefault.xml", null);
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(XmlConfiguration.class);
}

@Test
void loadOptionalOverrideConfigurationWhenDoesNotExistUponReinitialization() {
this.environment.setProperty("logging.log4j2.config.override", "optional:classpath:override.xml");
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(this.initializationContext, null, null);
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(XmlConfiguration.class);
this.loggingSystem.cleanUp();
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(this.initializationContext, null, null);
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(XmlConfiguration.class);
}

@Test
@WithNonDefaultXmlResource
@WithOverrideXmlResource
void loadOptionalOverrideConfiguration() {
this.environment.setProperty("logging.log4j2.config.override", "optional:classpath:override.xml");
this.loggingSystem.initialize(this.initializationContext, "classpath:nondefault.xml", null);
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(CompositeConfiguration.class);
}

@Test
@WithOverrideXmlResource
void loadOptionalOverrideConfigurationUponReinitialization() {
this.environment.setProperty("logging.log4j2.config.override", "optional:classpath:override.xml");
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(this.initializationContext, null, null);
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(CompositeConfiguration.class);
this.loggingSystem.cleanUp();
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(this.initializationContext, null, null);
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(CompositeConfiguration.class);
}

@Test
@WithNonDefaultXmlResource
@WithOverrideXmlResource
Expand Down