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