View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17  package org.apache.logging.log4j.core.config;
    18
    19  import java.io.File;
    20  import java.io.FileInputStream;
    21  import java.io.FileNotFoundException;
    22  import java.io.IOException;
    23  import java.io.InputStream;
    24  import java.net.MalformedURLException;
    25  import java.net.URI;
    26  import java.net.URISyntaxException;
    27  import java.net.URL;
    28  import java.util.ArrayList;
    29  import java.util.Collection;
    30  import java.util.Collections;
    31  import java.util.List;
    32  import java.util.Map;
    33  import java.util.concurrent.locks.Lock;
    34  import java.util.concurrent.locks.ReentrantLock;
    35
    36  import org.apache.logging.log4j.Level;
    37  import org.apache.logging.log4j.Logger;
    38  import org.apache.logging.log4j.core.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.util.FileUtils;
    43  import org.apache.logging.log4j.core.util.Loader;
    44  import org.apache.logging.log4j.core.util.ReflectionUtil;
    45  import org.apache.logging.log4j.status.StatusLogger;
    46  import org.apache.logging.log4j.util.LoaderUtil;
    47  import org.apache.logging.log4j.util.PropertiesUtil;
    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 the 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 {
    73      /**
    74       * Allow the ConfigurationFactory class to be specified as a system property.
    75       */
    76      public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory";
    77
    78      /**
    79       * Allow the location of the configuration file to be specified as a system property.
    80       */
    81      public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile";
    82
    83      /**
    84       * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.core.config.plugins.Plugin}
    85       * class.
    86       *
    87       * @since 2.1
    88       */
    89      public static final String CATEGORY = "ConfigurationFactory";
    90
    91      /**
    92       * Allow subclasses access to the status logger without creating another instance.
    93       */
    94      protected static final Logger LOGGER = StatusLogger.getLogger();
    95
    96      /**
    97       * File name prefix for test configurations.
    98       */
    99      protected static final String TEST_PREFIX = "log4j2-test";
    100
    101     /**
    102      * File name prefix for standard configurations.
    103      */
    104     protected static final String DEFAULT_PREFIX = "log4j2";
    105
    106     /**
    107      * The name of the classloader URI scheme.
    108      */
    109     private static final String CLASS_LOADER_SCHEME = "classloader";
    110
    111     /**
    112      * The name of the classpath URI scheme, synonymous with the classloader URI scheme.
    113      */
    114     private static final String CLASS_PATH_SCHEME = "classpath";
    115
    116     private static volatile List<ConfigurationFactory> factories = null;
    117
    118     private static ConfigurationFactory configFactory = new Factory();
    119
    120     protected final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator());
    121
    122     private static final Lock LOCK = new ReentrantLock();
    123
    124     /**
    125      * Returns the ConfigurationFactory.
    126      * @return the ConfigurationFactory.
    127      */
    128     public static ConfigurationFactory getInstance() {
    129         // volatile works in Java 1.6+, so double-checked locking also works properly
    130         //noinspection DoubleCheckedLocking
    131         if (factories == null) {
    132             LOCK.lock();
    133             try {
    134                 if (factories == null) {
    135                     final List<ConfigurationFactory> list = new ArrayList<ConfigurationFactory>();
    136                     final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
    137                     if (factoryClass != null) {
    138                         addFactory(list, factoryClass);
    139                     }
    140                     final PluginManager manager = new PluginManager(CATEGORY);
    141                     manager.collectPlugins();
    142                     final Map<String, PluginType<?>> plugins = manager.getPlugins();
    143                     final List<Class<? extends ConfigurationFactory>> ordered =
    144                         new ArrayList<Class<? extends ConfigurationFactory>>(plugins.size());
    145                     for (final PluginType<?> type : plugins.values()) {
    146                         try {
    147                             ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class));
    148                         } catch (final Exception ex) {
    149                             LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex);
    150                         }
    151                     }
    152                     Collections.sort(ordered, OrderComparator.getInstance());
    153                     for (final Class<? extends ConfigurationFactory> clazz : ordered) {
    154                         addFactory(list, clazz);
    155                     }
    156                     // see above comments about double-checked locking
    157                     //noinspection NonThreadSafeLazyInitialization
    158                     factories = Collections.unmodifiableList(list);
    159                 }
    160             } finally {
    161                 LOCK.unlock();
    162             }
    163         }
    164
    165         LOGGER.debug("Using configurationFactory {}", configFactory);
    166         return configFactory;
    167     }
    168
    169     private static void addFactory(final Collection<ConfigurationFactory> list, final String factoryClass) {
    170         try {
    171             addFactory(list, LoaderUtil.loadClass(factoryClass).asSubclass(ConfigurationFactory.class));
    172         } catch (final Exception ex) {
    173             LOGGER.error("Unable to load class {}", factoryClass, ex);
    174         }
    175     }
    176
    177     private static void addFactory(final Collection<ConfigurationFactory> list,
    178                                    final Class<? extends ConfigurationFactory> factoryClass) {
    179         try {
    180             list.add(ReflectionUtil.instantiate(factoryClass));
    181         } catch (final Exception ex) {
    182             LOGGER.error("Unable to create instance of {}", factoryClass.getName(), ex);
    183         }
    184     }
    185
    186     /**
    187      * Set the configuration factory. This method is not intended for general use and may not be thread safe.
    188      * @param factory the ConfigurationFactory.
    189      */
    190     public static void setConfigurationFactory(final ConfigurationFactory factory) {
    191         configFactory = factory;
    192     }
    193
    194     /**
    195      * Reset the ConfigurationFactory to the default. This method is not intended for general use and may
    196      * not be thread safe.
    197      */
    198     public static void resetConfigurationFactory() {
    199         configFactory = new Factory();
    200     }
    201
    202     /**
    203      * Remove the ConfigurationFactory. This method is not intended for general use and may not be thread safe.
    204      * @param factory The factory to remove.
    205      */
    206     public static void removeConfigurationFactory(final ConfigurationFactory factory) {
    207         if (configFactory == factory) {
    208             configFactory = new Factory();
    209         }
    210     }
    211
    212     protected abstract String[] getSupportedTypes();
    213
    214     protected boolean isActive() {
    215         return true;
    216     }
    217
    218     public abstract Configuration getConfiguration(ConfigurationSource source);
    219
    220     /**
    221      * Returns the Configuration.
    222      * @param name The configuration name.
    223      * @param configLocation The configuration location.
    224      * @return The Configuration.
    225      */
    226     public Configuration getConfiguration(final String name, final URI configLocation) {
    227         if (!isActive()) {
    228             return null;
    229         }
    230         if (configLocation != null) {
    231             final ConfigurationSource source = getInputFromUri(configLocation);
    232             if (source != null) {
    233                 return getConfiguration(source);
    234             }
    235         }
    236         return null;
    237     }
    238
    239     /**
    240      * Returns the Configuration obtained using a given ClassLoader.
    241      *
    242      * @param name The configuration name.
    243      * @param configLocation A URI representing the location of the configuration.
    244      * @param loader The default ClassLoader to use. If this is {@code null}, then the
    245      *               {@linkplain LoaderUtil#getThreadContextClassLoader() default ClassLoader} will be used.
    246      * @return The Configuration.
    247      * @since 2.1
    248      */
    249     public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) {
    250         if (!isActive()) {
    251             return null;
    252         }
    253         if (loader == null) {
    254             return getConfiguration(name, configLocation);
    255         }
    256         if (isClassLoaderUri(configLocation)) {
    257             final String path = extractClassLoaderUriPath(configLocation);
    258             final ConfigurationSource source = getInputFromResource(path, loader);
    259             if (source != null) {
    260                 final Configuration configuration = getConfiguration(source);
    261                 if (configuration != null) {
    262                     return configuration;
    263                 }
    264             }
    265         }
    266         return getConfiguration(name, configLocation);
    267     }
    268
    269     /**
    270      * Load the configuration from a URI.
    271      * @param configLocation A URI representing the location of the configuration.
    272      * @return The ConfigurationSource for the configuration.
    273      */
    274     protected ConfigurationSource getInputFromUri(final URI configLocation) {
    275         final File configFile = FileUtils.fileFromUri(configLocation);
    276         if (configFile != null && configFile.exists() && configFile.canRead()) {
    277             try {
    278                 return new ConfigurationSource(new FileInputStream(configFile), configFile);
    279             } catch (final FileNotFoundException ex) {
    280                 LOGGER.error("Cannot locate file {}", configLocation.getPath(), ex);
    281             }
    282         }
    283         if (isClassLoaderUri(configLocation)) {
    284             final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
    285             final String path = extractClassLoaderUriPath(configLocation);
    286             final ConfigurationSource source = getInputFromResource(path, loader);
    287             if (source != null) {
    288                 return source;
    289             }
    290         }
    291         if (!configLocation.isAbsolute()) { // LOG4J2-704 avoid confusing error message thrown by uri.toURL()
    292             LOGGER.error("File not found in file system or classpath: {}", configLocation.toString());
    293             return null;
    294         }
    295         try {
    296             return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL());
    297         } catch (final MalformedURLException ex) {
    298             LOGGER.error("Invalid URL {}", configLocation.toString(), ex);
    299         } catch (final Exception ex) {
    300             LOGGER.error("Unable to access {}", configLocation.toString(), ex);
    301         }
    302         return null;
    303     }
    304
    305     private static boolean isClassLoaderUri(final URI uri) {
    306         if (uri == null) {
    307             return false;
    308         }
    309         final String scheme = uri.getScheme();
    310         return scheme == null || scheme.equals(CLASS_LOADER_SCHEME) || scheme.equals(CLASS_PATH_SCHEME);
    311     }
    312
    313     private static String extractClassLoaderUriPath(final URI uri) {
    314         return uri.getScheme() == null ? uri.getPath() : uri.getSchemeSpecificPart();
    315     }
    316
    317     /**
    318      * Load the configuration from the location represented by the String.
    319      * @param config The configuration location.
    320      * @param loader The default ClassLoader to use.
    321      * @return The InputSource to use to read the configuration.
    322      */
    323     protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) {
    324         try {
    325             final URL url = new URL(config);
    326             return new ConfigurationSource(url.openStream(), FileUtils.fileFromUri(url.toURI()));
    327         } catch (final Exception ex) {
    328             final ConfigurationSource source = getInputFromResource(config, loader);
    329             if (source == null) {
    330                 try {
    331                     final File file = new File(config);
    332                     return new ConfigurationSource(new FileInputStream(file), file);
    333                 } catch (final FileNotFoundException fnfe) {
    334                     // Ignore the exception
    335                     LOGGER.catching(Level.DEBUG, fnfe);
    336                 }
    337             }
    338             return source;
    339         }
    340     }
    341
    342     /**
    343      * Retrieve the configuration via the ClassLoader.
    344      * @param resource The resource to load.
    345      * @param loader The default ClassLoader to use.
    346      * @return The ConfigurationSource for the configuration.
    347      */
    348     protected ConfigurationSource getInputFromResource(final String resource, final ClassLoader loader) {
    349         final URL url = Loader.getResource(resource, loader);
    350         if (url == null) {
    351             return null;
    352         }
    353         InputStream is = null;
    354         try {
    355             is = url.openStream();
    356         } catch (final IOException ioe) {
    357             LOGGER.catching(Level.DEBUG, ioe);
    358             return null;
    359         }
    360         if (is == null) {
    361             return null;
    362         }
    363
    364         if (FileUtils.isFile(url)) {
    365             try {
    366                 return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI()));
    367             } catch (final URISyntaxException ex) {
    368                 // Just ignore the exception.
    369                 LOGGER.catching(Level.DEBUG, ex);
    370             }
    371         }
    372         return new ConfigurationSource(is, url);
    373     }
    374
    375     /**
    376      * Default Factory.
    377      */
    378     private static class Factory extends ConfigurationFactory {
    379
    380         /**
    381          * Default Factory Constructor.
    382          * @param name The configuration name.
    383          * @param configLocation The configuration location.
    384          * @return The Configuration.
    385          */
    386         @Override
    387         public Configuration getConfiguration(final String name, final URI configLocation) {
    388
    389             if (configLocation == null) {
    390                 final String config = this.substitutor.replace(
    391                     PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY));
    392                 if (config != null) {
    393                     ConfigurationSource source = null;
    394                     try {
    395                         source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config));
    396                     } catch (final Exception ex) {
    397                         // Ignore the error and try as a String.
    398                         LOGGER.catching(Level.DEBUG, ex);
    399                     }
    400                     if (source == null) {
    401                         final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
    402                         source = getInputFromString(config, loader);
    403                     }
    404                     if (source != null) {
    405                         for (final ConfigurationFactory factory : factories) {
    406                             final String[] types = factory.getSupportedTypes();
    407                             if (types != null) {
    408                                 for (final String type : types) {
    409                                     if (type.equals("*") || config.endsWith(type)) {
    410                                         final Configuration c = factory.getConfiguration(source);
    411                                         if (c != null) {
    412                                             return c;
    413                                         }
    414                                     }
    415                                 }
    416                             }
    417                         }
    418                     }
    419                 }
    420             } else {
    421                 for (final ConfigurationFactory factory : factories) {
    422                     final String[] types = factory.getSupportedTypes();
    423                     if (types != null) {
    424                         for (final String type : types) {
    425                             if (type.equals("*") || configLocation.toString().endsWith(type)) {
    426                                 final Configuration config = factory.getConfiguration(name, configLocation);
    427                                 if (config != null) {
    428                                     return config;
    429                                 }
    430                             }
    431                         }
    432                     }
    433                 }
    434             }
    435
    436             Configuration config = getConfiguration(true, name);
    437             if (config == null) {
    438                 config = getConfiguration(true, null);
    439                 if (config == null) {
    440                     config = getConfiguration(false, name);
    441                     if (config == null) {
    442                         config = getConfiguration(false, null);
    443                     }
    444                 }
    445             }
    446             if (config != null) {
    447                 return config;
    448             }
    449             LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");
    450             return new DefaultConfiguration();
    451         }
    452
    453         private Configuration getConfiguration(final boolean isTest, final String name) {
    454             final boolean named = name != null && name.length() > 0;
    455             final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
    456             for (final ConfigurationFactory factory : factories) {
    457                 String configName;
    458                 final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
    459                 final String [] types = factory.getSupportedTypes();
    460                 if (types == null) {
    461                     continue;
    462                 }
    463
    464                 for (final String suffix : types) {
    465                     if (suffix.equals("*")) {
    466                         continue;
    467                     }
    468                     configName = named ? prefix + name + suffix : prefix + suffix;
    469
    470                     final ConfigurationSource source = getInputFromResource(configName, loader);
    471                     if (source != null) {
    472                         return factory.getConfiguration(source);
    473                     }
    474                 }
    475             }
    476             return null;
    477         }
    478
    479         @Override
    480         public String[] getSupportedTypes() {
    481             return null;
    482         }
    483
    484         @Override
    485         public Configuration getConfiguration(final ConfigurationSource source) {
    486             if (source != null) {
    487                 final String config = source.getLocation();
    488                 for (final ConfigurationFactory factory : factories) {
    489                     final String[] types = factory.getSupportedTypes();
    490                     if (types != null) {
    491                         for (final String type : types) {
    492                             if (type.equals("*") || config != null && config.endsWith(type)) {
    493                                 final Configuration c = factory.getConfiguration(source);
    494                                 if (c != null) {
    495                                     LOGGER.debug("Loaded configuration from {}", source);
    496                                     return c;
    497                                 }
    498                                 LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config);
    499                                 return null;
    500                             }
    501                         }
    502                     }
    503                 }
    504             }
    505             LOGGER.error("Cannot process configuration, input source is null");
    506             return null;
    507         }
    508     }
    509 }