1616
1717package org .springframework .boot .logging .log4j2 ;
1818
19- import java .io .FileNotFoundException ;
2019import java .io .IOException ;
21- import java .io .InputStream ;
22- import java .net .URL ;
23- import java .net .URLConnection ;
2420import java .util .ArrayList ;
2521import java .util .Collections ;
2622import java .util .LinkedHashMap ;
3733import org .apache .logging .log4j .core .LoggerContext ;
3834import org .apache .logging .log4j .core .config .AbstractConfiguration ;
3935import org .apache .logging .log4j .core .config .Configuration ;
36+ import org .apache .logging .log4j .core .config .ConfigurationException ;
4037import org .apache .logging .log4j .core .config .ConfigurationFactory ;
41- import org .apache .logging .log4j .core .config .ConfigurationSource ;
4238import org .apache .logging .log4j .core .config .LoggerConfig ;
4339import org .apache .logging .log4j .core .config .composite .CompositeConfiguration ;
4440import 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 ;
4941import org .apache .logging .log4j .core .util .NameUtil ;
5042import org .apache .logging .log4j .jul .Log4jBridgeHandler ;
5143import org .apache .logging .log4j .status .StatusConsoleListener ;
7264import org .springframework .core .io .ResourceLoader ;
7365import org .springframework .util .Assert ;
7466import org .springframework .util .ClassUtils ;
75- import org .springframework .util .CollectionUtils ;
7667import org .springframework .util .StringUtils ;
7768
7869/**
8879 */
8980public class Log4J2LoggingSystem extends AbstractLoggingSystem {
9081
82+ private static final org .apache .logging .log4j .Logger STATUS_LOGGER = StatusLogger .getLogger ();
83+
9184 private static final String OPTIONAL_PREFIX = "optional:" ;
9285
9386 /**
@@ -100,41 +93,6 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem {
10093 */
10194 static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager" ;
10295
103- /**
104- * JSON tree parser used by Log4j 2 (optional dependency).
105- */
106- static final String JSON_TREE_PARSER_V2 = "com.fasterxml.jackson.databind.ObjectMapper" ;
107-
108- /**
109- * JSON tree parser embedded in Log4j 3.
110- */
111- static final String JSON_TREE_PARSER_V3 = "org.apache.logging.log4j.kit.json.JsonReader" ;
112-
113- /**
114- * Configuration factory for properties files (Log4j 2).
115- */
116- static final String PROPS_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory" ;
117-
118- /**
119- * Configuration factory for properties files (Log4j 3, optional dependency).
120- */
121- static final String PROPS_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.properties.JavaPropsConfigurationFactory" ;
122-
123- /**
124- * YAML tree parser used by Log4j 2 (optional dependency).
125- */
126- static final String YAML_TREE_PARSER_V2 = "com.fasterxml.jackson.dataformat.yaml.YAMLMapper" ;
127-
128- /**
129- * Configuration factory for YAML files (Log4j 2, embedded).
130- */
131- static final String YAML_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory" ;
132-
133- /**
134- * Configuration factory for YAML files (Log4j 3, optional dependency).
135- */
136- static final String YAML_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.yaml.YamlConfigurationFactory" ;
137-
13896 private static final SpringEnvironmentPropertySource propertySource = new SpringEnvironmentPropertySource ();
13997
14098 static final String ENVIRONMENT_KEY = Conventions .getQualifiedAttributeName (Log4J2LoggingSystem .class ,
@@ -157,73 +115,69 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem {
157115
158116 private static final Filter FILTER = DenyAllFilter .newBuilder ().build ();
159117
160- public Log4J2LoggingSystem (ClassLoader classLoader ) {
161- super (classLoader );
162- }
118+ private final LoggerContext loggerContext ;
163119
164- @ Override
165- protected String [] getStandardConfigLocations () {
166- List <String > locations = new ArrayList <>();
167- addLocationsFromProperties (locations );
168- addStandardLocations (locations );
169- return StringUtils .toStringArray (locations );
120+ /**
121+ * Create a new {@link Log4J2LoggingSystem} instance.
122+ * @param classLoader the class loader to use.
123+ * @param loggerContext the {@link LoggerContext} to use.
124+ */
125+ Log4J2LoggingSystem (ClassLoader classLoader , LoggerContext loggerContext ) {
126+ super (classLoader );
127+ this .loggerContext = loggerContext ;
170128 }
171129
172- private void addLocationsFromProperties (List <String > locations ) {
173- for (String property : List .of ("log4j2.configurationFile" , "log4j.configuration.location" )) {
174- String propertyDefinedLocation = PropertiesUtil .getProperties ().getStringProperty (property );
175- if (propertyDefinedLocation != null ) {
176- locations .add (propertyDefinedLocation );
177- }
130+ /**
131+ * Create a new {@link Log4J2LoggingSystem} instance.
132+ * @param classLoader the class loader to use
133+ * @return a new {@link Log4J2LoggingSystem} instance
134+ * @throws IllegalStateException if Log4j Core is not the active Log4j API provider.
135+ */
136+ private static Log4J2LoggingSystem createLoggingSystem (ClassLoader classLoader ) {
137+ org .apache .logging .log4j .spi .LoggerContext loggerContext = LogManager .getContext (classLoader , false );
138+ if (loggerContext instanceof LoggerContext ) {
139+ return new Log4J2LoggingSystem (classLoader , (LoggerContext ) loggerContext );
178140 }
141+ throw new IllegalStateException ("Log4j Core is not the active Log4j API provider" );
179142 }
180143
181- private void addStandardLocations (List <String > locations ) {
182- LoggerContext loggerContext = getLoggerContext ();
183- String contextName = loggerContext .getName ();
184- List <String > extensions = getStandardConfigExtensions ();
185- addLocation (locations , "log4j2-test" + contextName , extensions );
186- addLocation (locations , "log4j2-test" , extensions );
187- addLocation (locations , "log4j2" + contextName , extensions );
188- addLocation (locations , "log4j2" , extensions );
189- }
190-
191- private List <String > getStandardConfigExtensions () {
192- List <String > extensions = new ArrayList <>();
193- // These classes need to be visible by the classloader that loads Log4j Core.
194- ClassLoader classLoader = LoggerContext .class .getClassLoader ();
195- // The order of the extensions corresponds to the order in which Log4j Core 2 and
196- // 3 will try to load them, in decreasing value of @Order.
197- if (isPresent (classLoader , PROPS_CONFIGURATION_FACTORY_V2 )
198- || isPresent (classLoader , PROPS_CONFIGURATION_FACTORY_V3 )) {
199- extensions .add (".properties" );
200- }
201- if (isPresent (classLoader , YAML_CONFIGURATION_FACTORY_V2 , YAML_TREE_PARSER_V2 )
202- || isPresent (classLoader , YAML_CONFIGURATION_FACTORY_V3 )) {
203- Collections .addAll (extensions , ".yaml" , ".yml" );
204- }
205- if (isPresent (classLoader , JSON_TREE_PARSER_V2 ) || isPresent (classLoader , JSON_TREE_PARSER_V3 )) {
206- Collections .addAll (extensions , ".json" , ".jsn" );
207- }
208- extensions .add (".xml" );
209- return extensions ;
144+ /**
145+ * {@inheritDoc}
146+ * @deprecated Since 4.0.0, in favor of the {@link ConfigurationFactory} SPI.
147+ */
148+ @ Override
149+ @ Deprecated (since = "4.0.0" , forRemoval = true )
150+ protected String [] getStandardConfigLocations () {
151+ return new String [] { "log4j2.xml" };
210152 }
211153
212- private void addLocation (List <String > locations , String location , List <String > extensions ) {
213- extensions .forEach ((extension ) -> locations .add (location + extension ));
154+ @ Override
155+ protected @ Nullable String getSelfInitializationConfig () {
156+ Configuration currentConfiguration = getLoggerContext ().getConfiguration ();
157+ return getConfigLocation (currentConfiguration );
214158 }
215159
216- private boolean isPresent (ClassLoader classLoader , String ... classNames ) {
217- for (String className : classNames ) {
218- if (!isClassAvailable (classLoader , className )) {
219- return false ;
220- }
160+ @ Override
161+ protected @ Nullable String getSpringInitializationConfig () {
162+ ConfigurationFactory configurationFactory = ConfigurationFactory .getInstance ();
163+ try {
164+ Configuration springConfiguration = configurationFactory .getConfiguration (getLoggerContext (), "-spring" ,
165+ null , getClassLoader ());
166+ String configLocation = getConfigLocation (springConfiguration );
167+ return (configLocation != null && configLocation .contains ("-spring" )) ? configLocation : null ;
168+ }
169+ catch (ConfigurationException ex ) {
170+ STATUS_LOGGER .warn ("Could not load Spring-specific Log4j Core configuration" , ex );
171+ return null ;
221172 }
222- return true ;
223173 }
224174
225- protected boolean isClassAvailable (ClassLoader classLoader , String className ) {
226- return ClassUtils .isPresent (className , classLoader );
175+ private @ Nullable String getConfigLocation (Configuration configuration ) {
176+ // The location may be:
177+ // - null: if DefaultConfiguration is used (no explicit config loaded)
178+ // - a file path: if provided explicitly by the user
179+ // - a URI: if loaded from the classpath default or a custom location
180+ return configuration .getConfigurationSource ().getLocation ();
227181 }
228182
229183 @ Deprecated (since = "4.0.0" , forRemoval = true )
@@ -335,7 +289,7 @@ private void load(LoggingInitializationContext initializationContext, String loc
335289 Environment environment = initializationContext .getEnvironment ();
336290 Assert .state (environment != null , "'environment' must not be null" );
337291 applySystemProperties (environment , logFile );
338- loadConfiguration (location , logFile , overrides );
292+ reconfigure (location , overrides );
339293 }
340294
341295 private List <String > getOverrides (LoggingInitializationContext initializationContext ) {
@@ -346,66 +300,56 @@ private List<String> getOverrides(LoggingInitializationContext initializationCon
346300 return overrides .orElse (Collections .emptyList ());
347301 }
348302
349- /**
350- * Load the configuration from the given {@code location}, creating a composite using
351- * the configuration from the given {@code overrides}.
352- * @param location the location
353- * @param logFile log file configuration
354- * @param overrides the overriding locations
355- * @since 2.6.0
356- */
357- protected void loadConfiguration (String location , @ Nullable LogFile logFile , List <String > overrides ) {
303+ private void reconfigure (String location , List <String > overrides ) {
358304 Assert .notNull (location , "'location' must not be null" );
359305 try {
360306 List <Configuration > configurations = new ArrayList <>();
361- LoggerContext context = getLoggerContext ();
362- ResourceLoader resourceLoader = ApplicationResourceLoader .get ();
363- configurations .add (load (resourceLoader .getResource (location ), context ));
307+ ResourceLoader resourceLoader = ApplicationResourceLoader .get (getClassLoader ());
308+ configurations .add (load (resourceLoader , location ));
364309 for (String override : overrides ) {
365- Configuration overrideConfiguration = loadOverride (resourceLoader , override , context );
310+ Configuration overrideConfiguration = loadOverride (resourceLoader , override );
366311 if (overrideConfiguration != null ) {
367312 configurations .add (overrideConfiguration );
368313 }
369314 }
370- context .reconfigure (mergeConfigurations (configurations ));
315+ this . loggerContext .reconfigure (mergeConfigurations (configurations ));
371316 }
372317 catch (Exception ex ) {
373- throw new IllegalStateException ("Could not initialize Log4J2 logging from " + location , ex );
318+ String message = "Could not initialize Log4J2 logging from " + location ;
319+ if (!overrides .isEmpty ()) {
320+ message += " with overrides " + overrides ;
321+ }
322+ throw new IllegalStateException (message , ex );
374323 }
375324 }
376325
377- private Configuration load (Resource resource , LoggerContext context ) throws IOException {
326+ private Configuration load (ResourceLoader resourceLoader , String location ) throws IOException {
378327 ConfigurationFactory factory = ConfigurationFactory .getInstance ();
379- if (resource .isFile ()) {
380- try (InputStream inputStream = resource .getInputStream ()) {
381- return factory .getConfiguration (context , new ConfigurationSource (inputStream , resource .getFile ()));
382- }
383- }
384- URL url = resource .getURL ();
385- AuthorizationProvider authorizationProvider = ConfigurationFactory
386- .authorizationProvider (PropertiesUtil .getProperties ());
387- SslConfiguration sslConfiguration = url .getProtocol ().equals ("https" )
388- ? SslConfigurationFactory .getSslConfiguration () : null ;
389- URLConnection connection = UrlConnectionFactory .createConnection (url , 0 , sslConfiguration ,
390- authorizationProvider );
391- try (InputStream inputStream = connection .getInputStream ()) {
392- return factory .getConfiguration (context ,
393- new ConfigurationSource (inputStream , url , connection .getLastModified ()));
328+ Resource resource = resourceLoader .getResource (location );
329+ Configuration configuration = factory .getConfiguration (getLoggerContext (), null , resource .getURI (),
330+ getClassLoader ());
331+ // The error handling in Log4j Core 2.25.x is not consistent:
332+ // some loading and parsing errors result in a null configuration,
333+ // others in an exception.
334+ if (configuration == null ) {
335+ throw new ConfigurationException ("Could not load Log4j Core configuration from " + location );
394336 }
337+ return configuration ;
395338 }
396339
397- private @ Nullable Configuration loadOverride (ResourceLoader resourceLoader , String location , LoggerContext context )
398- throws IOException {
340+ private @ Nullable Configuration loadOverride (ResourceLoader resourceLoader , String location ) throws IOException {
399341 if (location .startsWith (OPTIONAL_PREFIX )) {
400- Resource resource = resourceLoader .getResource (location .substring (OPTIONAL_PREFIX .length ()));
342+ String actualLocation = location .substring (OPTIONAL_PREFIX .length ());
343+ Resource resource = resourceLoader .getResource (actualLocation );
401344 try {
402- return (resource .exists ()) ? load (resource , context ) : null ;
345+ return (resource .exists ()) ? load (resourceLoader , actualLocation ) : null ;
403346 }
404- catch (FileNotFoundException ex ) {
347+ catch (ConfigurationException | IOException ex ) {
348+ STATUS_LOGGER .debug ("Could not load optional Log4j2 override from {}" , actualLocation , ex );
405349 return null ;
406350 }
407351 }
408- return load (resourceLoader . getResource ( location ), context );
352+ return load (resourceLoader , location );
409353 }
410354
411355 private Configuration mergeConfigurations (List <Configuration > configurations ) {
@@ -417,33 +361,11 @@ private Configuration mergeConfigurations(List<Configuration> configurations) {
417361
418362 @ Override
419363 protected void reinitialize (LoggingInitializationContext initializationContext ) {
420- List <String > overrides = getOverrides (initializationContext );
421- if (!CollectionUtils .isEmpty (overrides )) {
422- reinitializeWithOverrides (overrides );
423- }
424- else {
425- LoggerContext context = getLoggerContext ();
426- context .reconfigure ();
427- }
428- }
429-
430- private void reinitializeWithOverrides (List <String > overrides ) {
431- LoggerContext context = getLoggerContext ();
432- List <Configuration > configurations = new ArrayList <>();
433- configurations .add (context .getConfiguration ());
434- ResourceLoader resourceLoader = ApplicationResourceLoader .get ();
435- for (String override : overrides ) {
436- try {
437- Configuration overrideConfiguration = loadOverride (resourceLoader , override , context );
438- if (overrideConfiguration != null ) {
439- configurations .add (overrideConfiguration );
440- }
441- }
442- catch (IOException ex ) {
443- throw new RuntimeException ("Failed to load overriding configuration from '" + override + "'" , ex );
444- }
445- }
446- context .reconfigure (mergeConfigurations (configurations ));
364+ String currentLocation = getSelfInitializationConfig ();
365+ // `reinitialize` is only triggered when `getSelfInitializationConfig` returns a
366+ // non-null value
367+ Assert .notNull (currentLocation , "'currentLocation' must not be null" );
368+ load (initializationContext , currentLocation , null );
447369 }
448370
449371 @ Override
@@ -584,8 +506,8 @@ public void cleanUp() {
584506 return configuration .getLoggers ().get (name );
585507 }
586508
587- private LoggerContext getLoggerContext () {
588- return ( LoggerContext ) LogManager . getContext ( false ) ;
509+ LoggerContext getLoggerContext () {
510+ return this . loggerContext ;
589511 }
590512
591513 private boolean isAlreadyInitialized (LoggerContext loggerContext ) {
@@ -622,15 +544,20 @@ protected String getDefaultLogCorrelationPattern() {
622544 @ Order (0 )
623545 public static class Factory implements LoggingSystemFactory {
624546
625- static final String LOG4J_CORE_CONTEXT_FACTORY = "org.apache.logging.log4j.core.impl.Log4jContextFactory" ;
547+ private static final String LOG4J_CORE_CONTEXT_FACTORY = "org.apache.logging.log4j.core.impl.Log4jContextFactory" ;
626548
627549 private static final boolean PRESENT = ClassUtils .isPresent (LOG4J_CORE_CONTEXT_FACTORY ,
628550 Factory .class .getClassLoader ());
629551
630552 @ Override
631553 public @ Nullable LoggingSystem getLoggingSystem (ClassLoader classLoader ) {
632554 if (PRESENT ) {
633- return new Log4J2LoggingSystem (classLoader );
555+ try {
556+ return createLoggingSystem (classLoader );
557+ }
558+ catch (IllegalStateException ex ) {
559+ // Log4j Core is not the active Log4j API provider
560+ }
634561 }
635562 return null ;
636563 }
0 commit comments