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