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