• 深入源码解析日志框架Log4j2(一)


    前言

    log4j2是apache在log4j的基础上,参考logback架构实现的一套新的日志系统(我感觉是apache害怕logback了)。
    log4j2的官方文档上写着一些它的优点:

    • 在拥有全部logback特性的情况下,还修复了一些隐藏问题

    • API 分离:现在log4j2也是门面模式使用日志,默认的日志实现是log4j2,当然你也可以用logback(应该没有人会这么做)

    • 性能提升:log4j2包含下一代基于LMAX Disruptor library的异步logger,在多线程场景下,拥有18倍于log4j和logback的性能

    • 多API支持:log4j2提供Log4j 1.2, SLF4J, Commons Logging and java.util.logging (JUL) 的API支持

    • 避免锁定:使用Log4j2 API的应用程序始终可以选择使用任何符合SLF4J的库作为log4j-to-slf4j适配器的记录器实现

    • 自动重新加载配置:与Logback一样,Log4j 2可以在修改时自动重新加载其配置。与Logback不同,它会在重新配置发生时不会丢失日志事件。

    • 高级过滤: 与Logback一样,Log4j 2支持基于Log事件中的上下文数据,标记,正则表达式和其他组件进行过滤。

    • 插件架构: Log4j使用插件模式配置组件。因此,您无需编写代码来创建和配置Appender,Layout,Pattern Converter等。Log4j自动识别插件并在配置引用它们时使用它们。

    • 属性支持:您可以在配置中引用属性,Log4j将直接替换它们,或者Log4j将它们传递给将动态解析它们的底层组件。

    • Java 8 Lambda支持

    • 自定义日志级别

    • 产生垃圾少:在稳态日志记录期间,Log4j 2 在独立应用程序中是无垃圾的,在Web应用程序中是低垃圾。这减少了垃圾收集器的压力,并且可以提供更好的响应时间性能。

    • 和应用server集成:版本2.10.0引入了一个模块log4j-appserver,以改进与Apache Tomcat和Eclipse Jetty的集成。

    Log4j2类图:
    图片

    这次从四个地方去探索源码:启动,配置,异步,插件化

    源码探索

    启动

    log4j2的关键组件

    • LogManager

    根据配置指定LogContexFactory,初始化对应的LoggerContext

    • LoggerContext

    1、解析配置文件,解析为对应的java对象。
    2、通过LoggerRegisty缓存Logger配置
    3、Configuration配置信息
    4、start方法解析配置文件,转化为对应的java对象
    5、通过getLogger获取logger对象

    • Logger

    LogManaer

    该组件是Log4J启动的入口,后续的LoggerContext以及Logger都是通过调用LogManager的静态方法获得。我们可以使用下面的代码获取Logger

    Logger logger = LogManager.getLogger();

    可以看出LogManager是十分关键的组件,因此在这个小节中我们详细分析LogManager的启动流程。
    LogManager启动的入口是下面的static代码块:

    /**
    * Scans the classpath to find all logging implementation. Currently, only one will be used but this could be
    * extended to allow multiple implementations to be used.
    */
    static {
    // Shortcut binding to force a specific logging implementation.
    final PropertiesUtil managerProps = PropertiesUtil.getProperties();
    final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME);
    if (factoryClassName != null) {
    try {
    factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);
    } catch (final ClassNotFoundException cnfe) {
    LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);
    } catch (final Exception ex) {
    LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex);
    }
    }

    if (factory == null) {
    final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();
    // note that the following initial call to ProviderUtil may block until a Provider has been installed when
    // running in an OSGi environment
    if (ProviderUtil.hasProviders()) {
    for (final Provider provider : ProviderUtil.getProviders()) {
    final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory();
    if (factoryClass != null) {
    try {
    factories.put(provider.getPriority(), factoryClass.newInstance());
    } catch (final Exception e) {
    LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider
    .getUrl(), e);
    }
    }
    }

    if (factories.isEmpty()) {
    LOGGER.error("Log4j2 could not find a logging implementation. "
    + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
    factory = new SimpleLoggerContextFactory();
    } else if (factories.size() == 1) {
    factory = factories.get(factories.lastKey());
    } else {
    final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");
    for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
    sb.append("Factory: ").append(entry.getValue().getClass().getName());
    sb.append(", Weighting: ").append(entry.getKey()).append('\n');
    }
    factory = factories.get(factories.lastKey());
    sb.append("Using factory: ").append(factory.getClass().getName());
    LOGGER.warn(sb.toString());

    }
    } else {
    LOGGER.error("Log4j2 could not find a logging implementation. "
    + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
    factory = new SimpleLoggerContextFactory();
    }
    }
    }

    这段静态代码段主要分为下面的几个步骤:

    1. 首先根据特定配置文件的配置信息获取loggerContextFactory

    2. 如果没有找到对应的Factory的实现类则通过ProviderUtil中的getProviders()方法载入providers,随后通过provider的loadLoggerContextFactory方法载入LoggerContextFactory的实现类

    3. 如果provider中没有获取到LoggerContextFactory的实现类或provider为空,则使用SimpleLoggerContextFactory作为LoggerContextFactory。

    根据配置文件载入LoggerContextFactory

    // Shortcut binding to force a specific logging implementation.
    final PropertiesUtil managerProps = PropertiesUtil.getProperties();
    final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME);
    if (factoryClassName != null) {
    try {
    factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class);
    } catch (final ClassNotFoundException cnfe) {
    LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);
    } catch (final Exception ex) {
    LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex);
    }
    }

    在这段逻辑中,LogManager优先通过配置文件”log4j2.component.properties”通过配置项”log4j2.loggerContextFactory”来获取LoggerContextFactory,如果用户做了对应的配置,通过newCheckedInstanceOf方法实例化LoggerContextFactory的对象,最终的实现方式为:

    public static <T> T newInstanceOf(final Class<T> clazz)
    throws InstantiationException, IllegalAccessException, InvocationTargetException {
    try {
    return clazz.getConstructor().newInstance();
    } catch (final NoSuchMethodException ignored) {
    // FIXME: looking at the code for Class.newInstance(), this seems to do the same thing as above
    return clazz.newInstance();
    }
    }

    在默认情况下,不存在初始的默认配置文件log4j2.component.properties,因此需要从其他途径获取LoggerContextFactory。

    通过Provider实例化LoggerContextFactory对象

    代码:

    if (factory == null) {
    final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();
    // note that the following initial call to ProviderUtil may block until a Provider has been installed when
    // running in an OSGi environment
    if (ProviderUtil.hasProviders()) {
    for (final Provider provider : ProviderUtil.getProviders()) {
    final Class<? extends LoggerContextFactory> factoryClass = provider.loadLoggerContextFactory();
    if (factoryClass != null) {
    try {
    factories.put(provider.getPriority(), factoryClass.newInstance());
    } catch (final Exception e) {
    LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider
    .getUrl(), e);
    }
    }
    }

    if (factories.isEmpty()) {
    LOGGER.error("Log4j2 could not find a logging implementation. "
    + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
    factory = new SimpleLoggerContextFactory();
    } else if (factories.size() == 1) {
    factory = factories.get(factories.lastKey());
    } else {
    final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");
    for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
    sb.append("Factory: ").append(entry.getValue().getClass().getName());
    sb.append(", Weighting: ").append(entry.getKey()).append('\n');
    }
    factory = factories.get(factories.lastKey());
    sb.append("Using factory: ").append(factory.getClass().getName());
    LOGGER.warn(sb.toString());

    }
    } else {
    LOGGER.error("Log4j2 could not find a logging implementation. "
    + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
    factory = new SimpleLoggerContextFactory();
    }
    }

    这里比较有意思的是hasProviders和getProviders都会通过线程安全的方式去懒加载ProviderUtil这个对象。跟进lazyInit方法:

    protected static void lazyInit() {
    //noinspection DoubleCheckedLocking
    if (INSTANCE == null) {
    try {
    STARTUP_LOCK.lockInterruptibly();
    if (INSTANCE == null) {
    INSTANCE = new ProviderUtil();
    }
    } catch (final InterruptedException e) {
    LOGGER.fatal("Interrupted before Log4j Providers could be loaded.", e);
    Thread.currentThread().interrupt();
    } finally {
    STARTUP_LOCK.unlock();
    }
    }
    }

    再看构造方法:

    private ProviderUtil() {
    for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) {
    loadProvider(resource.getUrl(), resource.getClassLoader());
    }
    }

    这里的懒加载其实就是懒加载Provider对象。在创建新的providerUtil实例的过程中就会直接实例化provider对象,其过程是先通过getClassLoaders方法获取provider的类加载器,然后通过loadProviders(classLoader);加载类。在providerUtil实例化的最后,会统一查找”META-INF/log4j-provider.properties”文件中对应的provider的url,会考虑从远程加载provider。而loadProviders方法就是在ProviderUtil的PROVIDERS列表中添加对一个的provider。可以看到默认的provider是org.apache.logging.log4j.core.impl.Log4jContextFactory

    LoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactory
    Log4jAPIVersion = 2.1.0
    FactoryPriority= 10

    很有意思的是这里懒加载加上了锁,而且使用的是
    lockInterruptibly这个方法。lockInterruptibly和lock的区别如下:

    lock 与 lockInterruptibly比较区别在于:

    lock 优先考虑获取锁,待获取锁成功后,才响应中断。
    lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。
    ReentrantLock.lockInterruptibly允许在等待时由其它线程调用等待线程的
    Thread.interrupt 方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。 ReentrantLock.lock方法不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试获取锁,失败则继续休眠。只是在最后获取锁成功后再把当前线程置为interrupted状态,然后再中断线程。

    上面有一句注释值得注意:

    /**
    * Guards the ProviderUtil singleton instance from lazy initialization. This is primarily used for OSGi support.
    *
    * @since 2.1
    */
    protected static final Lock STARTUP_LOCK = new ReentrantLock();
    // STARTUP_LOCK guards INSTANCE for lazy initialization; this allows the OSGi Activator to pause the startup and
    // wait for a Provider to be installed. See LOG4J2-373
    private static volatile ProviderUtil INSTANCE;

    原来这里是为了让osgi可以阻止启动。
    再回到logManager:
    可以看到在加载完Provider之后,会做factory的绑定:

    if (factories.isEmpty()) {
    LOGGER.error("Log4j2 could not find a logging implementation. "
    + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...");
    factory = new SimpleLoggerContextFactory();
    } else if (factories.size() == 1) {
    factory = factories.get(factories.lastKey());
    } else {
    final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");
    for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
    sb.append("Factory: ").append(entry.getValue().getClass().getName());
    sb.append(", Weighting: ").append(entry.getKey()).append('\n');
    }
    factory = factories.get(factories.lastKey());
    sb.append("Using factory: ").append(factory.getClass().getName());
    LOGGER.warn(sb.toString());

    }

    到这里,logmanager的启动流程就结束了。

    配置

    在不使用slf4j的情况下,我们获取logger的方式是这样的:

    Logger logger = logManager.getLogger(xx.class)

    跟进getLogger方法:

        public static Logger getLogger(final Class<?> clazz) {
    final Class<?> cls = callerClass(clazz);
    return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls));
    }

    这里有一个getContext方法,跟进,

    public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) {
    try {
    return factory.getContext(FQCN, loader, null, currentContext);
    } catch (final IllegalStateException ex) {
    LOGGER.warn(ex.getMessage() + " Using SimpleLogger");
    return new SimpleLoggerContextFactory().getContext(FQCN, loader, null, currentContext);
    }
    }

    上文提到factory的具体实现是Log4jContextFactory,跟进getContext
    方法:

    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
    final boolean currentContext) {
    final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext);
    if (externalContext != null && ctx.getExternalContext() == null) {
    ctx.setExternalContext(externalContext);
    }
    if (ctx.getState() == LifeCycle.State.INITIALIZED) {
    ctx.start();
    }
    return ctx;
    }

    直接看start:

    public void start() {
    LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);
    if (PropertiesUtil.getProperties().getBooleanProperty("log4j.LoggerContext.stacktrace.on.start", false)) {
    LOGGER.debug("Stack trace to locate invoker",
    new Exception("Not a real error, showing stack trace to locate invoker"));
    }
    if (configLock.tryLock()) {
    try {
    if (this.isInitialized() || this.isStopped()) {
    this.setStarting();
    reconfigure();
    if (this.configuration.isShutdownHookEnabled()) {
    setUpShutdownHook();
    }
    this.setStarted();
    }
    } finally {
    configLock.unlock();
    }
    }
    LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);
    }

    发现其中的核心方法是reconfigure方法,继续跟进:

    private void reconfigure(final URI configURI) {
    final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
    LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
    contextName, configURI, this, cl);
    final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl);
    if (instance == null) {
    LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl);
    } else {
    setConfiguration(instance);
    /*
                 * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) {
    * old.stop(); }
    */
    final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource());
    LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
    contextName, location, this, cl);
    }
    }

    可以看到每一个configuration都是从ConfigurationFactory拿出来的,我们先看看这个类的getInstance看看:

    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();
    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 {
    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);
    }
    } finally {
    LOCK.unlock();
    }
    }

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

    这里可以看到ConfigurationFactory中利用了PluginManager来进行初始化,PluginManager会将ConfigurationFactory的子类加载进来,默认使用的XmlConfigurationFactory,JsonConfigurationFactory,YamlConfigurationFactory这三个子类,这里插件化加载暂时按下不表。
    回到reconfigure这个方法,我们看到获取ConfigurationFactory实例之后会去调用getConfiguration方法:

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

    跟进getConfiguration,这里值得注意的是有很多个getConfiguration,注意甄别,如果不确定的话可以通过debug的方式来确定。

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

    这里就会根据之前加载进来的factory进行配置的获取,具体的不再解析。
    回到reconfigure,之后的步骤就是setConfiguration,入参就是刚才获取的config

    private synchronized Configuration setConfiguration(final Configuration config) {
    Assert.requireNonNull(config, "No Configuration was provided");
    final Configuration prev = this.config;
    config.addListener(this);
    final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);

    try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
    map.putIfAbsent("hostName", NetUtils.getLocalHostname());
    } catch (final Exception ex) {
    LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
    map.putIfAbsent("hostName", "unknown");
    }
    map.putIfAbsent("contextName", name);
    config.start();
    this.config = config;
    updateLoggers();
    if (prev != null) {
    prev.removeListener(this);
    prev.stop();
    }

    firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));

    try {
    Server.reregisterMBeansAfterReconfigure();
    } catch (final Throwable t) {
    // LOG4J2-716: Android has no java.lang.management
    LOGGER.error("Could not reconfigure JMX", t);
    }
    return prev;
    }

    这个方法最重要的步骤就是config.start,这才是真正做配置解析的

    public void start() {
    LOGGER.debug("Starting configuration {}", this);
    this.setStarting();
    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();
    final Set<LoggerConfig> alreadyStarted = new HashSet<LoggerConfig>();
    for (final LoggerConfig logger : loggers.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. 初始化Advertiser

    4. 配置

    先看一下初始化,也就是setup这个方法,setup是一个需要被复写的方法,我们以XMLConfiguration作为例子,

    @Override
    public void setup() {
    if (rootElement == null) {
    LOGGER.error("No logging configuration");
    return;
    }
    constructHierarchy(rootNode, rootElement);
    if (status.size() > 0) {
    for (final Status s : status) {
    LOGGER.error("Error processing element {}: {}", s.name, s.errorType);
    }
    return;
    }
    rootElement = null;
    }

    发现这里面有一个比较重要的方法constructHierarchy,跟进:

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

    发现这个就是一个树遍历的过程。诚然,配置文件是以xml的形式给出的,xml的结构就是一个树形结构。回到start方法,跟进doConfiguration:

    protected void doConfigure() {
    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("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();
    loggers = 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<CustomLevelConfig>(customLevels);
    copy.add(child.getObject(CustomLevelConfig.class));
    customLevels = copy;
    } else {
    LOGGER.error("Unknown object \"{}\" of type {} is ignored.", child.getName(),
    child.getObject().getClass().getName());
    }
    }

    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 : loggers.entrySet()) {
    final LoggerConfig l = entry.getValue();
    for (final AppenderRef ref : l.getAppenderRefs()) {
    final Appender app = appenders.get(ref.getRef());
    if (app != null) {
    l.addAppender(app, ref.getLevel(), ref.getFilter());
    } else {
    LOGGER.error("Unable to locate appender {} for logger {}", ref.getRef(), l.getName());
    }
    }

    }

    setParents();
    }

    发现就是对刚刚获取的configuration进行解析,然后塞进正确的地方。回到start方法,可以看到昨晚配置之后就是开启logger和appender了。

  • 相关阅读:
    iOS 上线流程
    静态库和动态库的区别
    iOS如何生成.a文件
    苹果公司软件
    iOS 的主要框架
    多线程图解
    判断屏幕横屏/竖屏
    最大堆构建和堆排序
    hadoop2.6.0 + hbase-1.0.0 伪分布配置
    centos6 名字服务dnsmasq配置
  • 原文地址:https://www.cnblogs.com/lanblogs/p/15160592.html
Copyright © 2020-2023  润新知