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