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