Skip to content

Commit 4d67fbb

Browse files
committed
Merge pull request #44488 from nosan
* pr/44488: Polish "Add support for optional Log4J2 configuration" Add support for optional Log4J2 configuration Closes gh-44488
2 parents 93dc0fb + 0e347af commit 4d67fbb

File tree

4 files changed

+88
-15
lines changed

4 files changed

+88
-15
lines changed

Diff for: spring-boot-project/spring-boot-docs/src/docs/antora/modules/how-to/pages/logging.adoc

+6
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,9 @@ To configure Log4j 2 to use an alternative configuration file format, add the ap
201201
Log4j 2 has support for combining multiple configuration files into a single composite configuration.
202202
To use this support in Spring Boot, configure configprop:logging.log4j2.config.override[] with the locations of one or more secondary configuration files.
203203
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.
204+
205+
[NOTE]
206+
====
207+
Log4j2 override configuration file locations can be prefixed with `optional:`.
208+
For example, `optional:classpath:log4j2-override.xml` indicates that `log4j2-override.xml` should only be loaded if the resource exists.
209+
====

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java

+38-14
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.logging.log4j2;
1818

19+
import java.io.FileNotFoundException;
1920
import java.io.IOException;
2021
import java.io.InputStream;
2122
import java.net.URL;
@@ -68,6 +69,7 @@
6869
import org.springframework.core.annotation.Order;
6970
import org.springframework.core.env.Environment;
7071
import org.springframework.core.io.Resource;
72+
import org.springframework.core.io.ResourceLoader;
7173
import org.springframework.util.Assert;
7274
import org.springframework.util.ClassUtils;
7375
import org.springframework.util.CollectionUtils;
@@ -85,6 +87,8 @@
8587
*/
8688
public class Log4J2LoggingSystem extends AbstractLoggingSystem {
8789

90+
private static final String OPTIONAL_PREFIX = "optional:";
91+
8892
private static final String LOG4J_BRIDGE_HANDLER = "org.apache.logging.log4j.jul.Log4jBridgeHandler";
8993

9094
private static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager";
@@ -269,21 +273,22 @@ protected void loadConfiguration(String location, LogFile logFile, List<String>
269273
try {
270274
List<Configuration> configurations = new ArrayList<>();
271275
LoggerContext context = getLoggerContext();
272-
configurations.add(load(location, context));
276+
ResourceLoader resourceLoader = ApplicationResourceLoader.get();
277+
configurations.add(load(resourceLoader.getResource(location), context));
273278
for (String override : overrides) {
274-
configurations.add(load(override, context));
279+
Configuration overrideConfiguration = loadOverride(resourceLoader, override, context);
280+
if (overrideConfiguration != null) {
281+
configurations.add(overrideConfiguration);
282+
}
275283
}
276-
Configuration configuration = (configurations.size() > 1) ? createComposite(configurations)
277-
: configurations.iterator().next();
278-
context.start(configuration);
284+
context.start(mergeConfigurations(configurations));
279285
}
280286
catch (Exception ex) {
281287
throw new IllegalStateException("Could not initialize Log4J2 logging from " + location, ex);
282288
}
283289
}
284290

285-
private Configuration load(String location, LoggerContext context) throws IOException {
286-
Resource resource = ApplicationResourceLoader.get().getResource(location);
291+
private Configuration load(Resource resource, LoggerContext context) throws IOException {
287292
ConfigurationFactory factory = ConfigurationFactory.getInstance();
288293
if (resource.isFile()) {
289294
try (InputStream inputStream = resource.getInputStream()) {
@@ -303,7 +308,24 @@ private Configuration load(String location, LoggerContext context) throws IOExce
303308
}
304309
}
305310

306-
private CompositeConfiguration createComposite(List<Configuration> configurations) {
311+
private Configuration loadOverride(ResourceLoader resourceLoader, String location, LoggerContext context)
312+
throws IOException {
313+
if (location.startsWith(OPTIONAL_PREFIX)) {
314+
Resource resource = resourceLoader.getResource(location.substring(OPTIONAL_PREFIX.length()));
315+
try {
316+
return (resource.exists()) ? load(resource, context) : null;
317+
}
318+
catch (FileNotFoundException ex) {
319+
return null;
320+
}
321+
}
322+
return load(resourceLoader.getResource(location), context);
323+
}
324+
325+
private Configuration mergeConfigurations(List<Configuration> configurations) {
326+
if (configurations.size() == 1) {
327+
return configurations.iterator().next();
328+
}
307329
return new CompositeConfiguration(configurations.stream().map(AbstractConfiguration.class::cast).toList());
308330
}
309331

@@ -321,19 +343,21 @@ protected void reinitialize(LoggingInitializationContext initializationContext)
321343

322344
private void reinitializeWithOverrides(List<String> overrides) {
323345
LoggerContext context = getLoggerContext();
324-
Configuration base = context.getConfiguration();
325-
List<AbstractConfiguration> configurations = new ArrayList<>();
326-
configurations.add((AbstractConfiguration) base);
346+
List<Configuration> configurations = new ArrayList<>();
347+
configurations.add(context.getConfiguration());
348+
ResourceLoader resourceLoader = ApplicationResourceLoader.get();
327349
for (String override : overrides) {
328350
try {
329-
configurations.add((AbstractConfiguration) load(override, context));
351+
Configuration overrideConfiguration = loadOverride(resourceLoader, override, context);
352+
if (overrideConfiguration != null) {
353+
configurations.add(overrideConfiguration);
354+
}
330355
}
331356
catch (IOException ex) {
332357
throw new RuntimeException("Failed to load overriding configuration from '" + override + "'", ex);
333358
}
334359
}
335-
CompositeConfiguration composite = new CompositeConfiguration(configurations);
336-
context.reconfigure(composite);
360+
context.reconfigure(mergeConfigurations(configurations));
337361
}
338362

339363
@Override

Diff for: spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126
{
127127
"name": "logging.log4j2.config.override",
128128
"type": "java.util.List<java.lang.String>",
129-
"description": "Overriding configuration files used to create a composite configuration."
129+
"description": "Overriding configuration files used to create a composite configuration. Can be prefixed with 'optional:' to only load the override if it exists."
130130
},
131131
{
132132
"name": "logging.logback.rollingpolicy.clean-history-on-start",

Diff for: spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java

+43
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.apache.logging.log4j.core.config.Reconfigurable;
4444
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
4545
import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry;
46+
import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
4647
import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
4748
import org.apache.logging.log4j.jul.Log4jBridgeHandler;
4849
import org.apache.logging.log4j.status.StatusListener;
@@ -453,6 +454,48 @@ void shutdownHookIsDisabled() {
453454
.isFalse();
454455
}
455456

457+
@Test
458+
@WithNonDefaultXmlResource
459+
void loadOptionalOverrideConfigurationWhenDoesNotExist() {
460+
this.environment.setProperty("logging.log4j2.config.override", "optional:classpath:override.xml");
461+
this.loggingSystem.initialize(this.initializationContext, "classpath:nondefault.xml", null);
462+
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(XmlConfiguration.class);
463+
}
464+
465+
@Test
466+
void loadOptionalOverrideConfigurationWhenDoesNotExistUponReinitialization() {
467+
this.environment.setProperty("logging.log4j2.config.override", "optional:classpath:override.xml");
468+
this.loggingSystem.beforeInitialize();
469+
this.loggingSystem.initialize(this.initializationContext, null, null);
470+
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(XmlConfiguration.class);
471+
this.loggingSystem.cleanUp();
472+
this.loggingSystem.beforeInitialize();
473+
this.loggingSystem.initialize(this.initializationContext, null, null);
474+
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(XmlConfiguration.class);
475+
}
476+
477+
@Test
478+
@WithNonDefaultXmlResource
479+
@WithOverrideXmlResource
480+
void loadOptionalOverrideConfiguration() {
481+
this.environment.setProperty("logging.log4j2.config.override", "optional:classpath:override.xml");
482+
this.loggingSystem.initialize(this.initializationContext, "classpath:nondefault.xml", null);
483+
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(CompositeConfiguration.class);
484+
}
485+
486+
@Test
487+
@WithOverrideXmlResource
488+
void loadOptionalOverrideConfigurationUponReinitialization() {
489+
this.environment.setProperty("logging.log4j2.config.override", "optional:classpath:override.xml");
490+
this.loggingSystem.beforeInitialize();
491+
this.loggingSystem.initialize(this.initializationContext, null, null);
492+
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(CompositeConfiguration.class);
493+
this.loggingSystem.cleanUp();
494+
this.loggingSystem.beforeInitialize();
495+
this.loggingSystem.initialize(this.initializationContext, null, null);
496+
assertThat(this.loggingSystem.getConfiguration()).isInstanceOf(CompositeConfiguration.class);
497+
}
498+
456499
@Test
457500
@WithNonDefaultXmlResource
458501
@WithOverrideXmlResource

0 commit comments

Comments
 (0)