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.plugins.util.PluginManager;
39  import org.apache.logging.log4j.core.config.plugins.util.PluginType;
40  import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
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.ReflectionUtil;
46  import org.apache.logging.log4j.status.StatusLogger;
47  import org.apache.logging.log4j.util.LoaderUtil;
48  import org.apache.logging.log4j.util.PropertiesUtil;
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 the 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 {
74      /**
75       * Allow the ConfigurationFactory class to be specified as a system property.
76       */
77      public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory";
78  
79      /**
80       * Allow the location of the configuration file to be specified as a system property.
81       */
82      public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile";
83  
84      /**
85       * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.core.config.plugins.Plugin}
86       * class.
87       *
88       * @since 2.1
89       */
90      public static final String CATEGORY = "ConfigurationFactory";
91  
92      /**
93       * Allow subclasses access to the status logger without creating another instance.
94       */
95      protected static final Logger LOGGER = StatusLogger.getLogger();
96  
97      /**
98       * File name prefix for test configurations.
99       */
100     protected static final String TEST_PREFIX = "log4j2-test";
101 
102     /**
103      * File name prefix for standard configurations.
104      */
105     protected static final String DEFAULT_PREFIX = "log4j2";
106 
107     /**
108      * The name of the classloader URI scheme.
109      */
110     private static final String CLASS_LOADER_SCHEME = "classloader";
111 
112     /**
113      * The name of the classpath URI scheme, synonymous with the classloader URI scheme.
114      */
115     private static final String CLASS_PATH_SCHEME = "classpath";
116 
117     private static volatile List<ConfigurationFactory> factories = null;
118 
119     private static ConfigurationFactory configFactory = new Factory();
120 
121     protected final StrSubstitutor substitutor = new ConfigurationStrSubstitutor(new Interpolator());
122 
123     private static final Lock LOCK = new ReentrantLock();
124 
125     /**
126      * Returns the ConfigurationFactory.
127      * @return the ConfigurationFactory.
128      */
129     public static ConfigurationFactory getInstance() {
130         // volatile works in Java 1.6+, so double-checked locking also works properly
131         //noinspection DoubleCheckedLocking
132         if (factories == null) {
133             LOCK.lock();
134             try {
135                 if (factories == null) {
136                     final List<ConfigurationFactory> list = new ArrayList<ConfigurationFactory>();
137                     final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
138                     if (factoryClass != null) {
139                         addFactory(list, factoryClass);
140                     }
141                     final PluginManager manager = new PluginManager(CATEGORY);
142                     manager.collectPlugins();
143                     final Map<String, PluginType<?>> plugins = manager.getPlugins();
144                     final List<Class<? extends ConfigurationFactory>> ordered =
145                         new ArrayList<Class<? extends ConfigurationFactory>>(plugins.size());
146                     for (final PluginType<?> type : plugins.values()) {
147                         try {
148                             ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class));
149                         } catch (final Exception ex) {
150                             LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex);
151                         }
152                     }
153                     Collections.sort(ordered, OrderComparator.getInstance());
154                     for (final Class<? extends ConfigurationFactory> clazz : ordered) {
155                         addFactory(list, clazz);
156                     }
157                     // see above comments about double-checked locking
158                     //noinspection NonThreadSafeLazyInitialization
159                     factories = Collections.unmodifiableList(list);
160                 }
161             } finally {
162                 LOCK.unlock();
163             }
164         }
165 
166         LOGGER.debug("Using configurationFactory {}", configFactory);
167         return configFactory;
168     }
169 
170     private static void addFactory(final Collection<ConfigurationFactory> list, final String factoryClass) {
171         try {
172             addFactory(list, LoaderUtil.loadClass(factoryClass).asSubclass(ConfigurationFactory.class));
173         } catch (final Exception ex) {
174             LOGGER.error("Unable to load class {}", factoryClass, ex);
175         }
176     }
177 
178     private static void addFactory(final Collection<ConfigurationFactory> list,
179                                    final Class<? extends ConfigurationFactory> factoryClass) {
180         try {
181             list.add(ReflectionUtil.instantiate(factoryClass));
182         } catch (final Exception ex) {
183             LOGGER.error("Unable to create instance of {}", factoryClass.getName(), ex);
184         }
185     }
186 
187     /**
188      * Set the configuration factory. This method is not intended for general use and may not be thread safe.
189      * @param factory the ConfigurationFactory.
190      */
191     public static void setConfigurationFactory(final ConfigurationFactory factory) {
192         configFactory = factory;
193     }
194 
195     /**
196      * Reset the ConfigurationFactory to the default. This method is not intended for general use and may
197      * not be thread safe.
198      */
199     public static void resetConfigurationFactory() {
200         configFactory = new Factory();
201     }
202 
203     /**
204      * Remove the ConfigurationFactory. This method is not intended for general use and may not be thread safe.
205      * @param factory The factory to remove.
206      */
207     public static void removeConfigurationFactory(final ConfigurationFactory factory) {
208         if (configFactory == factory) {
209             configFactory = new Factory();
210         }
211     }
212 
213     protected abstract String[] getSupportedTypes();
214 
215     protected boolean isActive() {
216         return true;
217     }
218 
219     public abstract Configuration getConfiguration(ConfigurationSource source);
220 
221     /**
222      * Returns the Configuration.
223      * @param name The configuration name.
224      * @param configLocation The configuration location.
225      * @return The Configuration.
226      */
227     public Configuration getConfiguration(final String name, final URI configLocation) {
228         if (!isActive()) {
229             return null;
230         }
231         if (configLocation != null) {
232             final ConfigurationSource source = getInputFromUri(configLocation);
233             if (source != null) {
234                 return getConfiguration(source);
235             }
236         }
237         return null;
238     }
239 
240     /**
241      * Returns the Configuration obtained using a given ClassLoader.
242      *
243      * @param name The configuration name.
244      * @param configLocation A URI representing the location of the configuration.
245      * @param loader The default ClassLoader to use. If this is {@code null}, then the
246      *               {@linkplain LoaderUtil#getThreadContextClassLoader() default ClassLoader} will be used.
247      * @return The Configuration.
248      * @since 2.1
249      */
250     public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) {
251         if (!isActive()) {
252             return null;
253         }
254         if (loader == null) {
255             return getConfiguration(name, configLocation);
256         }
257         if (isClassLoaderUri(configLocation)) {
258             final String path = extractClassLoaderUriPath(configLocation);
259             final ConfigurationSource source = getInputFromResource(path, loader);
260             if (source != null) {
261                 final Configuration configuration = getConfiguration(source);
262                 if (configuration != null) {
263                     return configuration;
264                 }
265             }
266         }
267         return getConfiguration(name, configLocation);
268     }
269 
270     /**
271      * Load the configuration from a URI.
272      * @param configLocation A URI representing the location of the configuration.
273      * @return The ConfigurationSource for the configuration.
274      */
275     protected ConfigurationSource getInputFromUri(final URI configLocation) {
276         final File configFile = FileUtils.fileFromUri(configLocation);
277         if (configFile != null && configFile.exists() && configFile.canRead()) {
278             try {
279                 return new ConfigurationSource(new FileInputStream(configFile), configFile);
280             } catch (final FileNotFoundException ex) {
281                 LOGGER.error("Cannot locate file {}", configLocation.getPath(), ex);
282             }
283         }
284         if (isClassLoaderUri(configLocation)) {
285             final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
286             final String path = extractClassLoaderUriPath(configLocation);
287             final ConfigurationSource source = getInputFromResource(path, loader);
288             if (source != null) {
289                 return source;
290             }
291         }
292         if (!configLocation.isAbsolute()) { // LOG4J2-704 avoid confusing error message thrown by uri.toURL()
293             LOGGER.error("File not found in file system or classpath: {}", configLocation.toString());
294             return null;
295         }
296         try {
297             return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL());
298         } catch (final MalformedURLException ex) {
299             LOGGER.error("Invalid URL {}", configLocation.toString(), ex);
300         } catch (final Exception ex) {
301             LOGGER.error("Unable to access {}", configLocation.toString(), ex);
302         }
303         return null;
304     }
305 
306     private static boolean isClassLoaderUri(final URI uri) {
307         if (uri == null) {
308             return false;
309         }
310         final String scheme = uri.getScheme();
311         return scheme == null || scheme.equals(CLASS_LOADER_SCHEME) || scheme.equals(CLASS_PATH_SCHEME);
312     }
313 
314     private static String extractClassLoaderUriPath(final URI uri) {
315         return uri.getScheme() == null ? uri.getPath() : uri.getSchemeSpecificPart();
316     }
317 
318     /**
319      * Load the configuration from the location represented by the String.
320      * @param config The configuration location.
321      * @param loader The default ClassLoader to use.
322      * @return The InputSource to use to read the configuration.
323      */
324     protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) {
325         try {
326             final URL url = new URL(config);
327             return new ConfigurationSource(url.openStream(), FileUtils.fileFromUri(url.toURI()));
328         } catch (final Exception ex) {
329             final ConfigurationSource source = getInputFromResource(config, loader);
330             if (source == null) {
331                 try {
332                     final File file = new File(config);
333                     return new ConfigurationSource(new FileInputStream(file), file);
334                 } catch (final FileNotFoundException fnfe) {
335                     // Ignore the exception
336                     LOGGER.catching(Level.DEBUG, fnfe);
337                 }
338             }
339             return source;
340         }
341     }
342 
343     /**
344      * Retrieve the configuration via the ClassLoader.
345      * @param resource The resource to load.
346      * @param loader The default ClassLoader to use.
347      * @return The ConfigurationSource for the configuration.
348      */
349     protected ConfigurationSource getInputFromResource(final String resource, final ClassLoader loader) {
350         final URL url = Loader.getResource(resource, loader);
351         if (url == null) {
352             return null;
353         }
354         InputStream is = null;
355         try {
356             is = url.openStream();
357         } catch (final IOException ioe) {
358             LOGGER.catching(Level.DEBUG, ioe);
359             return null;
360         }
361         if (is == null) {
362             return null;
363         }
364 
365         if (FileUtils.isFile(url)) {
366             try {
367                 return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI()));
368             } catch (final URISyntaxException ex) {
369                 // Just ignore the exception.
370                 LOGGER.catching(Level.DEBUG, ex);
371             }
372         }
373         return new ConfigurationSource(is, url);
374     }
375 
376     /**
377      * Default Factory.
378      */
379     private static class Factory extends ConfigurationFactory {
380 
381         /**
382          * Default Factory Constructor.
383          * @param name The configuration name.
384          * @param configLocation The configuration location.
385          * @return The Configuration.
386          */
387         @Override
388         public Configuration getConfiguration(final String name, final URI configLocation) {
389 
390             if (configLocation == null) {
391                 final String config = this.substitutor.replace(
392                     PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY));
393                 if (config != null) {
394                     ConfigurationSource source = null;
395                     try {
396                         source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config));
397                     } catch (final Exception ex) {
398                         // Ignore the error and try as a String.
399                         LOGGER.catching(Level.DEBUG, ex);
400                     }
401                     if (source == null) {
402                         final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
403                         source = getInputFromString(config, loader);
404                     }
405                     if (source != null) {
406                         for (final ConfigurationFactory factory : factories) {
407                             final String[] types = factory.getSupportedTypes();
408                             if (types != null) {
409                                 for (final String type : types) {
410                                     if (type.equals("*") || config.endsWith(type)) {
411                                         final Configuration c = factory.getConfiguration(source);
412                                         if (c != null) {
413                                             return c;
414                                         }
415                                     }
416                                 }
417                             }
418                         }
419                     }
420                 }
421             } else {
422                 for (final ConfigurationFactory factory : factories) {
423                     final String[] types = factory.getSupportedTypes();
424                     if (types != null) {
425                         for (final String type : types) {
426                             if (type.equals("*") || configLocation.toString().endsWith(type)) {
427                                 final Configuration config = factory.getConfiguration(name, configLocation);
428                                 if (config != null) {
429                                     return config;
430                                 }
431                             }
432                         }
433                     }
434                 }
435             }
436 
437             Configuration config = getConfiguration(true, name);
438             if (config == null) {
439                 config = getConfiguration(true, null);
440                 if (config == null) {
441                     config = getConfiguration(false, name);
442                     if (config == null) {
443                         config = getConfiguration(false, null);
444                     }
445                 }
446             }
447             if (config != null) {
448                 return config;
449             }
450             LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");
451             return new DefaultConfiguration();
452         }
453 
454         private Configuration getConfiguration(final boolean isTest, final String name) {
455             final boolean named = name != null && name.length() > 0;
456             final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
457             for (final ConfigurationFactory factory : factories) {
458                 String configName;
459                 final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
460                 final String [] types = factory.getSupportedTypes();
461                 if (types == null) {
462                     continue;
463                 }
464 
465                 for (final String suffix : types) {
466                     if (suffix.equals("*")) {
467                         continue;
468                     }
469                     configName = named ? prefix + name + suffix : prefix + suffix;
470 
471                     final ConfigurationSource source = getInputFromResource(configName, loader);
472                     if (source != null) {
473                         return factory.getConfiguration(source);
474                     }
475                 }
476             }
477             return null;
478         }
479 
480         @Override
481         public String[] getSupportedTypes() {
482             return null;
483         }
484 
485         @Override
486         public Configuration getConfiguration(final ConfigurationSource source) {
487             if (source != null) {
488                 final String config = source.getLocation();
489                 for (final ConfigurationFactory factory : factories) {
490                     final String[] types = factory.getSupportedTypes();
491                     if (types != null) {
492                         for (final String type : types) {
493                             if (type.equals("*") || config != null && config.endsWith(type)) {
494                                 final Configuration c = factory.getConfiguration(source);
495                                 if (c != null) {
496                                     LOGGER.debug("Loaded configuration from {}", source);
497                                     return c;
498                                 }
499                                 LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config);
500                                 return null;
501                             }
502                         }
503                     }
504                 }
505             }
506             LOGGER.error("Cannot process configuration, input source is null");
507             return null;
508         }
509     }
510 }