3.解析logback.xml配置文件入口

文章目录

  • 一、前言
  • 二、源码解析
    • LogbackServiceProvider
    • DefaultJoranConfigurator
    • JoranConfigurator
  • 三、总结

一、前言

前面介绍了slf4j相关的知识点, 提到了实现模块是通过SLF4JServiceProvider进行自定义日志框架的, 本节就来介绍logback的LogbackServiceProvider

二、源码解析

LogbackServiceProvider

public class LogbackServiceProvider implements SLF4JServiceProvider {
    public void initialize() {
        // 日志上下文
        defaultLoggerContext = new LoggerContext();
        // 设置名称:default
        defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
        // 初始化日志上下文
        initializeLoggerContext();
        // 标记上下文已启动, 顺便发送启动事件
        defaultLoggerContext.start();
        markerFactory = new BasicMarkerFactory();
        mdcAdapter = new LogbackMDCAdapter();
        // set the MDCAdapter for the defaultLoggerContext immediately
        defaultLoggerContext.setMDCAdapter(mdcAdapter);
    }
    
}
  1. 构建了一个logback中最核心的类LoggerContext, 默认名字为default
  2. 进行初始化, initializeLoggerContext
  3. 标记上下文已启动, 顺便发送启动事件

核心的部分是初始化日志上下文initializeLoggerContext

private void initializeLoggerContext() {
    try {
        try {
            // 解析配置文件
            new ContextInitializer(defaultLoggerContext).autoConfig();
        } catch (JoranException je) {
            Util.report("Failed to auto configure default logger context", je);
        }
        // LOGBACK-292
        if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
            StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
        }
        // contextSelectorBinder.init(defaultLoggerContext, KEY);

    } catch (Exception t) { // see LOGBACK-1159
        Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
    }
}

//  ContextInitializer#autoConfig
public void autoConfig(ClassLoader classLoader) throws JoranException {

        // see https://github.com/qos-ch/logback/issues/715
        classLoader = Loader.systemClassloaderIfNull(classLoader);
		// 检查logback-classic的版本
        checkVersions();
		// 添加日志状态监听器
        StatusListenerConfigHelper.installIfAsked(loggerContext);


        // invoke custom configurators
        // spi获取Configurator
        List<Configurator> configuratorList = ClassicEnvUtil.loadFromServiceLoader(Configurator.class, classLoader);
        // 排个序
        configuratorList.sort(rankComparator);
        if (configuratorList.isEmpty()) {
            contextAware.addInfo("No custom configurators were discovered as a service.");
        } else {
            // 打印获取到的configurator
            printConfiguratorOrder(configuratorList);
        }
		// 执行spi获取的Configurator
        for (Configurator c : configuratorList) {
            // 返回执行状态为DO_NOT_INVOKE_NEXT_IF_ANY直接返回
            if (invokeConfigure(c) == Configurator.ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY) {
                return;
            }
        }

        // 执行默认的Configurator
        // 1. SerializedModelConfigurator
        // 2. DefaultJoranConfigurator: 解析配置文件
        // 3. BasicConfigurator
        for (String configuratorClassName : INTERNAL_CONFIGURATOR_CLASSNAME_LIST) {
            contextAware.addInfo("Trying to configure with "+configuratorClassName);
            // 实例化
            Configurator c = instantiateConfiguratorByClassName(configuratorClassName, classLoader);
            if (c == null) {
                continue;
            }
            // 解析配置文件
            if (invokeConfigure(c) == Configurator.ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY) {
                return;
            }
        }
    }

方法小结

初始化逻辑不属于LogbackServiceProvider, 内容又比较长怎么办, logback就创建了一个上下文初始化对象ContextInitializer来帮助处理启动初始化这个动作

  1. 先用spi获取Configurator, 然后执行, 如果存在并且执行完后返回DO_NOT_INVOKE_NEXT_IF_ANY, 直接中断后面的解析; 默认没有
  2. 执行默认内置的Configurator, 如果存在并且执行完后返回DO_NOT_INVOKE_NEXT_IF_ANY, 直接中断后面的解析
  3. 默认的内置Configurator有如下三个
  • SerializedModelConfigurator, 用来解析.scmo结尾的文件(logback.scmo, logback-test.scmo)
  • DefaultJoranConfigurator, 用来解析配置文件(logback.xml,logback-test.xml)
  • BasicConfigurator

这里的核心Configurator是DefaultJoranConfigurator, 用来解析我们常用的logback.xml

DefaultJoranConfigurator

@ConfiguratorRank(value = ConfiguratorRank.NOMINAL)
public class DefaultJoranConfigurator extends ContextAwareBase implements Configurator {
    
    @Override
    public ExecutionStatus configure(LoggerContext context) {
        // 查找配置文件的路径
        URL url = performMultiStepConfigurationFileSearch(true);
        if (url != null) {
            try {
                // 解析url
                configureByResource(url);
            } catch (JoranException e) {
                e.printStackTrace();
            }
            // You tried and that counts Mary.
            // 解析正常不再继续后续的Configurator解析
            return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
        } else {
            // 异常执行下一个
            return ExecutionStatus.INVOKE_NEXT_IF_ANY;
        }
    }
}

这里有两个重要的方法

  1. performMultiStepConfigurationFileSearch获取要解析的配置文件
  2. configureByResource解析url

performMultiStepConfigurationFileSearch

private URL performMultiStepConfigurationFileSearch(boolean updateStatus) {
    ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
    // 获取系统指定的配置文件地址; key:logback.configurationFile
    URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
    if (url != null) {
        return url;
    }
    // logback-test.xml
    url = getResource(ClassicConstants.TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
    if (url != null) {
        return url;
    }
    // logback.xml
    return getResource(ClassicConstants.AUTOCONFIG_FILE, myClassLoader, updateStatus);
}

// 获取指定的日志文件地址
private URL findConfigFileURLFromSystemProperties(ClassLoader classLoader, boolean updateStatus) {
    // 系统属性获取 key:logback.configurationFile
    String logbackConfigFile = OptionHelper.getSystemProperty(ClassicConstants.CONFIG_FILE_PROPERTY);
    if (logbackConfigFile != null) {
        URL result = null;
        try {
            // 1.网路路径
            result = new URL(logbackConfigFile);
            return result;
        } catch (MalformedURLException e) {
            // 2.classpath路径
            result = Loader.getResource(logbackConfigFile, classLoader);
            if (result != null) {
                return result;
            }
            // 3.磁盘文件路径
            File f = new File(logbackConfigFile);
            if (f.exists() && f.isFile()) {
                try {
                    result = f.toURI().toURL();
                    return result;
                } catch (MalformedURLException e1) {
                }
            }
        } finally {
            // 打印日志
            if (updateStatus) {
                statusOnResourceSearch(logbackConfigFile, classLoader, result);
            }
        }
    }
    return null;
}

方法小结

  1. 先获取指定的日志配置文件, 用-Dlogback.configurationFile=你的log配置文件地址指定需要解析的配置文件; 它可以是网络路径, 也可以是磁盘路径, 也可以是项目中classpath下的路径
  2. 如果不存在指定的log配置文件, 那么从classpath下获取logback-test.xml或者 logback.xml, 顺序是以logback-test.xml优先哦, 如果项目中这两个都配置了, 那么只有logback-test.xml会生效

classpath路径指的是我们项目中编译后, 源码同级及以下的路径, 例如我们maven项目中配置的Sources和Resources

3.解析logback.xml配置文件入口_第1张图片
编译之后就是这个地方classes的路径
3.解析logback.xml配置文件入口_第2张图片

configureByResource

public void configureByResource(URL url) throws JoranException {
    if (url == null) {
        throw new IllegalArgumentException("URL argument cannot be null");
    }
    final String urlString = url.toString();
    // 一般是logback.xml
    if (urlString.endsWith("xml")) {
        JoranConfigurator configurator = new JoranConfigurator();
        configurator.setContext(context);
        configurator.doConfigure(url);
    } else {
        throw new LogbackException(
                "Unexpected filename extension of file [" + url.toString() + "]. Should be .xml");
    }
}

使用JoranConfigurator解析xml文件, 看得出来仅支持xml文件的日志配置

JoranConfigurator

继承图
3.解析logback.xml配置文件入口_第3张图片
JoranConfigurator是logback中最重要的一个配置器类, 它包含了

  1. 对logbak.xml文件的解析
  2. 将解析的节点生成对应的model
  3. 将model和对应的处理器modelHandler关联, 并使用modelHandler处理model
  4. 最后会将所有对打印日志有关的组件添加到loggerContext中

具体的解析流量将会在下一篇中介绍到

三、总结

  1. 默认的日志上下文是loggerContext
  2. 使用spi获取到Configurator进行初始化, 如果存在(默认不存在), 并且执行后的返回值为DO_NOT_INVOKE_NEXT_IF_ANY则解析完成
  3. 使用默认的Configurator进行初始化, 其中DefaultJoranConfigurator比较核心, 是logback用来解析logback.xml的配置类, 解析成功后会返回DO_NOT_INVOKE_NEXT_IF_ANY, 直接终端后面的配置类解析; 默认的三个配置类如下
  • SerializedModelConfigurator
  • DefaultJoranConfigurator
  • BasicConfigurator
  1. 可以通过环境参数-Dlogback.configurationFile指定使用的logback.xml文件的位置, 可以是网络路径, 磁盘路径, classpath路径; 所以不一定是logback.xml这个名字(后缀必须是xml), 也不定需要放在classpath目录下
  2. 如果没有手动指定logback.xml文件的位置, 会默认先获取classpath下的logback-test.xml, 如果没有, 则会获取classpath下的logback.xml
  3. 默认会使用JoranConfigurator类解析获取到的配置文件, 例如 logback.xml
  4. 如果想要自定义解析器, 或者解析其它类型的配置文件, 比如properties, 那么你需要使用spi的方式自定义一个Configurator类, 然后自己加载配置文件进行解析

你可能感兴趣的:(slf4j,logback,slf4j)