View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.config;
18  
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.MalformedURLException;
25  import java.net.URI;
26  import java.net.URISyntaxException;
27  import java.net.URL;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.concurrent.locks.Lock;
34  import java.util.concurrent.locks.ReentrantLock;
35  
36  import org.apache.logging.log4j.Level;
37  import org.apache.logging.log4j.Logger;
38  import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
39  import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
40  import org.apache.logging.log4j.core.config.plugins.util.PluginType;
41  import org.apache.logging.log4j.core.lookup.Interpolator;
42  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
43  import org.apache.logging.log4j.core.util.FileUtils;
44  import org.apache.logging.log4j.core.util.Loader;
45  import org.apache.logging.log4j.core.util.NetUtils;
46  import org.apache.logging.log4j.core.util.ReflectionUtil;
47  import org.apache.logging.log4j.status.StatusLogger;
48  import org.apache.logging.log4j.util.LoaderUtil;
49  import org.apache.logging.log4j.util.PropertiesUtil;
50  import org.apache.logging.log4j.util.Strings;
51  
52  /**
53   * Factory class for parsed {@link Configuration} objects from a configuration file.
54   * ConfigurationFactory allows the configuration implementation to be
55   * dynamically chosen in 1 of 3 ways:
56   * <ol>
57   * <li>A system property named "log4j.configurationFactory" can be set with the
58   * name of the ConfigurationFactory to be used.</li>
59   * <li>
60   * {@linkplain #setConfigurationFactory(ConfigurationFactory)} can be called
61   * with the instance of the ConfigurationFactory to be used. This must be called
62   * before any other calls to Log4j.</li>
63   * <li>
64   * A ConfigurationFactory implementation can be added to the classpath and configured as a plugin in the
65   * {@link #CATEGORY ConfigurationFactory} category. The {@link Order} annotation should be used to configure the
66   * factory to be the first one inspected. See
67   * {@linkplain org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory} for an example.</li>
68   * </ol>
69   *
70   * If the ConfigurationFactory that was added returns null on a call to
71   * getConfiguration then any other ConfigurationFactories found as plugins will
72   * be called in their respective order. DefaultConfiguration is always called
73   * last if no configuration has been returned.
74   */
75  public abstract class ConfigurationFactory extends ConfigurationBuilderFactory {
76      /**
77       * Allow the ConfigurationFactory class to be specified as a system property.
78       */
79      public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory";
80  
81      /**
82       * Allow the location of the configuration file to be specified as a system property.
83       */
84      public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile";
85  
86      /**
87       * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.core.config.plugins.Plugin}
88       * class.
89       *
90       * @since 2.1
91       */
92      public static final String CATEGORY = "ConfigurationFactory";
93  
94      /**
95       * Allow subclasses access to the status logger without creating another instance.
96       */
97      protected static final Logger LOGGER = StatusLogger.getLogger();
98  
99      /**
100      * File name prefix for test configurations.
101      */
102     protected static final String TEST_PREFIX = "log4j2-test";
103 
104     /**
105      * File name prefix for standard configurations.
106      */
107     protected static final String DEFAULT_PREFIX = "log4j2";
108 
109     /**
110      * The name of the classloader URI scheme.
111      */
112     private static final String CLASS_LOADER_SCHEME = "classloader";
113 
114     /**
115      * The name of the classpath URI scheme, synonymous with the classloader URI scheme.
116      */
117     private static final String CLASS_PATH_SCHEME = "classpath";
118 
119     private static volatile List<ConfigurationFactory> factories = null;
120 
121     private static ConfigurationFactory configFactory = new Factory();
122 
123     protected final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator());
124 
125     private static final Lock LOCK = new ReentrantLock();
126 
127     /**
128      * Returns the ConfigurationFactory.
129      * @return the ConfigurationFactory.
130      */
131     public static ConfigurationFactory getInstance() {
132         // volatile works in Java 1.6+, so double-checked locking also works properly
133         //noinspection DoubleCheckedLocking
134         if (factories == null) {
135             LOCK.lock();
136             try {
137                 if (factories == null) {
138                     final List<ConfigurationFactory> list = new ArrayList<>();
139                     final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
140                     if (factoryClass != null) {
141                         addFactory(list, factoryClass);
142                     }
143                     final PluginManager manager = new PluginManager(CATEGORY);
144                     manager.collectPlugins();
145                     final Map<String, PluginType<?>> plugins = manager.getPlugins();
146                     final List<Class<? extends ConfigurationFactory>> ordered = new ArrayList<>(plugins.size());
147                     for (final PluginType<?> type : plugins.values()) {
148                         try {
149                             ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class));
150                         } catch (final Exception ex) {
151                             LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex);
152                         }
153                     }
154                     Collections.sort(ordered, OrderComparator.getInstance());
155                     for (final Class<? extends ConfigurationFactory> clazz : ordered) {
156                         addFactory(list, clazz);
157                     }
158                     // see above comments about double-checked locking
159                     //noinspection NonThreadSafeLazyInitialization
160                     factories = Collections.unmodifiableList(list);
161                 }
162             } finally {
163                 LOCK.unlock();
164             }
165         }
166 
167         LOGGER.debug("Using configurationFactory {}", configFactory);
168         return configFactory;
169     }
170 
171     private static void addFactory(final Collection<ConfigurationFactory> list, final String factoryClass) {
172         try {
173             addFactory(list, LoaderUtil.loadClass(factoryClass).asSubclass(ConfigurationFactory.class));
174         } catch (final Exception ex) {
175             LOGGER.error("Unable to load class {}", factoryClass, ex);
176         }
177     }
178 
179     private static void addFactory(final Collection<ConfigurationFactory> list,
180                                    final Class<? extends ConfigurationFactory> factoryClass) {
181         try {
182             list.add(ReflectionUtil.instantiate(factoryClass));
183         } catch (final Exception ex) {
184             LOGGER.error("Unable to create instance of {}", factoryClass.getName(), ex);
185         }
186     }
187 
188     /**
189      * Set the configuration factory. This method is not intended for general use and may not be thread safe.
190      * @param factory the ConfigurationFactory.
191      */
192     public static void setConfigurationFactory(final ConfigurationFactory factory) {
193         configFactory = factory;
194     }
195 
196     /**
197      * Reset the ConfigurationFactory to the default. This method is not intended for general use and may
198      * not be thread safe.
199      */
200     public static void resetConfigurationFactory() {
201         configFactory = new Factory();
202     }
203 
204     /**
205      * Remove the ConfigurationFactory. This method is not intended for general use and may not be thread safe.
206      * @param factory The factory to remove.
207      */
208     public static void removeConfigurationFactory(final ConfigurationFactory factory) {
209         if (configFactory == factory) {
210             configFactory = new Factory();
211         }
212     }
213 
214     protected abstract String[] getSupportedTypes();
215 
216     protected boolean isActive() {
217         return true;
218     }
219 
220     public abstract Configuration getConfiguration(ConfigurationSource source);
221 
222     /**
223      * Returns the Configuration.
224      * @param name The configuration name.
225      * @param configLocation The configuration location.
226      * @return The Configuration.
227      */
228     public Configuration getConfiguration(final String name, final URI configLocation) {
229         if (!isActive()) {
230             return null;
231         }
232         if (configLocation != null) {
233             final ConfigurationSource source = getInputFromUri(configLocation);
234             if (source != null) {
235                 return getConfiguration(source);
236             }
237         }
238         return null;
239     }
240 
241     /**
242      * Returns the Configuration obtained using a given ClassLoader.
243      *
244      * @param name The configuration name.
245      * @param configLocation A URI representing the location of the configuration.
246      * @param loader The default ClassLoader to use. If this is {@code null}, then the
247      *               {@linkplain LoaderUtil#getThreadContextClassLoader() default ClassLoader} will be used.
248      * @return The Configuration.
249      * @since 2.1
250      */
251     public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) {
252         if (!isActive()) {
253             return null;
254         }
255         if (loader == null) {
256             return getConfiguration(name, configLocation);
257         }
258         if (isClassLoaderUri(configLocation)) {
259             final String path = extractClassLoaderUriPath(configLocation);
260             final ConfigurationSource source = getInputFromResource(path, loader);
261             if (source != null) {
262                 final Configuration configuration = getConfiguration(source);
263                 if (configuration != null) {
264                     return configuration;
265                 }
266             }
267         }
268         return getConfiguration(name, configLocation);
269     }
270 
271     /**
272      * Load the configuration from a URI.
273      * @param configLocation A URI representing the location of the configuration.
274      * @return The ConfigurationSource for the configuration.
275      */
276     protected ConfigurationSource getInputFromUri(final URI configLocation) {
277         final File configFile = FileUtils.fileFromUri(configLocation);
278         if (configFile != null && configFile.exists() && configFile.canRead()) {
279             try {
280                 return new ConfigurationSource(new FileInputStream(configFile), configFile);
281             } catch (final FileNotFoundException ex) {
282                 LOGGER.error("Cannot locate file {}", configLocation.getPath(), ex);
283             }
284         }
285         if (isClassLoaderUri(configLocation)) {
286             final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
287             final String path = extractClassLoaderUriPath(configLocation);
288             final ConfigurationSource source = getInputFromResource(path, loader);
289             if (source != null) {
290                 return source;
291             }
292         }
293         if (!configLocation.isAbsolute()) { // LOG4J2-704 avoid confusing error message thrown by uri.toURL()
294             LOGGER.error("File not found in file system or classpath: {}", configLocation.toString());
295             return null;
296         }
297         try {
298             return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL());
299         } catch (final MalformedURLException ex) {
300             LOGGER.error("Invalid URL {}", configLocation.toString(), ex);
301         } catch (final Exception ex) {
302             LOGGER.error("Unable to access {}", configLocation.toString(), ex);
303         }
304         return null;
305     }
306 
307     private static boolean isClassLoaderUri(final URI uri) {
308         if (uri == null) {
309             return false;
310         }
311         final String scheme = uri.getScheme();
312         return scheme == null || scheme.equals(CLASS_LOADER_SCHEME) || scheme.equals(CLASS_PATH_SCHEME);
313     }
314 
315     private static String extractClassLoaderUriPath(final URI uri) {
316         return uri.getScheme() == null ? uri.getPath() : uri.getSchemeSpecificPart();
317     }
318 
319     /**
320      * Load the configuration from the location represented by the String.
321      * @param config The configuration location.
322      * @param loader The default ClassLoader to use.
323      * @return The InputSource to use to read the configuration.
324      */
325     protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) {
326         try {
327             final URL url = new URL(config);
328             return new ConfigurationSource(url.openStream(), FileUtils.fileFromUri(url.toURI()));
329         } catch (final Exception ex) {
330             final ConfigurationSource source = getInputFromResource(config, loader);
331             if (source == null) {
332                 try {
333                     final File file = new File(config);
334                     return new ConfigurationSource(new FileInputStream(file), file);
335                 } catch (final FileNotFoundException fnfe) {
336                     // Ignore the exception
337                     LOGGER.catching(Level.DEBUG, fnfe);
338                 }
339             }
340             return source;
341         }
342     }
343 
344     /**
345      * Retrieve the configuration via the ClassLoader.
346      * @param resource The resource to load.
347      * @param loader The default ClassLoader to use.
348      * @return The ConfigurationSource for the configuration.
349      */
350     protected ConfigurationSource getInputFromResource(final String resource, final ClassLoader loader) {
351         final URL url = Loader.getResource(resource, loader);
352         if (url == null) {
353             return null;
354         }
355         InputStream is = null;
356         try {
357             is = url.openStream();
358         } catch (final IOException ioe) {
359             LOGGER.catching(Level.DEBUG, ioe);
360             return null;
361         }
362         if (is == null) {
363             return null;
364         }
365 
366         if (FileUtils.isFile(url)) {
367             try {
368                 return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI()));
369             } catch (final URISyntaxException ex) {
370                 // Just ignore the exception.
371                 LOGGER.catching(Level.DEBUG, ex);
372             }
373         }
374         return new ConfigurationSource(is, url);
375     }
376 
377     /**
378      * Default Factory.
379      */
380     private static class Factory extends ConfigurationFactory {
381 
382         /**
383          * Default Factory Constructor.
384          * @param name The configuration name.
385          * @param configLocation The configuration location.
386          * @return The Configuration.
387          */
388         @Override
389         public Configuration getConfiguration(final String name, final URI configLocation) {
390 
391             if (configLocation == null) {
392                 final String configLocationStr = this.substitutor.replace(PropertiesUtil.getProperties()
393                         .getStringProperty(CONFIGURATION_FILE_PROPERTY));
394                 if (configLocationStr != null) {
395                     ConfigurationSource source = null;
396                     try {
397                         source = getInputFromUri(NetUtils.toURI(configLocationStr));
398                     } catch (final Exception ex) {
399                         // Ignore the error and try as a String.
400                         LOGGER.catching(Level.DEBUG, ex);
401                     }
402                     if (source == null) {
403                         final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
404                         source = getInputFromString(configLocationStr, loader);
405                     }
406                     if (source != null) {
407                         for (final ConfigurationFactory factory : getFactories()) {
408                             final String[] types = factory.getSupportedTypes();
409                             if (types != null) {
410                                 for (final String type : types) {
411                                     if (type.equals("*") || configLocationStr.endsWith(type)) {
412                                         final Configuration config = factory.getConfiguration(source);
413                                         if (config != null) {
414                                             return config;
415                                         }
416                                     }
417                                 }
418                             }
419                         }
420                     }
421                 } else {
422                     for (final ConfigurationFactory factory : getFactories()) {
423                         final String[] types = factory.getSupportedTypes();
424                         if (types != null) {
425                             for (final String type : types) {
426                                 if (type.equals("*")) {
427                                     final Configuration config = factory.getConfiguration(name, configLocation);
428                                     if (config != null) {
429                                         return config;
430                                     }
431                                 }
432                             }
433                         }
434                     }
435                 }
436             } else {
437                 // configLocation != null
438                 final String configLocationStr = configLocation.toString();
439                 for (final ConfigurationFactory factory : getFactories()) {
440                     final String[] types = factory.getSupportedTypes();
441                     if (types != null) {
442                         for (final String type : types) {
443                             if (type.equals("*") || configLocationStr.endsWith(type)) {
444                                 final Configuration config = factory.getConfiguration(name, configLocation);
445                                 if (config != null) {
446                                     return config;
447                                 }
448                             }
449                         }
450                     }
451                 }
452             }
453 
454             Configuration config = getConfiguration(true, name);
455             if (config == null) {
456                 config = getConfiguration(true, null);
457                 if (config == null) {
458                     config = getConfiguration(false, name);
459                     if (config == null) {
460                         config = getConfiguration(false, null);
461                     }
462                 }
463             }
464             if (config != null) {
465                 return config;
466             }
467             LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");
468             return new DefaultConfiguration();
469         }
470 
471         private Configuration getConfiguration(final boolean isTest, final String name) {
472             final boolean named = Strings.isNotEmpty(name);
473             final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
474             for (final ConfigurationFactory factory : getFactories()) {
475                 String configName;
476                 final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
477                 final String [] types = factory.getSupportedTypes();
478                 if (types == null) {
479                     continue;
480                 }
481 
482                 for (final String suffix : types) {
483                     if (suffix.equals("*")) {
484                         continue;
485                     }
486                     configName = named ? prefix + name + suffix : prefix + suffix;
487 
488                     final ConfigurationSource source = getInputFromResource(configName, loader);
489                     if (source != null) {
490                         return factory.getConfiguration(source);
491                     }
492                 }
493             }
494             return null;
495         }
496 
497         @Override
498         public String[] getSupportedTypes() {
499             return null;
500         }
501 
502         @Override
503         public Configuration getConfiguration(final ConfigurationSource source) {
504             if (source != null) {
505                 final String config = source.getLocation();
506                 for (final ConfigurationFactory factory : getFactories()) {
507                     final String[] types = factory.getSupportedTypes();
508                     if (types != null) {
509                         for (final String type : types) {
510                             if (type.equals("*") || config != null && config.endsWith(type)) {
511                                 final Configuration c = factory.getConfiguration(source);
512                                 if (c != null) {
513                                     LOGGER.debug("Loaded configuration from {}", source);
514                                     return c;
515                                 }
516                                 LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config);
517                                 return null;
518                             }
519                         }
520                     }
521                 }
522             }
523             LOGGER.error("Cannot process configuration, input source is null");
524             return null;
525         }
526     }
527 
528     static List<ConfigurationFactory> getFactories() {
529         return factories;
530     }
531 }