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