| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache license, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the license for the specific language governing permissions and |
| * limitations under the license. |
| */ |
| package org.apache.logging.log4j.core.config; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.UnsupportedEncodingException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.net.URLDecoder; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.locks.Lock; |
| import java.util.concurrent.locks.ReentrantLock; |
| |
| import org.apache.logging.log4j.Level; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.logging.log4j.core.LoggerContext; |
| import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; |
| import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; |
| import org.apache.logging.log4j.core.lookup.Interpolator; |
| import org.apache.logging.log4j.core.lookup.StrSubstitutor; |
| import org.apache.logging.log4j.core.net.UrlConnectionFactory; |
| import org.apache.logging.log4j.core.util.AuthorizationProvider; |
| import org.apache.logging.log4j.core.util.BasicAuthorizationProvider; |
| import org.apache.logging.log4j.core.util.FileUtils; |
| import org.apache.logging.log4j.core.util.Loader; |
| import org.apache.logging.log4j.core.util.NetUtils; |
| import org.apache.logging.log4j.plugins.util.PluginManager; |
| import org.apache.logging.log4j.plugins.util.PluginType; |
| import org.apache.logging.log4j.status.StatusLogger; |
| import org.apache.logging.log4j.util.LoaderUtil; |
| import org.apache.logging.log4j.util.PropertiesUtil; |
| import org.apache.logging.log4j.util.ReflectionUtil; |
| import org.apache.logging.log4j.util.Strings; |
| |
| /** |
| * Factory class for parsed {@link Configuration} objects from a configuration file. |
| * ConfigurationFactory allows the configuration implementation to be |
| * dynamically chosen in 1 of 3 ways: |
| * <ol> |
| * <li>A system property named "log4j.configurationFactory" can be set with the |
| * name of the ConfigurationFactory to be used.</li> |
| * <li> |
| * {@linkplain #setConfigurationFactory(ConfigurationFactory)} can be called |
| * with the instance of the ConfigurationFactory to be used. This must be called |
| * before any other calls to Log4j.</li> |
| * <li> |
| * A ConfigurationFactory implementation can be added to the classpath and configured as a plugin in the |
| * {@link #CATEGORY ConfigurationFactory} category. The {@link Order} annotation should be used to configure the |
| * factory to be the first one inspected. See |
| * {@linkplain org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory} for an example.</li> |
| * </ol> |
| * |
| * If the ConfigurationFactory that was added returns null on a call to |
| * getConfiguration then any other ConfigurationFactories found as plugins will |
| * be called in their respective order. DefaultConfiguration is always called |
| * last if no configuration has been returned. |
| */ |
| public abstract class ConfigurationFactory extends ConfigurationBuilderFactory { |
| |
| public ConfigurationFactory() { |
| super(); |
| // TEMP For breakpoints |
| } |
| |
| /** |
| * Allows the ConfigurationFactory class to be specified as a system property. |
| */ |
| public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory"; |
| |
| /** |
| * Allows the location of the configuration file to be specified as a system property. |
| */ |
| public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile"; |
| |
| public static final String LOG4J1_CONFIGURATION_FILE_PROPERTY = "log4j.configuration"; |
| |
| public static final String LOG4J1_EXPERIMENTAL = "log4j1.compatibility"; |
| |
| public static final String AUTHORIZATION_PROVIDER = "log4j2.authorizationProvider"; |
| |
| /** |
| * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.plugins.Plugin} |
| * class. |
| * |
| * @since 2.1 |
| */ |
| public static final String CATEGORY = "ConfigurationFactory"; |
| |
| /** |
| * Allows subclasses access to the status logger without creating another instance. |
| */ |
| protected static final Logger LOGGER = StatusLogger.getLogger(); |
| |
| /** |
| * File name prefix for test configurations. |
| */ |
| protected static final String TEST_PREFIX = "log4j2-test"; |
| |
| /** |
| * File name prefix for standard configurations. |
| */ |
| protected static final String DEFAULT_PREFIX = "log4j2"; |
| |
| protected static final String LOG4J1_VERSION = "1"; |
| protected static final String LOG4J2_VERSION = "2"; |
| |
| /** |
| * The name of the classloader URI scheme. |
| */ |
| private static final String CLASS_LOADER_SCHEME = "classloader"; |
| |
| /** |
| * The name of the classpath URI scheme, synonymous with the classloader URI scheme. |
| */ |
| private static final String CLASS_PATH_SCHEME = "classpath"; |
| |
| private static final String OVERRIDE_PARAM = "override"; |
| |
| private static volatile List<ConfigurationFactory> factories; |
| |
| private static ConfigurationFactory configFactory = new Factory(); |
| |
| protected final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator()); |
| |
| private static final Lock LOCK = new ReentrantLock(); |
| |
| private static final String HTTPS = "https"; |
| private static final String HTTP = "http"; |
| |
| private static volatile AuthorizationProvider authorizationProvider; |
| |
| /** |
| * Returns the ConfigurationFactory. |
| * @return the ConfigurationFactory. |
| */ |
| public static ConfigurationFactory getInstance() { |
| // volatile works in Java 1.6+, so double-checked locking also works properly |
| //noinspection DoubleCheckedLocking |
| if (factories == null) { |
| LOCK.lock(); |
| try { |
| if (factories == null) { |
| final List<ConfigurationFactory> list = new ArrayList<>(); |
| PropertiesUtil props = PropertiesUtil.getProperties(); |
| final String factoryClass = props.getStringProperty(CONFIGURATION_FACTORY_PROPERTY); |
| if (factoryClass != null) { |
| addFactory(list, factoryClass); |
| } |
| final PluginManager manager = new PluginManager(CATEGORY); |
| manager.collectPlugins(); |
| final Map<String, PluginType<?>> plugins = manager.getPlugins(); |
| final List<Class<? extends ConfigurationFactory>> ordered = new ArrayList<>(plugins.size()); |
| for (final PluginType<?> type : plugins.values()) { |
| try { |
| ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class)); |
| } catch (final Exception ex) { |
| LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex); |
| } |
| } |
| Collections.sort(ordered, OrderComparator.getInstance()); |
| for (final Class<? extends ConfigurationFactory> clazz : ordered) { |
| addFactory(list, clazz); |
| } |
| // see above comments about double-checked locking |
| //noinspection NonThreadSafeLazyInitialization |
| factories = Collections.unmodifiableList(list); |
| authorizationProvider = authorizationProvider(props); |
| } |
| } finally { |
| LOCK.unlock(); |
| } |
| } |
| |
| LOGGER.debug("Using configurationFactory {}", configFactory); |
| return configFactory; |
| } |
| |
| public static AuthorizationProvider authorizationProvider(PropertiesUtil props) { |
| final String authClass = props.getStringProperty(AUTHORIZATION_PROVIDER); |
| AuthorizationProvider provider = null; |
| if (authClass != null) { |
| try { |
| Object obj = LoaderUtil.newInstanceOf(authClass); |
| if (obj instanceof AuthorizationProvider) { |
| provider = (AuthorizationProvider) obj; |
| } else { |
| LOGGER.warn("{} is not an AuthorizationProvider, using default", obj.getClass().getName()); |
| } |
| } catch (Exception ex) { |
| LOGGER.warn("Unable to create {}, using default: {}", authClass, ex.getMessage()); |
| } |
| } |
| if (provider == null) { |
| provider = new BasicAuthorizationProvider(props); |
| } |
| return provider; |
| } |
| |
| public static AuthorizationProvider getAuthorizationProvider() { |
| return authorizationProvider; |
| } |
| |
| private static void addFactory(final Collection<ConfigurationFactory> list, final String factoryClass) { |
| try { |
| addFactory(list, Loader.loadClass(factoryClass).asSubclass(ConfigurationFactory.class)); |
| } catch (final Exception ex) { |
| LOGGER.error("Unable to load class {}", factoryClass, ex); |
| } |
| } |
| |
| private static void addFactory(final Collection<ConfigurationFactory> list, |
| final Class<? extends ConfigurationFactory> factoryClass) { |
| try { |
| list.add(ReflectionUtil.instantiate(factoryClass)); |
| } catch (final Exception ex) { |
| LOGGER.error("Unable to create instance of {}", factoryClass.getName(), ex); |
| } |
| } |
| |
| /** |
| * Sets the configuration factory. This method is not intended for general use and may not be thread safe. |
| * @param factory the ConfigurationFactory. |
| */ |
| public static void setConfigurationFactory(final ConfigurationFactory factory) { |
| configFactory = factory; |
| } |
| |
| /** |
| * Resets the ConfigurationFactory to the default. This method is not intended for general use and may |
| * not be thread safe. |
| */ |
| public static void resetConfigurationFactory() { |
| configFactory = new Factory(); |
| } |
| |
| /** |
| * Removes the ConfigurationFactory. This method is not intended for general use and may not be thread safe. |
| * @param factory The factory to remove. |
| */ |
| public static void removeConfigurationFactory(final ConfigurationFactory factory) { |
| if (configFactory == factory) { |
| configFactory = new Factory(); |
| } |
| } |
| |
| protected abstract String[] getSupportedTypes(); |
| |
| protected String getTestPrefix() { |
| return TEST_PREFIX; |
| } |
| |
| protected String getDefaultPrefix() { |
| return DEFAULT_PREFIX; |
| } |
| |
| protected String getVersion() { |
| return LOG4J2_VERSION; |
| } |
| |
| protected boolean isActive() { |
| return true; |
| } |
| |
| public abstract Configuration getConfiguration(final LoggerContext loggerContext, ConfigurationSource source); |
| |
| /** |
| * Returns the Configuration. |
| * @param loggerContext The logger context |
| * @param name The configuration name. |
| * @param configLocation The configuration location. |
| * @return The Configuration. |
| */ |
| public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) { |
| if (!isActive()) { |
| return null; |
| } |
| if (configLocation != null) { |
| final ConfigurationSource source = ConfigurationSource.fromUri(configLocation); |
| if (source != null) { |
| return getConfiguration(loggerContext, source); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the Configuration obtained using a given ClassLoader. |
| * @param loggerContext The logger context |
| * @param name The configuration name. |
| * @param configLocation A URI representing the location of the configuration. |
| * @param loader The default ClassLoader to use. If this is {@code null}, then the |
| * {@linkplain LoaderUtil#getThreadContextClassLoader() default ClassLoader} will be used. |
| * |
| * @return The Configuration. |
| */ |
| public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation, final ClassLoader loader) { |
| if (!isActive()) { |
| return null; |
| } |
| if (loader == null) { |
| return getConfiguration(loggerContext, name, configLocation); |
| } |
| if (isClassLoaderUri(configLocation)) { |
| final String path = extractClassLoaderUriPath(configLocation); |
| final ConfigurationSource source = ConfigurationSource.fromResource(path, loader); |
| if (source != null) { |
| final Configuration configuration = getConfiguration(loggerContext, source); |
| if (configuration != null) { |
| return configuration; |
| } |
| } |
| } |
| return getConfiguration(loggerContext, name, configLocation); |
| } |
| |
| static boolean isClassLoaderUri(final URI uri) { |
| if (uri == null) { |
| return false; |
| } |
| final String scheme = uri.getScheme(); |
| return scheme == null || scheme.equals(CLASS_LOADER_SCHEME) || scheme.equals(CLASS_PATH_SCHEME); |
| } |
| |
| static String extractClassLoaderUriPath(final URI uri) { |
| return uri.getScheme() == null ? uri.getPath() : uri.getSchemeSpecificPart(); |
| } |
| |
| /** |
| * Loads the configuration from the location represented by the String. |
| * @param config The configuration location. |
| * @param loader The default ClassLoader to use. |
| * @return The InputSource to use to read the configuration. |
| */ |
| protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) { |
| try { |
| final URL url = new URL(config); |
| URLConnection urlConnection = UrlConnectionFactory.createConnection(url); |
| File file = FileUtils.fileFromUri(url.toURI()); |
| if (file != null) { |
| return new ConfigurationSource(urlConnection.getInputStream(), FileUtils.fileFromUri(url.toURI())); |
| } else { |
| return new ConfigurationSource(urlConnection.getInputStream(), url, urlConnection.getLastModified()); |
| } |
| } catch (final Exception ex) { |
| final ConfigurationSource source = ConfigurationSource.fromResource(config, loader); |
| if (source == null) { |
| try { |
| final File file = new File(config); |
| return new ConfigurationSource(new FileInputStream(file), file); |
| } catch (final FileNotFoundException fnfe) { |
| // Ignore the exception |
| LOGGER.catching(Level.DEBUG, fnfe); |
| } |
| } |
| return source; |
| } |
| } |
| |
| /** |
| * Default Factory. |
| */ |
| private static class Factory extends ConfigurationFactory { |
| |
| private static final String ALL_TYPES = "*"; |
| |
| /** |
| * Default Factory Constructor. |
| * @param name The configuration name. |
| * @param configLocation The configuration location. |
| * @return The Configuration. |
| */ |
| @Override |
| public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) { |
| |
| if (configLocation == null) { |
| final String configLocationStr = this.substitutor.replace(PropertiesUtil.getProperties() |
| .getStringProperty(CONFIGURATION_FILE_PROPERTY)); |
| if (configLocationStr != null) { |
| String[] sources = parseConfigLocations(configLocationStr); |
| if (sources.length > 1) { |
| final List<AbstractConfiguration> configs = new ArrayList<>(); |
| for (final String sourceLocation : sources) { |
| final Configuration config = getConfiguration(loggerContext, sourceLocation.trim()); |
| if (config != null && config instanceof AbstractConfiguration) { |
| configs.add((AbstractConfiguration) config); |
| } else { |
| LOGGER.error("Failed to created configuration at {}", sourceLocation); |
| return null; |
| } |
| } |
| return new CompositeConfiguration(configs); |
| } |
| return getConfiguration(loggerContext, configLocationStr); |
| } else { |
| final String log4j1ConfigStr = this.substitutor.replace(PropertiesUtil.getProperties() |
| .getStringProperty(LOG4J1_CONFIGURATION_FILE_PROPERTY)); |
| if (log4j1ConfigStr != null) { |
| System.setProperty(LOG4J1_EXPERIMENTAL, "true"); |
| return getConfiguration(LOG4J1_VERSION, loggerContext, log4j1ConfigStr); |
| } |
| } |
| for (final ConfigurationFactory factory : getFactories()) { |
| final String[] types = factory.getSupportedTypes(); |
| if (types != null) { |
| for (final String type : types) { |
| if (type.equals(ALL_TYPES)) { |
| final Configuration config = factory.getConfiguration(loggerContext, name, configLocation); |
| if (config != null) { |
| return config; |
| } |
| } |
| } |
| } |
| } |
| } else { |
| // configLocation != null |
| String[] sources = parseConfigLocations(configLocation); |
| if (sources.length > 1) { |
| final List<AbstractConfiguration> configs = new ArrayList<>(); |
| for (final String sourceLocation : sources) { |
| final Configuration config = getConfiguration(loggerContext, sourceLocation.trim()); |
| if (config instanceof AbstractConfiguration) { |
| configs.add((AbstractConfiguration) config); |
| } else { |
| LOGGER.error("Failed to created configuration at {}", sourceLocation); |
| return null; |
| } |
| } |
| return new CompositeConfiguration(configs); |
| } |
| final String configLocationStr = configLocation.toString(); |
| for (final ConfigurationFactory factory : getFactories()) { |
| final String[] types = factory.getSupportedTypes(); |
| if (types != null) { |
| for (final String type : types) { |
| if (type.equals(ALL_TYPES) || configLocationStr.endsWith(type)) { |
| final Configuration config = factory.getConfiguration(loggerContext, name, configLocation); |
| if (config != null) { |
| return config; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| Configuration config = getConfiguration(loggerContext, true, name); |
| if (config == null) { |
| config = getConfiguration(loggerContext, true, null); |
| if (config == null) { |
| config = getConfiguration(loggerContext, false, name); |
| if (config == null) { |
| config = getConfiguration(loggerContext, false, null); |
| } |
| } |
| } |
| if (config != null) { |
| return config; |
| } |
| LOGGER.warn("No Log4j 2 configuration file found. " + |
| "Using default configuration (logging only errors to the console), " + |
| "or user programmatically provided configurations. " + |
| "Set system property 'log4j2.debug' " + |
| "to show Log4j 2 internal initialization logging. " + |
| "See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2"); |
| return new DefaultConfiguration(); |
| } |
| |
| private Configuration getConfiguration(final LoggerContext loggerContext, final String configLocationStr) { |
| return getConfiguration(null, loggerContext, configLocationStr); |
| } |
| |
| private Configuration getConfiguration(String requiredVersion, final LoggerContext loggerContext, |
| final String configLocationStr) { |
| ConfigurationSource source = null; |
| try { |
| source = ConfigurationSource.fromUri(NetUtils.toURI(configLocationStr)); |
| } catch (final Exception ex) { |
| // Ignore the error and try as a String. |
| LOGGER.catching(Level.DEBUG, ex); |
| } |
| if (source == null) { |
| final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); |
| source = getInputFromString(configLocationStr, loader); |
| } |
| if (source != null) { |
| for (final ConfigurationFactory factory : getFactories()) { |
| if (requiredVersion != null && !factory.getVersion().equals(requiredVersion)) { |
| continue; |
| } |
| final String[] types = factory.getSupportedTypes(); |
| if (types != null) { |
| for (final String type : types) { |
| if (type.equals(ALL_TYPES) || configLocationStr.endsWith(type)) { |
| final Configuration config = factory.getConfiguration(loggerContext, source); |
| if (config != null) { |
| return config; |
| } |
| } |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| private Configuration getConfiguration(final LoggerContext loggerContext, final boolean isTest, final String name) { |
| final boolean named = Strings.isNotEmpty(name); |
| final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); |
| for (final ConfigurationFactory factory : getFactories()) { |
| String configName; |
| final String prefix = isTest ? factory.getTestPrefix() : factory.getDefaultPrefix(); |
| final String [] types = factory.getSupportedTypes(); |
| if (types == null) { |
| continue; |
| } |
| |
| for (final String suffix : types) { |
| if (suffix.equals(ALL_TYPES)) { |
| continue; |
| } |
| configName = named ? prefix + name + suffix : prefix + suffix; |
| |
| final ConfigurationSource source = ConfigurationSource.fromResource(configName, loader); |
| if (source != null) { |
| if (!factory.isActive()) { |
| LOGGER.warn("Found configuration file {} for inactive ConfigurationFactory {}", configName, factory.getClass().getName()); |
| } |
| return factory.getConfiguration(loggerContext, source); |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public String[] getSupportedTypes() { |
| return null; |
| } |
| |
| @Override |
| public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) { |
| if (source != null) { |
| final String config = source.getLocation(); |
| for (final ConfigurationFactory factory : getFactories()) { |
| final String[] types = factory.getSupportedTypes(); |
| if (types != null) { |
| for (final String type : types) { |
| if (type.equals(ALL_TYPES) || config != null && config.endsWith(type)) { |
| final Configuration c = factory.getConfiguration(loggerContext, source); |
| if (c != null) { |
| LOGGER.debug("Loaded configuration from {}", source); |
| return c; |
| } |
| LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config); |
| return null; |
| } |
| } |
| } |
| } |
| } |
| LOGGER.error("Cannot process configuration, input source is null"); |
| return null; |
| } |
| |
| private String[] parseConfigLocations(URI configLocations) { |
| final String[] uris = configLocations.toString().split("\\?"); |
| final List<String> locations = new ArrayList<>(); |
| if (uris.length > 1) { |
| locations.add(uris[0]); |
| final String[] pairs = configLocations.getQuery().split("&"); |
| for (String pair : pairs) { |
| final int idx = pair.indexOf("="); |
| try { |
| final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair; |
| if (key.equalsIgnoreCase(OVERRIDE_PARAM)) { |
| locations.add(URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); |
| } |
| } catch (UnsupportedEncodingException ex) { |
| LOGGER.warn("Invalid query parameter in {}", configLocations); |
| } |
| } |
| return locations.toArray(new String[0]); |
| } |
| return new String[] {uris[0]}; |
| } |
| |
| private String[] parseConfigLocations(String configLocations) { |
| final String[] uris = configLocations.split(","); |
| if (uris.length > 1) { |
| return uris; |
| } |
| try { |
| return parseConfigLocations(new URI(configLocations)); |
| } catch (URISyntaxException ex) { |
| LOGGER.warn("Error parsing URI {}", configLocations); |
| } |
| return new String[] {configLocations}; |
| } |
| } |
| |
| static List<ConfigurationFactory> getFactories() { |
| return factories; |
| } |
| } |