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