log4j2源码解析(3)--Configuration

。_饼干妹妹 提交于 2020-01-08 17:42:05

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

Configuration的获取以及初始化

在上一篇文章中介绍了LoggerContext,这个类一个主要的作用就是根据配置文件将配置文件的结构转化为Java对象,即Appender,Logger等对象,其中配置主要是由Configuration类进行管理。这是一个基类,有几个重要的子类,每个子类对应一种配置文件。分别为:

JSONConfiguration
PropertiesConfiguration
YAMLConfiguration
XMLConfiguration

每一种Configuration由其对应的ConfigurationFactory进行管理,ConfigurationFactory负责管理每种类型Configuration配置所包含的文件名后缀,以及负责生成对应的Configuration实例。因此在LoggerContext的reconfigure方法中使用如下的调用来获取Configuration。

ConfigurationFactory.getInstance().getConfiguration()

这些初始的ConfigurationFactory类信息均存在META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat文件中,在getInstance方法中通过PluginManager的collectPlugins方法进行初始化,然后经过,合并,排序等操作,统一放到基类ConfigurationFactory的factories列表中。

getInstance方法我们上篇文章已经介绍过,这里不赘述,本文主要看getConguration方法。

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);
    }

由于现在没有classLoader,因此跳到了这个方法,这个方法是ConfigurationFactory的静态内部类Factory类的方法

public Configuration getConfiguration(final String name, final URI configLocation) {

            if (configLocation == null) {
                final String configLocationStr = this.substitutor.replace(PropertiesUtil.getProperties()
                        .getStringProperty(CONFIGURATION_FILE_PROPERTY));
                if (configLocationStr != null) {
                    final String[] sources = configLocationStr.split(",");
                    if (sources.length > 1) {
                        final List<AbstractConfiguration> configs = new ArrayList<>();
                        for (final String sourceLocation : sources) {
                            final Configuration config = getConfiguration(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(configLocationStr);
                }
                for (final ConfigurationFactory factory : getFactories()) {
                    final String[] types = factory.getSupportedTypes();
                    if (types != null) {
                        for (final String type : types) {
                            if (type.equals("*")) {
                                final Configuration config = factory.getConfiguration(name, configLocation);
                                if (config != null) {
                                    return config;
                                }
                            }
                        }
                    }
                }
            } else {
                // configLocation != null
                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("*") || configLocationStr.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();
        }

代码很长,实际的功能比较明确,就是根据各个ConfigurationFactory中的文件后缀的顺序匹配对应的文件,然后返回对应的Configuration实例。匹配的顺序如下所示:

1、log4j.configurationFile---ConfigurationFactory 
2、log4j2-test.properties---PropertiesConfigurationFactory
3、log4j2-test.yaml/log4j2-test.yml---YAMLConfigurationFactory
4、log4j2-test.json/log4j2-test.jsn---JsonConfigurationFactory
5、log4j2-test.xml---XMLConfigurationFactory
6、log4j2.properties---PropertiesConfigurationFactory
7、log4j2.yaml/log4j2.yml---YAMLConfigurationFactory
8、log4j2.json/log4j2.jsn---JSONConfigurationFactory
9、log4j2.xml---XMLConfigurationFactory
10、console控制台---DefaultConfiguration

本文中以xml文件为例,我们将配置文件放在对应的log4j2.xml,配置文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="info">
    <appenders>
        <Console name="Console" target="SYSTEM_OUT" >
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %level %logger{36} - %msg%n"/>
        </Console>
        <File name="File"  fileName="logs/test.log">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %level %logger{36} - %msg%n"/>
        </File>
    </appenders>

    <loggers>
        <!--定义一个名为"test"的Appender,类别为Console-->
        <logger name="test" additivity="false">
            <appender-ref ref="Console"/>
        </logger>
        <root level="info">
            <appender-ref ref="File"/>
        </root>
    </loggers>
</configuration>

简要分析一下里面的结构,其中有一个根配置,以configuration标签开头,有个status属性为info,随后定义了两个appender,两个appender分别是Console和File其中,ConsoleAppender中有一个target是SYSTEM_OUT,还有一个PatternLayout定义了日志的格式,FileAppender有一个fileName,还包含一个PatternLayout。最后定义了两个logger,一个是rootLogger,定义了一个Appender的引用,引用的是FileAppender,还有一个logger命名为test,additivity属性为false,随后引用了一个Appender,是前面定义的ConsoleAppender。根据这个配置文件的内容,Configuration会自动解析,下面我们来继续看代码。

在上面的配置匹配中,我们会得到一个XMLConfigurationFactory,其中里面包含文件名,文件内容等,随后在setConfiguration中会调用config的start方法,对于这个方法,首先会调用AbstractConfiguration的start方法,代码如下:

 public void start() {
        // Preserve the prior behavior of initializing during start if not initialized.
        if (getState().equals(State.INITIALIZING)) {
            initialize();
        }
        LOGGER.debug("Starting configuration {}", this);
        this.setStarting();
        if (watchManager.getIntervalSeconds() > 0) {
            watchManager.start();
        }
        if (hasAsyncLoggers()) {
            asyncLoggerConfigDisruptor.start();
        }
        final Set<LoggerConfig> alreadyStarted = new HashSet<>();
        for (final LoggerConfig logger : loggerConfigs.values()) {
            logger.start();
            alreadyStarted.add(logger);
        }
        for (final Appender appender : appenders.values()) {
            appender.start();
        }
        if (!alreadyStarted.contains(root)) { // LOG4J2-392
            root.start(); // LOG4J2-336
        }
        super.start();
        LOGGER.debug("Started configuration {} OK.", this);
    }

这里面主要包含下面几个关键步骤:

1、初始化
2、设置生命周期状态
3、启动所有的logger
4、启动所有的appender

在初始化过程中就会将配置文件中所有的元素转化为对应的Logger或Appender对象,然后启动他们,我们首先进入初始化方法

public void initialize() {
        LOGGER.debug("Initializing configuration {}", this);
        subst.setConfiguration(this);
        scriptManager = new ScriptManager(watchManager);
        pluginManager.collectPlugins(pluginPackages);
        final PluginManager levelPlugins = new PluginManager(Level.CATEGORY);
        levelPlugins.collectPlugins(pluginPackages);
        final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins();
        if (plugins != null) {
            for (final PluginType<?> type : plugins.values()) {
                try {
                    // Cause the class to be initialized if it isn't already.
                    Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader());
                } catch (final Exception e) {
                    LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass()
                            .getSimpleName(), e);
                }
            }
        }
        setup();
        setupAdvertisement();
        doConfigure();
        setState(State.INITIALIZED);
        LOGGER.debug("Configuration {} initialized", this);
    }

这个方法首先实现寻找category为level的插件,默认并没有插件,所以进入setup方法,setup中的核心是根据传入的xml dom树解析配置文件,解析成的对象为Node对象,其中解析的方法为constructHierarchy(rootNode, rootElement);具体方法如下:

private void constructHierarchy(final Node node, final Element element) {
        processAttributes(node, element);
        final StringBuilder buffer = new StringBuilder();
        final NodeList list = element.getChildNodes();
        final List<Node> children = node.getChildren();
        for (int i = 0; i < list.getLength(); i++) {
            final org.w3c.dom.Node w3cNode = list.item(i);
            if (w3cNode instanceof Element) {
                final Element child = (Element) w3cNode;
                final String name = getType(child);
                final PluginType<?> type = pluginManager.getPluginType(name);
                final Node childNode = new Node(node, name, type);
                constructHierarchy(childNode, child);
                if (type == null) {
                    final String value = childNode.getValue();
                    if (!childNode.hasChildren() && value != null) {
                        node.getAttributes().put(name, value);
                    } else {
                        status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
                    }
                } else {
                    children.add(childNode);
                }
            } else if (w3cNode instanceof Text) {
                final Text data = (Text) w3cNode;
                buffer.append(data.getData());
            }
        }

        final String text = buffer.toString().trim();
        if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
            node.setValue(text);
        }
    }

在介绍这个解析方法之前,我们首先来看Node类的定义

public class Node {
    public static final String CATEGORY = "Core";
    private final Node parent;
    private final String name;
    private String value;
    private final PluginType<?> type;
    private final Map<String, String> attributes = new HashMap<>();
    private final List<Node> children = new ArrayList<>();
    private Object object;
}

从Node的定义来看可以看出这是一个典型的属性结构,每个Node都有一个parent节点,有一个children的节点列表,通过getParent和getChildren可以方便的操作整颗Node树

再来说这个方法,可以很清晰的看出,这是一个深度优先遍历整个xml dom并构建Node树的过程,主要是设置attribute,type,children,parent等一系列信息,其中type是后续要载入的java对象的全限定符。经过该方法,我们上面的配置文件被解析成了如下的一棵Node树,如下图所示。

完成了Node树的构造,就需要根据这颗树来构造对应的Appender和Logger,这个方法由doConfigure方法完成,具体的定义如下:

 protected void doConfigure() {
        preConfigure(rootNode);
        configurationScheduler.start();
        if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) {
            final Node first = rootNode.getChildren().get(0);
            createConfiguration(first, null);
            if (first.getObject() != null) {
                subst.setVariableResolver((StrLookup) first.getObject());
            }
        } else {
            final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES);
            final StrLookup lookup = map == null ? null : new MapLookup(map);
            subst.setVariableResolver(new Interpolator(lookup, pluginPackages));
        }

        boolean setLoggers = false;
        boolean setRoot = false;
        for (final Node child : rootNode.getChildren()) {
            if (child.getName().equalsIgnoreCase("Properties")) {
                if (tempLookup == subst.getVariableResolver()) {
                    LOGGER.error("Properties declaration must be the first element in the configuration");
                }
                continue;
            }
            createConfiguration(child, null);
            if (child.getObject() == null) {
                continue;
            }
            if (child.getName().equalsIgnoreCase("Scripts")) {
                for (final AbstractScript script : child.getObject(AbstractScript[].class)) {
                    if (script instanceof ScriptRef) {
                        LOGGER.error("Script reference to {} not added. Scripts definition cannot contain script references",
                                script.getName());
                    } else {
                        scriptManager.addScript(script);
                    }
                }
            } else if (child.getName().equalsIgnoreCase("Appenders")) {
                appenders = child.getObject();
            } else if (child.isInstanceOf(Filter.class)) {
                addFilter(child.getObject(Filter.class));
            } else if (child.getName().equalsIgnoreCase("Loggers")) {
                final Loggers l = child.getObject();
                loggerConfigs = l.getMap();
                setLoggers = true;
                if (l.getRoot() != null) {
                    root = l.getRoot();
                    setRoot = true;
                }
            } else if (child.getName().equalsIgnoreCase("CustomLevels")) {
                customLevels = child.getObject(CustomLevels.class).getCustomLevels();
            } else if (child.isInstanceOf(CustomLevelConfig.class)) {
                final List<CustomLevelConfig> copy = new ArrayList<>(customLevels);
                copy.add(child.getObject(CustomLevelConfig.class));
                customLevels = copy;
            } else {
                final List<String> expected = Arrays.asList("\"Appenders\"", "\"Loggers\"", "\"Properties\"",
                        "\"Scripts\"", "\"CustomLevels\"");
                LOGGER.error("Unknown object \"{}\" of type {} is ignored: try nesting it inside one of: {}.",
                        child.getName(), child.getObject().getClass().getName(), expected);
            }
        }

        if (!setLoggers) {
            LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?");
            setToDefault();
            return;
        } else if (!setRoot) {
            LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender");
            setToDefault();
            // return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers
        }

        for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) {
            final LoggerConfig loggerConfig = entry.getValue();
            for (final AppenderRef ref : loggerConfig.getAppenderRefs()) {
                final Appender app = appenders.get(ref.getRef());
                if (app != null) {
                    loggerConfig.addAppender(app, ref.getLevel(), ref.getFilter());
                } else {
                    LOGGER.error("Unable to locate appender \"{}\" for logger config \"{}\"", ref.getRef(),
                            loggerConfig);
                }
            }

        }

        setParents();
    }

该方法的主要工作就是根据上面的Node依赖树,自动为Node的object字段来赋值,同时,将所有的LogConfig构建为一个列表,可以按名称获取相应的LogConfig,在LogConfig中维护了每个logger对应的appender,layout等实例的对应关系,并同时启动了所有的logger和appender。对于每个logger,都会维护一个自己的LogConfig,但进行类一定的封装,变为了PrivateConfig类的实例。下一篇文章中,我将会介绍如何根据name创建对应的logger,即getLogger方法的大致流程。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!