log4j2如何根据配置的配置文件选取配置文件处理类的(ConfigurationFactory源码分析)

时间:2021-07-26 21:50:40

从上一篇(slf4j绑定log4j2日志系统的过程(源码分析)的reconfigure()方法分析

一、环境

log4j-core-2.2.jar


二、背景知识

我们知道log4j2(2.2版本)支持的配置文件类型有以下几种:

XML、JSON、YAML

当然配置的实现由多种方式:官方文档中有介绍

Configuration of Log4j 2 can be accomplished in 1 of 4 ways:
1. Through a configuration file written in XML, JSON, YAML, or properties format.
2. Programmatically, by creating a ConfigurationFactory and Configuration implementation.
3. Programmatically, by calling the APIs exposed in the Configuration interface to add components
to the default configuration.
4. Programmatically, by calling methods on the internal Logger class.

Log4j has the ability to automatically configure itself during initialization. When Log4j starts it
will locate all the ConfigurationFactory plugins and arrange them in weighted order from highest to
lowest. As delivered, Log4j contains four ConfigurationFactory implementations: one for JSON, one
for YAML, one for properties, and one for XML.

在初始化阶段Log4j2有自动完成配置的能力,其中上述提到的四种ConfigurationFactory,for properties在本版本并不支持(但是从2.4版本开始支持,并且最新的2.7版本也支持)。上一篇也说到如果没有配置配置文件,则采用系统默认的配置DefaultConfiguration。

Log4j will provide a default configuration if it cannot locate a configuration file. The default
configuration, provided in the DefaultConfiguration class, will set up:
• AConsoleAppenderattached to the root logger.
• APatternLayoutset to the pattern "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg
%n" attached to the ConsoleAppender
Note that by default Log4j assigns the root logger toLevel.ERROR.


三、源码分析

LoggerContext.java
<pre class="java" name="code">/**
     * Reconfigure the context.
     */
    public synchronized void reconfigure() {
        final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
        LOGGER.debug("Reconfiguration started for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
            configLocation, this, cl);
        final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation, cl);
        setConfiguration(instance);
        /*
         * instance.start(); Configuration old = setConfiguration(instance);
         * updateLoggers(); if (old != null) { old.stop(); }
         */

        LOGGER.debug("Reconfiguration complete for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
            configLocation, this, cl);
    }

 
 
</pre><span style="color:#000000;font-family:; font-size: 11pt; font-style: normal; font-variant: normal;"></span><pre class="java" code_snippet_id="1937793" snippet_file_name="blog_20161019_4_7185684" name="code">

1. 首先看getInstance()

**
     * 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<ConfigurationFactory>();
                    final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
                    if (factoryClass != null) {
                        addFactory(list, factoryClass);
                    }
                    final PluginManager manager = new PluginManager(CATEGORY);
                    manager.collectPlugins();   //获取所有以@Plugin注解的类
                    final Map<String, PluginType<?>> plugins = manager.getPlugins();
                    final List<Class<? extends ConfigurationFactory>> ordered =
                        new ArrayList<Class<? extends ConfigurationFactory>>(plugins.size());
                    for (final PluginType<?> type : plugins.values()) {
                        try {//将继承ConfigurationFactory的@Plugin注解的类添加
                            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());  //按照order排序
                    for (final Class<? extends ConfigurationFactory> clazz : ordered) {
                        addFactory(list, clazz);
                    }
                    // see above comments about double-checked locking
                    //noinspection NonThreadSafeLazyInitialization
                    factories = Collections.unmodifiableList(list);   //factories无法修改的list,final修饰
                }
            } finally {
                LOCK.unlock();
            }
        }

        LOGGER.debug("Using configurationFactory {}", configFactory);
        return configFactory;
    }

该方法有两个作用,获取所有的ConfigurationFactory添加到factories中(包括:YamlConfigurationFactory、JsonConfigurationFactory、XmlConfigurationFactory),以及返回configFactory为Factory

private static ConfigurationFactory configFactory = new Factory();

2.看获取ConfigurationFactory的过程

log4j2是支持plugin方式的,以@Plugin方式定义

**
     * Locates all the plugins including search of specific packages. Warns about name collisions.
     *
     * @param packages the list of packages to scan for plugins
     * @since 2.1
     */
    public void collectPlugins(final List<String> packages) {
        final String categoryLowerCase = category.toLowerCase();
        final Map<String, PluginType<?>> newPlugins = new LinkedHashMap<String, PluginType<?>>();

        // First, iterate the Log4j2Plugin.dat files found in the main CLASSPATH
        Map<String, List<PluginType<?>>> builtInPlugins = PluginRegistry.getInstance().loadFromMainClassLoader();
        if (builtInPlugins.isEmpty()) {
            // If we didn't find any plugins above, someone must have messed with the log4j-core.jar.
            // Search the standard package in the hopes we can find our core plugins.
            builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES);
        }
        mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase));

        // Next, iterate any Log4j2Plugin.dat files from OSGi Bundles
        for (final Map<String, List<PluginType<?>>> pluginsByCategory : PluginRegistry.getInstance().getPluginsByCategoryByBundleId().values()) {
            mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase));
        }

        // Next iterate any packages passed to the static addPackage method.
        for (final String pkg : PACKAGES) {
            mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
        }
        // Finally iterate any packages provided in the configuration (note these can be changed at runtime).
        if (packages != null) {
            for (final String pkg : packages) {
                mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
            }
        }

        LOGGER.debug("PluginManager '{}' found {} plugins", category, newPlugins.size());

        plugins = newPlugins;
    }

从上面的英文注释可知其获取过程,看一下2.7中的官方文档:

In Log4j 2 a plugin is declared by adding a@Pluginannotation to the class declaration. During
initialization theConfigurationwill invoke the PluginManager to load the built-in Log4j plugins as
well as any custom plugins. ThePluginManagerlocates plugins by looking in five places:
1. Serialized plugin listing files on the classpath. These files are generated automatically during the
build (more details below).
2. (OSGi only) Serialized plugin listing files in each active OSGi bundle. ABundleListeneris
added on activation to continue checking new bundles afterlog4j-corehas started.
3. A comma-separated list of packages specified by thelog4j.plugin.packagessystem
property.
4. Packages passed to the staticPluginManager.addPackagesmethod (before Log4j
configuration occurs).
5. Thepackagesdeclared in your log4j2 configuration file.


2.2版本中是从Log4j2Plugin.dat文件中获取的,不看其过程,重点看下loadFromPackage的过程

 builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES);
private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core";


3. loadFromPackage

 public Map<String, List<PluginType<?>>> loadFromPackage(final String pkg) {
        if (Strings.isBlank(pkg)) {
            // happens when splitting an empty string
            return Collections.emptyMap();
        }
        Map<String, List<PluginType<?>>> existing = pluginsByCategoryByPackage.get(pkg);
        if (existing != null) {
            // already loaded this package
            return existing;
        }

        final long startTime = System.nanoTime();
        final ResolverUtil resolver = new ResolverUtil();
        final ClassLoader classLoader = Loader.getClassLoader();
        if (classLoader != null) {
            resolver.setClassLoader(classLoader);
        }
        resolver.findInPackage(new PluginTest(), pkg);

        final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<String, List<PluginType<?>>>();
        for (final Class<?> clazz : resolver.getClasses()) {
            final Plugin plugin = clazz.getAnnotation(Plugin.class);
            final String categoryLowerCase = plugin.category().toLowerCase();
            List<PluginType<?>> list = newPluginsByCategory.get(categoryLowerCase);
            if (list == null) {
                newPluginsByCategory.put(categoryLowerCase, list = new ArrayList<PluginType<?>>());
            }
            final PluginEntry mainEntry = new PluginEntry();
            final String mainElementName = plugin.elementType().equals(
                Plugin.EMPTY) ? plugin.name() : plugin.elementType();
            mainEntry.setKey(plugin.name().toLowerCase());
            mainEntry.setName(plugin.name());
            mainEntry.setCategory(plugin.category());
            mainEntry.setClassName(clazz.getName());
            mainEntry.setPrintable(plugin.printObject());
            mainEntry.setDefer(plugin.deferChildren());
            @SuppressWarnings({"unchecked","rawtypes"})
            final PluginType<?> mainType = new PluginType(mainEntry, clazz, mainElementName);
            list.add(mainType);
            final PluginAliases pluginAliases = clazz.getAnnotation(PluginAliases.class);
            if (pluginAliases != null) {
                for (final String alias : pluginAliases.value()) {
                    final PluginEntry aliasEntry = new PluginEntry();
                    final String aliasElementName = plugin.elementType().equals(
                        Plugin.EMPTY) ? alias.trim() : plugin.elementType();
                    aliasEntry.setKey(alias.trim().toLowerCase());
                    aliasEntry.setName(plugin.name());
                    aliasEntry.setCategory(plugin.category());
                    aliasEntry.setClassName(clazz.getName());
                    aliasEntry.setPrintable(plugin.printObject());
                    aliasEntry.setDefer(plugin.deferChildren());
                    @SuppressWarnings({"unchecked","rawtypes"})
                    final PluginType<?> aliasType = new PluginType(aliasEntry, clazz, aliasElementName);
                    list.add(aliasType);
                }
            }
        }

        final long endTime = System.nanoTime();
        final DecimalFormat numFormat = new DecimalFormat("#0.000000");
        final double seconds = (endTime - startTime) * 1e-9;
        LOGGER.debug("Took {} seconds to load {} plugins from package {}",
            numFormat.format(seconds), resolver.getClasses().size(), pkg);

        // Note multiple threads could be calling this method concurrently. Both will do the work,
        // but only one will be allowed to store the result in the outer map.
        // Return the inner map produced by whichever thread won the race, so all callers will get the same result.
        existing = pluginsByCategoryByPackage.putIfAbsent(pkg, newPluginsByCategory);
        if (existing != null) {
            return existing;
        }
        return newPluginsByCategory;
    }



看一下从package中寻找的过程

public void findInPackage(final Test test, String packageName) {
        packageName = packageName.replace('.', '/');
        final ClassLoader loader = getClassLoader();
        Enumeration<URL> urls;

        try {
            urls = loader.getResources(packageName);  //获取包含该packageName的资源
        } catch (final IOException ioe) {
            LOGGER.warn("Could not read package: " + packageName, ioe);
            return;
        }

        while (urls.hasMoreElements()) {
            try {
                final URL url = urls.nextElement();
                final String urlPath = extractPath(url);

                LOGGER.info("Scanning for classes in [" + urlPath + "] matching criteria: " + test);
                // Check for a jar in a war in JBoss
                if (VFSZIP.equals(url.getProtocol())) {
                    final String path = urlPath.substring(0, urlPath.length() - packageName.length() - 2);
                    final URL newURL = new URL(url.getProtocol(), url.getHost(), path);
                    @SuppressWarnings("resource")
                    final JarInputStream stream = new JarInputStream(newURL.openStream());
                    try {
                        loadImplementationsInJar(test, packageName, path, stream);
                    } finally {
                        close(stream, newURL);
                    }
                } else if (BUNDLE_RESOURCE.equals(url.getProtocol())) {
                    loadImplementationsInBundle(test, packageName);
                } else {
                    final File file = new File(urlPath);
                    if (file.isDirectory()) {
                        loadImplementationsInDirectory(test, packageName, file);
                    } else {
                        loadImplementationsInJar(test, packageName, file);   //这里获取的是jar包
                    }
                }
            } catch (final IOException ioe) {
                LOGGER.warn("could not read entries", ioe);
            }
        }
    }


loadImplementationsInJar

private void loadImplementationsInJar(final Test test, final String parent, final File jarFile) {
        @SuppressWarnings("resource")
        JarInputStream jarStream = null;
        try {
            jarStream = new JarInputStream(new FileInputStream(jarFile));
            loadImplementationsInJar(test, parent, jarFile.getPath(), jarStream);
        } catch (final FileNotFoundException ex) {
            LOGGER.error("Could not search jar file '" + jarFile + "' for classes matching criteria: " + test
                    + " file not found", ex);
        } catch (final IOException ioe) {
            LOGGER.error("Could not search jar file '" + jarFile + "' for classes matching criteria: " + test
                    + " due to an IOException", ioe);
        } finally {
            close(jarStream, jarFile);
        }
    }


获取的方式也很简单,一层层的找,将以"org/apache/logging/log4j/core"开头的,以.class结尾的加入

private void loadImplementationsInJar(final Test test, final String parent, final String path,
                                          final JarInputStream stream) {

        try {
            JarEntry entry;

            while ((entry = stream.getNextJarEntry()) != null) {
                final String name = entry.getName();
                if (!entry.isDirectory() && name.startsWith(parent) && isTestApplicable(test, name)) {
                    addIfMatching(test, name);  //
                }
            }
        } catch (final IOException ioe) {
            LOGGER.error("Could not search jar file '" + path + "' for classes matching criteria: " +
                test + " due to an IOException", ioe);
        }
    }

 protected void addIfMatching(final Test test, final String fqn) {
        try {
            final ClassLoader loader = getClassLoader();
            if (test.doesMatchClass()) {
                final String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Checking to see if class " + externalName + " matches criteria [" + test + ']');
                }

                final Class<?> type = loader.loadClass(externalName);   //根据名称获取类
                if (test.matches(type)) {   /只将@Plugin注解的类加入
                    classMatches.add(type);
                }
            }
            if (test.doesMatchResource()) {
                URL url = loader.getResource(fqn);
                if (url == null) {
                    url = loader.getResource(fqn.substring(1));
                }
                if (url != null && test.matches(url.toURI())) {
                    resourceMatches.add(url.toURI());
                }
            }
        } catch (final Throwable t) {
            LOGGER.warn("Could not examine class '" + fqn, t);
        }
    }


public boolean matches(final Class<?> type) {
            return type != null && type.isAnnotationPresent(Plugin.class);
        }


到此为止,factories中只有YamlConfigurationFactory、JsonConfigurationFactory、XmlConfigurationFactory三种ConfigurationFactory


四、如何根据配置文件选用对应的ConfigurationFactory

正题:传入的参数为23156357、null、null

public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) {
        if (!isActive()) {
            return null;
        }
        if (loader == null) {
            return getConfiguration(name, configLocation);
        }
        if (isClassLoaderUri(configLocation)) {
            final String path = extractClassLoaderUriPath(configLocation);
            final ConfigurationSource source = getInputFromResource(path, loader);
            if (source != null) {
                final Configuration configuration = getConfiguration(source);
                if (configuration != null) {
                    return configuration;
                }
            }
        }
        return getConfiguration(name, configLocation);
    }


调用的是Factory.class中的getConfiguration

/**
         * Default Factory Constructor.
         * @param name The configuration name.
         * @param configLocation The configuration location.
         * @return The Configuration.
         */
        @Override
        public Configuration getConfiguration(final String name, final URI configLocation) {

            if (configLocation == null) {
                final String config = this.substitutor.replace(
                    PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY));
                if (config != null) {  //没有配置log4j.configurationFile,config为null
                    ConfigurationSource source = null;
                    try {
                        source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config));
                    } 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(config, loader);
                    }
                    if (source != null) {
                        for (final ConfigurationFactory factory : factories) {
                            final String[] types = factory.getSupportedTypes();
                            if (types != null) {
                                for (final String type : types) {
                                    if (type.equals("*") || config.endsWith(type)) {
                                        final Configuration c = factory.getConfiguration(source);
                                        if (c != null) {
                                            return c;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } else {
                for (final ConfigurationFactory factory : factories) {
                    final String[] types = factory.getSupportedTypes();
                    if (types != null) {
                        for (final String type : types) {
                            if (type.equals("*") || configLocation.toString().endsWith(type)) {
                                final Configuration config = factory.getConfiguration(name, configLocation);
                                if (config != null) {
                                    return config;
                                }
                            }
                        }
                    }
                }
            }
		//上述条件都不成立,直接到此
            Configuration config = getConfiguration(true, name);
            if (config == null) {
                config = getConfiguration(true, null);
                if (config == null) {
                    config = getConfiguration(false, name);
                    if (config == null) {
                        config = getConfiguration(false, null);
                    }
                }
            }
            if (config != null) {
                return config;
            }
            LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");
            return new DefaultConfiguration();
        }

没有配置log4j.configurationFile

接着看

private Configuration getConfiguration(final boolean isTest, final String name) {
            final boolean named = name != null && name.length() > 0;
            final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
            for (final ConfigurationFactory factory : factories) {
                String configName;
                final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
                final String [] types = factory.getSupportedTypes(); 获取XmlConfigurationFactory支持类型
                if (types == null) {
                    continue;
                }

                for (final String suffix : types) {
                    if (suffix.equals("*")) {
                        continue;
                    }
                    configName = named ? prefix + name + suffix : prefix + suffix;

                    final ConfigurationSource source = getInputFromResource(configName, loader);
                    if (source != null) {
                        return factory.getConfiguration(source);
                    }
                }
            }
            return null;
        }


上面是根据配置文件获取ConfigurationFactory重要的一步。2.7版本官方文档这样说明的

1. Log4j will inspect the"log4j.configurationFile"system property and, if set, will attempt
to load the configuration using theConfigurationFactorythat matches the file extension.
2. If no system property is set the properties ConfigurationFactory will look forlog4j2-
test.propertiesin the classpath.
3. If no such file is found the YAML ConfigurationFactory will look forlog4j2-test.yamlor
log4j2-test.ymlin the classpath.
4. If no such file is found the JSON ConfigurationFactory will look forlog4j2-test.jsonor
log4j2-test.jsnin the classpath.
5. If no such file is found the XML ConfigurationFactory will look forlog4j2-test.xmlin the
classpath.
6. If a test file cannot be located the properties ConfigurationFactory will look for
log4j2.propertieson the classpath.
7. If a properties file cannot be located the YAML ConfigurationFactory will look for
log4j2.yamlorlog4j2.ymlon the classpath.
8. If a YAML file cannot be located the JSON ConfigurationFactory will look forlog4j2.jsonor
log4j2.jsnon the classpath.
9. If a JSON file cannot be located the XML ConfigurationFactory will try to locatelog4j2.xml
on the classpath.
10.If no configuration file could be located theDefaultConfigurationwill be used. This will
cause logging output to go to the console.


例如,项目中配置的是log4j2.xml,则获取的ConfigurationFactory为XmlConfigurationFactory


/**
     * File name prefix for standard configurations.
     */
    protected static final String DEFAULT_PREFIX = "log4j2";

 /**
     * Returns the file suffixes for XML files.
     * @return An array of File extensions.
     */
    @Override
    public String[] getSupportedTypes() {
        return SUFFIXES;
    }

/**
     * Valid file extensions for XML files.
     */
    public static final String[] SUFFIXES = new String[] {".xml", "*"};


总结,整体过程是将所有的ConfigurationFactory都获取到,然后根据配置的配置文件选用哪个ConfigurationFactory。

五、使用到的设计模式

1.单例设计模式:

ConfigurationFactory.getInstance()

 采用的是类变量的方式实现的,可参考Java开发中的23种设计模式详解(转) ,图解Java单例模式内存分配

 private static ConfigurationFactory configFactory = new Factory();

log4j2如何根据配置的配置文件选取配置文件处理类的(ConfigurationFactory源码分析)

Factory是继承ConfigurationFactory的内部类,采用静态变量configFactory引用。


2.工厂模式:

选用ConfigurationFactory及Configuration时采用的是工厂模式,比较复杂,看下面的类图

log4j2如何根据配置的配置文件选取配置文件处理类的(ConfigurationFactory源码分析)