Skip to content

Commit 89b40e1

Browse files
committed
Add support for Log4j2's composite configuration
Closes gh-27110
1 parent d6aab2f commit 89b40e1

File tree

6 files changed

+148
-31
lines changed

6 files changed

+148
-31
lines changed

spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/logging.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,11 @@ To configure Log4j 2 to use an alternative configuration file format, add the ap
185185
| `com.fasterxml.jackson.core:jackson-databind`
186186
| `log4j2.json` + `log4j2.jsn`
187187
|===
188+
189+
190+
191+
[[howto.logging.log4j.composite-configuration]]
192+
==== Use Composite Configuration to Configure Log4j 2
193+
Log4j 2 has support for combining multiple configuration files into a single composite configuration.
194+
To use this support in Spring Boot, configure configprop:logging.log4j2.config.override[] with the locations of one or more secondary configuration files.
195+
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.

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

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import java.util.Set;
28+
import java.util.stream.Collectors;
2829

2930
import org.apache.logging.log4j.Level;
3031
import org.apache.logging.log4j.LogManager;
@@ -38,10 +39,14 @@
3839
import org.apache.logging.log4j.core.config.ConfigurationFactory;
3940
import org.apache.logging.log4j.core.config.ConfigurationSource;
4041
import org.apache.logging.log4j.core.config.LoggerConfig;
42+
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
4143
import org.apache.logging.log4j.core.filter.AbstractFilter;
4244
import org.apache.logging.log4j.core.util.NameUtil;
4345
import org.apache.logging.log4j.message.Message;
4446

47+
import org.springframework.boot.context.properties.bind.BindResult;
48+
import org.springframework.boot.context.properties.bind.Bindable;
49+
import org.springframework.boot.context.properties.bind.Binder;
4550
import org.springframework.boot.logging.LogFile;
4651
import org.springframework.boot.logging.LogLevel;
4752
import org.springframework.boot.logging.LoggerConfiguration;
@@ -53,6 +58,7 @@
5358
import org.springframework.core.annotation.Order;
5459
import org.springframework.util.Assert;
5560
import org.springframework.util.ClassUtils;
61+
import org.springframework.util.CollectionUtils;
5662
import org.springframework.util.ResourceUtils;
5763
import org.springframework.util.StringUtils;
5864

@@ -167,33 +173,70 @@ public void initialize(LoggingInitializationContext initializationContext, Strin
167173
@Override
168174
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
169175
if (logFile != null) {
170-
loadConfiguration(getPackagedConfigFile("log4j2-file.xml"), logFile);
176+
loadConfiguration(getPackagedConfigFile("log4j2-file.xml"), logFile, getOverrides(initializationContext));
171177
}
172178
else {
173-
loadConfiguration(getPackagedConfigFile("log4j2.xml"), logFile);
179+
loadConfiguration(getPackagedConfigFile("log4j2.xml"), logFile, getOverrides(initializationContext));
174180
}
175181
}
176182

183+
private List<String> getOverrides(LoggingInitializationContext initializationContext) {
184+
BindResult<List<String>> overrides = Binder.get(initializationContext.getEnvironment())
185+
.bind("logging.log4j2.config.override", Bindable.listOf(String.class));
186+
return overrides.orElse(Collections.emptyList());
187+
}
188+
177189
@Override
178190
protected void loadConfiguration(LoggingInitializationContext initializationContext, String location,
179191
LogFile logFile) {
180192
super.loadConfiguration(initializationContext, location, logFile);
181-
loadConfiguration(location, logFile);
193+
loadConfiguration(location, logFile, getOverrides(initializationContext));
182194
}
183195

196+
/**
197+
* Load the configuration from the given {@code location}.
198+
* @param location the location
199+
* @param logFile log file configuration
200+
* @deprecated since 2.6.0 for removal in 2.8.0 in favor of
201+
* {@link #loadConfiguration(String, LogFile, List)}
202+
*/
203+
@Deprecated
184204
protected void loadConfiguration(String location, LogFile logFile) {
205+
this.loadConfiguration(location, logFile, Collections.emptyList());
206+
}
207+
208+
/**
209+
* Load the configuration from the given {@code location}, creating a composite using
210+
* the configuration from the given {@code overrides}.
211+
* @param location the location
212+
* @param logFile log file configuration
213+
* @param overrides the overriding locations
214+
* @since 2.6.0
215+
*/
216+
protected void loadConfiguration(String location, LogFile logFile, List<String> overrides) {
185217
Assert.notNull(location, "Location must not be null");
186218
try {
187-
LoggerContext ctx = getLoggerContext();
188-
URL url = ResourceUtils.getURL(location);
189-
ConfigurationSource source = getConfigurationSource(url);
190-
ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source));
219+
List<Configuration> configurations = new ArrayList<>();
220+
LoggerContext context = getLoggerContext();
221+
configurations.add(load(location, context));
222+
for (String override : overrides) {
223+
configurations.add(load(override, context));
224+
}
225+
Configuration configuration = (configurations.size() > 1) ? createComposite(configurations)
226+
: configurations.iterator().next();
227+
context.start(configuration);
191228
}
192229
catch (Exception ex) {
193230
throw new IllegalStateException("Could not initialize Log4J2 logging from " + location, ex);
194231
}
195232
}
196233

234+
private Configuration load(String location, LoggerContext context) throws IOException {
235+
URL url = ResourceUtils.getURL(location);
236+
ConfigurationSource source = getConfigurationSource(url);
237+
return ConfigurationFactory.getInstance().getConfiguration(context, source);
238+
}
239+
197240
private ConfigurationSource getConfigurationSource(URL url) throws IOException {
198241
InputStream stream = url.openStream();
199242
if (FILE_PROTOCOL.equals(url.getProtocol())) {
@@ -202,9 +245,38 @@ private ConfigurationSource getConfigurationSource(URL url) throws IOException {
202245
return new ConfigurationSource(stream, url);
203246
}
204247

248+
private CompositeConfiguration createComposite(List<Configuration> configurations) {
249+
return new CompositeConfiguration(
250+
configurations.stream().map(AbstractConfiguration.class::cast).collect(Collectors.toList()));
251+
}
252+
205253
@Override
206254
protected void reinitialize(LoggingInitializationContext initializationContext) {
207-
getLoggerContext().reconfigure();
255+
List<String> overrides = getOverrides(initializationContext);
256+
if (!CollectionUtils.isEmpty(overrides)) {
257+
reinitializeWithOverrides(overrides);
258+
}
259+
else {
260+
LoggerContext context = getLoggerContext();
261+
context.reconfigure();
262+
}
263+
}
264+
265+
private void reinitializeWithOverrides(List<String> overrides) {
266+
LoggerContext context = getLoggerContext();
267+
Configuration base = context.getConfiguration();
268+
List<AbstractConfiguration> configurations = new ArrayList<>();
269+
configurations.add((AbstractConfiguration) base);
270+
for (String override : overrides) {
271+
try {
272+
configurations.add((AbstractConfiguration) load(override, context));
273+
}
274+
catch (IOException ex) {
275+
throw new RuntimeException("Failed to load overriding configuration from '" + override + "'", ex);
276+
}
277+
}
278+
CompositeConfiguration composite = new CompositeConfiguration(configurations);
279+
context.reconfigure(composite);
208280
}
209281

210282
@Override

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@
198198
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
199199
"defaultValue": 7
200200
},
201+
{
202+
"name": "logging.log4j2.config.override",
203+
"type": "java.util.List<java.lang.String>",
204+
"description": "Overriding configuration files used to create a composite configuration."
205+
},
201206
{
202207
"name": "spring.application.index",
203208
"type": "java.lang.Integer",

0 commit comments

Comments
 (0)