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