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