Skip to content

Commit cf2dcfb

Browse files
committed
Simplify Log4J2LoggingSystem
This change leverages the major Spring Boot version bump to streamline and harden `Log4J2LoggingSystem` in two key areas: ### 1. Association with `LoggerContext` Previously, each method fetched the `LoggerContext` directly from `LogManager` and cast it to `o.a.l.l.core.LoggerContext`. This approach introduced several issues: * **`ClassCastException` risks**: * When Log4j Core is on the classpath but not the active implementation (e.g. when `log4j-to-slf4j` is used). * During shutdown, when `LogManager` may return a `SimpleLoggerContext` (see #26953). * **Unexpected reinitialization**: If the logger context had already been stopped, `Log4J2LoggingSystem` would trigger creation of a **new** context, even mid-shutdown. ### 2. Configuration format detection Configuration file detection was previously hardcoded in `Log4J2LoggingSystem`, which limited flexibility: * Harder to support additional configuration formats. * Coupled Spring Boot to internal Log4j Core classes such as `AuthorizationProvider`. This change now delegates configuration resolution to Log4j Core via: `ConfigurationFactory.getConfiguration(LoggerContext, String, URI, ClassLoader)` This reduces reliance on internal APIs and allows Log4j Core to handle configuration formats and factories more naturally. ### Summary * Avoids fragile casts and unintended logger context reinitializations. * Delegates configuration handling to Log4j Core for improved extensibility and reduced internal API usage. Signed-off-by: Piotr P. Karwasz <[email protected]>
1 parent 7613024 commit cf2dcfb

File tree

1 file changed

+57
-170
lines changed

1 file changed

+57
-170
lines changed

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

Lines changed: 57 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818

1919
import java.io.FileNotFoundException;
2020
import java.io.IOException;
21-
import java.io.InputStream;
22-
import java.net.URL;
23-
import java.net.URLConnection;
2421
import java.util.ArrayList;
2522
import java.util.Collections;
2623
import java.util.LinkedHashMap;
@@ -38,16 +35,12 @@
3835
import org.apache.logging.log4j.core.config.AbstractConfiguration;
3936
import org.apache.logging.log4j.core.config.Configuration;
4037
import org.apache.logging.log4j.core.config.ConfigurationFactory;
41-
import org.apache.logging.log4j.core.config.ConfigurationSource;
4238
import org.apache.logging.log4j.core.config.LoggerConfig;
4339
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
4440
import org.apache.logging.log4j.core.filter.DenyAllFilter;
45-
import org.apache.logging.log4j.core.net.UrlConnectionFactory;
46-
import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
47-
import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
48-
import org.apache.logging.log4j.core.util.AuthorizationProvider;
4941
import org.apache.logging.log4j.core.util.NameUtil;
5042
import org.apache.logging.log4j.jul.Log4jBridgeHandler;
43+
import org.apache.logging.log4j.spi.LoggerContextFactory;
5144
import org.apache.logging.log4j.status.StatusConsoleListener;
5245
import org.apache.logging.log4j.status.StatusLogger;
5346
import org.apache.logging.log4j.util.PropertiesUtil;
@@ -72,7 +65,6 @@
7265
import org.springframework.core.io.ResourceLoader;
7366
import org.springframework.util.Assert;
7467
import org.springframework.util.ClassUtils;
75-
import org.springframework.util.CollectionUtils;
7668
import org.springframework.util.StringUtils;
7769

7870
/**
@@ -94,41 +86,6 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem {
9486

9587
private static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager";
9688

97-
/**
98-
* JSON tree parser used by Log4j 2 (optional dependency).
99-
*/
100-
private static final String JSON_TREE_PARSER_V2 = "com.fasterxml.jackson.databind.ObjectMapper";
101-
102-
/**
103-
* JSON tree parser embedded in Log4j 3.
104-
*/
105-
private static final String JSON_TREE_PARSER_V3 = "org.apache.logging.log4j.kit.json.JsonReader";
106-
107-
/**
108-
* Configuration factory for properties files (Log4j 2).
109-
*/
110-
private static final String PROPS_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory";
111-
112-
/**
113-
* Configuration factory for properties files (Log4j 3, optional dependency).
114-
*/
115-
private static final String PROPS_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.properties.JavaPropsConfigurationFactory";
116-
117-
/**
118-
* YAML tree parser used by Log4j 2 (optional dependency).
119-
*/
120-
private static final String YAML_TREE_PARSER_V2 = "com.fasterxml.jackson.dataformat.yaml.YAMLMapper";
121-
122-
/**
123-
* Configuration factory for YAML files (Log4j 2, embedded).
124-
*/
125-
private static final String YAML_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory";
126-
127-
/**
128-
* Configuration factory for YAML files (Log4j 3, optional dependency).
129-
*/
130-
private static final String YAML_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.yaml.YamlConfigurationFactory";
131-
13289
private static final SpringEnvironmentPropertySource propertySource = new SpringEnvironmentPropertySource();
13390

13491
static final String ENVIRONMENT_KEY = Conventions.getQualifiedAttributeName(Log4J2LoggingSystem.class,
@@ -151,73 +108,43 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem {
151108

152109
private static final Filter FILTER = DenyAllFilter.newBuilder().build();
153110

154-
public Log4J2LoggingSystem(ClassLoader classLoader) {
111+
private final LoggerContext loggerContext;
112+
113+
private Log4J2LoggingSystem(ClassLoader classLoader, org.apache.logging.log4j.spi.LoggerContext loggerContext) {
155114
super(classLoader);
115+
this.loggerContext = (LoggerContext) loggerContext;
156116
}
157117

118+
/**
119+
* {@inheritDoc}
120+
* @deprecated Since 4.0.0, in favor of the {@link ConfigurationFactory} SPI.
121+
*/
158122
@Override
123+
@Deprecated(since = "4.0.0", forRemoval = true)
159124
protected String[] getStandardConfigLocations() {
160-
List<String> locations = new ArrayList<>();
161-
addLocationsFromProperties(locations);
162-
addStandardLocations(locations);
163-
return StringUtils.toStringArray(locations);
164-
}
165-
166-
private void addLocationsFromProperties(List<String> locations) {
167-
for (String property : List.of("log4j2.configurationFile", "log4j.configuration.location")) {
168-
String propertyDefinedLocation = PropertiesUtil.getProperties().getStringProperty(property);
169-
if (propertyDefinedLocation != null) {
170-
locations.add(propertyDefinedLocation);
171-
}
172-
}
125+
return new String[] { "log4j2.xml" };
173126
}
174127

175-
private void addStandardLocations(List<String> locations) {
176-
LoggerContext loggerContext = getLoggerContext();
177-
String contextName = loggerContext.getName();
178-
List<String> extensions = getStandardConfigExtensions();
179-
addLocation(locations, "log4j2-test" + contextName, extensions);
180-
addLocation(locations, "log4j2-test", extensions);
181-
addLocation(locations, "log4j2" + contextName, extensions);
182-
addLocation(locations, "log4j2", extensions);
183-
}
184-
185-
private List<String> getStandardConfigExtensions() {
186-
List<String> extensions = new ArrayList<>();
187-
// These classes need to be visible by the classloader that loads Log4j Core.
188-
ClassLoader classLoader = LoggerContext.class.getClassLoader();
189-
// The order of the extensions corresponds to the order in which Log4j Core 2 and
190-
// 3 will try to load them, in decreasing value of @Order.
191-
if (isPresent(classLoader, PROPS_CONFIGURATION_FACTORY_V2)
192-
|| isPresent(classLoader, PROPS_CONFIGURATION_FACTORY_V3)) {
193-
extensions.add(".properties");
194-
}
195-
if (isPresent(classLoader, YAML_CONFIGURATION_FACTORY_V2, YAML_TREE_PARSER_V2)
196-
|| isPresent(classLoader, YAML_CONFIGURATION_FACTORY_V3)) {
197-
Collections.addAll(extensions, ".yaml", ".yml");
198-
}
199-
if (isPresent(classLoader, JSON_TREE_PARSER_V2) || isPresent(classLoader, JSON_TREE_PARSER_V3)) {
200-
Collections.addAll(extensions, ".json", ".jsn");
201-
}
202-
extensions.add(".xml");
203-
return extensions;
204-
}
205-
206-
private void addLocation(List<String> locations, String location, List<String> extensions) {
207-
extensions.forEach((extension) -> locations.add(location + extension));
128+
@Override
129+
protected @Nullable String getSelfInitializationConfig() {
130+
Configuration currentConfiguration = getLoggerContext().getConfiguration();
131+
return getConfigLocation(currentConfiguration);
208132
}
209133

210-
private boolean isPresent(ClassLoader classLoader, String... classNames) {
211-
for (String className : classNames) {
212-
if (!isClassAvailable(classLoader, className)) {
213-
return false;
214-
}
215-
}
216-
return true;
134+
@Override
135+
protected @Nullable String getSpringInitializationConfig() {
136+
ConfigurationFactory configurationFactory = ConfigurationFactory.getInstance();
137+
Configuration springConfiguration = configurationFactory.getConfiguration(getLoggerContext(), "-spring", null,
138+
getClassLoader());
139+
return getConfigLocation(springConfiguration);
217140
}
218141

219-
protected boolean isClassAvailable(ClassLoader classLoader, String className) {
220-
return ClassUtils.isPresent(className, classLoader);
142+
private @Nullable String getConfigLocation(Configuration configuration) {
143+
// The location may be:
144+
// - null: if DefaultConfiguration is used (no explicit config loaded)
145+
// - a file path: if provided explicitly by the user
146+
// - a URI: if loaded from the classpath default or a custom location
147+
return configuration.getConfigurationSource().getLocation();
221148
}
222149

223150
@Deprecated(since = "4.0.0", forRemoval = true)
@@ -329,7 +256,7 @@ private void load(LoggingInitializationContext initializationContext, String loc
329256
Environment environment = initializationContext.getEnvironment();
330257
Assert.state(environment != null, "'environment' must not be null");
331258
applySystemProperties(environment, logFile);
332-
loadConfiguration(location, logFile, overrides);
259+
reconfigure(location, overrides);
333260
}
334261

335262
private List<String> getOverrides(LoggingInitializationContext initializationContext) {
@@ -340,66 +267,43 @@ private List<String> getOverrides(LoggingInitializationContext initializationCon
340267
return overrides.orElse(Collections.emptyList());
341268
}
342269

343-
/**
344-
* Load the configuration from the given {@code location}, creating a composite using
345-
* the configuration from the given {@code overrides}.
346-
* @param location the location
347-
* @param logFile log file configuration
348-
* @param overrides the overriding locations
349-
* @since 2.6.0
350-
*/
351-
protected void loadConfiguration(String location, @Nullable LogFile logFile, List<String> overrides) {
270+
private void reconfigure(String location, List<String> overrides) {
352271
Assert.notNull(location, "'location' must not be null");
353272
try {
354273
List<Configuration> configurations = new ArrayList<>();
355-
LoggerContext context = getLoggerContext();
356-
ResourceLoader resourceLoader = ApplicationResourceLoader.get();
357-
configurations.add(load(resourceLoader.getResource(location), context));
274+
ResourceLoader resourceLoader = ApplicationResourceLoader.get(getClassLoader());
275+
configurations.add(load(resourceLoader, location));
358276
for (String override : overrides) {
359-
Configuration overrideConfiguration = loadOverride(resourceLoader, override, context);
277+
Configuration overrideConfiguration = loadOverride(resourceLoader, override);
360278
if (overrideConfiguration != null) {
361279
configurations.add(overrideConfiguration);
362280
}
363281
}
364-
context.start(mergeConfigurations(configurations));
282+
this.loggerContext.reconfigure(mergeConfigurations(configurations));
365283
}
366284
catch (Exception ex) {
367285
throw new IllegalStateException("Could not initialize Log4J2 logging from " + location, ex);
368286
}
369287
}
370288

371-
private Configuration load(Resource resource, LoggerContext context) throws IOException {
289+
private Configuration load(ResourceLoader resourceLoader, String location) throws IOException {
372290
ConfigurationFactory factory = ConfigurationFactory.getInstance();
373-
if (resource.isFile()) {
374-
try (InputStream inputStream = resource.getInputStream()) {
375-
return factory.getConfiguration(context, new ConfigurationSource(inputStream, resource.getFile()));
376-
}
377-
}
378-
URL url = resource.getURL();
379-
AuthorizationProvider authorizationProvider = ConfigurationFactory
380-
.authorizationProvider(PropertiesUtil.getProperties());
381-
SslConfiguration sslConfiguration = url.getProtocol().equals("https")
382-
? SslConfigurationFactory.getSslConfiguration() : null;
383-
URLConnection connection = UrlConnectionFactory.createConnection(url, 0, sslConfiguration,
384-
authorizationProvider);
385-
try (InputStream inputStream = connection.getInputStream()) {
386-
return factory.getConfiguration(context,
387-
new ConfigurationSource(inputStream, url, connection.getLastModified()));
388-
}
291+
Resource resource = resourceLoader.getResource(location);
292+
return factory.getConfiguration(getLoggerContext(), null, resource.getURI(), getClassLoader());
389293
}
390294

391-
private @Nullable Configuration loadOverride(ResourceLoader resourceLoader, String location, LoggerContext context)
392-
throws IOException {
295+
private @Nullable Configuration loadOverride(ResourceLoader resourceLoader, String location) throws IOException {
393296
if (location.startsWith(OPTIONAL_PREFIX)) {
394-
Resource resource = resourceLoader.getResource(location.substring(OPTIONAL_PREFIX.length()));
297+
String actualLocation = location.substring(OPTIONAL_PREFIX.length());
298+
Resource resource = resourceLoader.getResource(actualLocation);
395299
try {
396-
return (resource.exists()) ? load(resource, context) : null;
300+
return (resource.exists()) ? load(resourceLoader, actualLocation) : null;
397301
}
398302
catch (FileNotFoundException ex) {
399303
return null;
400304
}
401305
}
402-
return load(resourceLoader.getResource(location), context);
306+
return load(resourceLoader, location);
403307
}
404308

405309
private Configuration mergeConfigurations(List<Configuration> configurations) {
@@ -411,33 +315,11 @@ private Configuration mergeConfigurations(List<Configuration> configurations) {
411315

412316
@Override
413317
protected void reinitialize(LoggingInitializationContext initializationContext) {
414-
List<String> overrides = getOverrides(initializationContext);
415-
if (!CollectionUtils.isEmpty(overrides)) {
416-
reinitializeWithOverrides(overrides);
417-
}
418-
else {
419-
LoggerContext context = getLoggerContext();
420-
context.reconfigure();
421-
}
422-
}
423-
424-
private void reinitializeWithOverrides(List<String> overrides) {
425-
LoggerContext context = getLoggerContext();
426-
List<Configuration> configurations = new ArrayList<>();
427-
configurations.add(context.getConfiguration());
428-
ResourceLoader resourceLoader = ApplicationResourceLoader.get();
429-
for (String override : overrides) {
430-
try {
431-
Configuration overrideConfiguration = loadOverride(resourceLoader, override, context);
432-
if (overrideConfiguration != null) {
433-
configurations.add(overrideConfiguration);
434-
}
435-
}
436-
catch (IOException ex) {
437-
throw new RuntimeException("Failed to load overriding configuration from '" + override + "'", ex);
438-
}
439-
}
440-
context.reconfigure(mergeConfigurations(configurations));
318+
String currentLocation = getSelfInitializationConfig();
319+
// `reinitialize` is only triggered when `getSelfInitializationConfig` returns a
320+
// non-null value
321+
Assert.notNull(currentLocation, "'currentLocation' must not be null");
322+
load(initializationContext, currentLocation, null);
441323
}
442324

443325
@Override
@@ -579,7 +461,7 @@ public void cleanUp() {
579461
}
580462

581463
private LoggerContext getLoggerContext() {
582-
return (LoggerContext) LogManager.getContext(false);
464+
return this.loggerContext;
583465
}
584466

585467
private boolean isAlreadyInitialized(LoggerContext loggerContext) {
@@ -614,15 +496,20 @@ protected String getDefaultLogCorrelationPattern() {
614496
* {@link LoggingSystemFactory} that returns {@link Log4J2LoggingSystem} if possible.
615497
*/
616498
@Order(0)
617-
public static class Factory implements LoggingSystemFactory {
499+
public static class Factory extends LogManager implements LoggingSystemFactory {
500+
501+
private static final String FQCN = Factory.class.getName();
618502

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

622505
@Override
623506
public @Nullable LoggingSystem getLoggingSystem(ClassLoader classLoader) {
624-
if (PRESENT) {
625-
return new Log4J2LoggingSystem(classLoader);
507+
LoggerContextFactory contextFactory = getFactory();
508+
// At the same time, we check that Log4j Core is present and that it is the
509+
// active
510+
// implementation.
511+
if (LOG4J_CORE_CONTEXT_FACTORY.equals(contextFactory.getClass().getName())) {
512+
return new Log4J2LoggingSystem(classLoader, contextFactory.getContext(FQCN, classLoader, null, false));
626513
}
627514
return null;
628515
}

0 commit comments

Comments
 (0)