logback源码浅析

一. 前言

1. 相关日志文档

2. 说明

  • logback也是基于slf4j开发的一套日志框架, 使用到门面模式. 点击了解更多门面模式
  • 本文基于使用logback.xml配置文件进行源码解析
  • 本文只讲核心代码, 不是主线的代码不展示出来
  • 本文基于的logback-classic依赖版本为1.2.12
  • 如有疑问可留意或查看官方文档

3. 代码准备

3.1 添加logback依赖

a. 非maven项目添加依赖 (三个)
  • logback-classic.jar (示例版本: 1.2.12)
  • logback-core.jar (示例版本: 1.2.12)
  • slf4j-api.jar (示例版本: 1.7.36) -> 该依赖是slf4j的
b. maven项目添加依赖
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.12</version>
        </dependency>

说明: logback-classic中会自动导入logback-core和slf4j-api的包, 故只需要添加一个即可

c. SpringBoot项目添加依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.11-SNAPSHOT</version>
        </dependency>
        
		<!-- 或者 -->
		
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.7.11-SNAPSHOT</version>
        </dependency>

说明: spring-boot-starter-web依赖会自动导入spring-boot-starter依赖, 而spring-boot-starter又会自动导入logback-classic. 如下图

在这里插入图片描述

3.2 添加logback.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<!-- 简单配置: 
		自己项目的打印info级别及以上的日志, 输出到控制台和info.log中
	 	非自己项目只打印error级别日志, 输出到控制台和error.log中
	-->

	<!-- 日志存放路径 -->
	<property name="log.path" value="/logs/logback-study" />
	<!-- 日志输出格式 -->
	<property name="log.pattern" value="%d [%thread] %-5level [%logger:%line] - %msg%n" />
	
	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>${log.pattern}</pattern>
		</encoder>
	</appender>

	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<file>${log.path}/info/info.log</file>
		<!-- 循环政策:基于时间创建日志文件 -->
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!-- 日志文件名格式 -->
			<fileNamePattern>${log.path}/info/info.%d{yyyy-MM-dd}.log</fileNamePattern>
			<!-- 日志最大的历史 3天 -->
			<maxHistory>3</maxHistory>
		</rollingPolicy>
		<encoder>
			<pattern>${log.pattern}</pattern>
		</encoder>
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<!-- 过滤的级别: info及以上 -->
			<level>INFO</level>
		</filter>
	</appender>

	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<file>${log.path}/error/error.log</file>
		<!-- 循环政策:基于时间创建日志文件 -->
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<!-- 日志文件名格式 -->
			<fileNamePattern>${log.path}/error/error.%d{yyyy-MM-dd}.log</fileNamePattern>
			<!-- 日志最大的历史 3天 -->
			<maxHistory>3</maxHistory>
		</rollingPolicy>
		<encoder>
			<pattern>${log.pattern}</pattern>
		</encoder>
		<filter class="ch.qos.logback.classic.filter.LevelFilter">
			<!-- 过滤的级别 -->
			<level>ERROR</level>
			<!-- 匹配时的操作:接收(记录) -->
			<onMatch>ACCEPT</onMatch>
			<!-- 不匹配时的操作:拒绝(不记录) -->
			<onMismatch>DENY</onMismatch>
		</filter>
	</appender>

	<logger name="com.chenlongji.logbackstudy" level="INFO" additivity="false">
		<appender-ref ref="console"/>
		<appender-ref ref="file_info"/>
	</logger>

	<root level="error">
		<appender-ref ref="console" />
		<appender-ref ref="file_error"/>
	</root>
</configuration>
  • 注: 想了解如何配置的, 点击查询此文章
  • 说明:
    • 非Spring项目可以使用的配置文件:
      • logback-test.xml(优先级更高)
      • logback.xml
    • Spring项目可以使用的配置文件
      • logback-test.groovy
      • logback-test.xml
      • logback.groovy
      • logback.xml
      • logback-test-spring.groovy
      • logback-test-spring.xml
      • logback-spring.groovy
      • logback-spring.xml -> 详情见[SpringBoot环境下logback-spring.xml配置文件一般配置]

3.3 添加main测试方法

// 使用logback.xml (下面源码都是基于logback.xml进行)
public class Test {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Test.class);
        logger.info("我是info级别的日志");
    }
}

// 使用logback-spring.xml
@SpringBootApplication
public class LogbackStudyApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(LogbackStudyApplication.class, args);
        Logger logger = LoggerFactory.getLogger(Test1.class);
        logger.info("我是info级别的日志");
        context.close();
    }
}

二. 流程图和常用类图

初始化流程

在这里插入图片描述
在这里插入图片描述

说明: 额, 此人很懒,什么也没有留下

输出日志流程

待出

GenericConfigurator类图

在这里插入图片描述

Action类图

在这里插入图片描述

Appender类图

全部Appender类实现类

在这里插入图片描述

说明:

  1. 常用的主要是UnsynchronizedAppenderBase的实现类

Appender的UnsynchronizedAppenderBase的实现类

在这里插入图片描述

说明:

  1. 最最常用的就是ConsoleAppender 和 RollingFileAppender
  2. AsyncAppender为异步输出日志的日志输出器(较简单, 就不讲其源码了).
    AsyncAppender不执行具体的日志输出操作, 它包含了多个appender, 使用包含的appender执行日志输出
    AsyncAppender启动后, 会开启一个Worker线程异步处理添加到blockingQueue的日志事件
    AsyncAppender接受到日志事件时, 都是直接丢入blockingQueue队列中, 待worker去处理

Appender的OutputStreamAppender的实现类详情

在这里插入图片描述

三. 初始化源码

从main方法出发

public class Test {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Test.class);
    }
}

进入LoggerFactory的getLogger(Class<?> clazz)方法

    public static Logger getLogger(Class<?> clazz) {
        // 获取logger对象
        Logger logger = getLogger(clazz.getName());
        // 传入的clazz != 实际创建logger所在的类时, 输出预警信息(不展开讲)
        if (DETECT_LOGGER_NAME_MISMATCH) {
            Class<?> autoComputedCallingClass = Util.getCallingClass();
            if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
                Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
                                autoComputedCallingClass.getName()));
                Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
            }
        }
        return logger;
    }

进入LoggerFactory的getLogger(String name)方法

    public static Logger getLogger(String name) {
        // 核心代码: 获取ILoggerFactory对象, 即LoggerContext (该值类似于log4j中的Hierarchy)
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        // 核心代码: 获取logger对象
        return iLoggerFactory.getLogger(name);
    }

说明:

  1. getILoggerFactory()方法执行了Logback框架的初始化
  2. iLoggerFactory.getLogger(name)方法获取logger对象

进入LoggerFactory的getILoggerFactory()方法

    public static ILoggerFactory getILoggerFactory() {
        // 未初始化, 执行初始化
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory1.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    // 核心代码: 初始化
                    performInitialization();
                }
            }
        }
        // 根据初始化结果的状态, 返回ILoggerFactory实现或抛出异常
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            // 正常完成初始化, 返回LoggerContext
            // 注: LoggerFactory为slf4j-api的类, 该jar中是不包含StaticLoggerBinder的. 
            //     据网上说法是编译前是有的, 编译后slf4j-api中的StaticLoggerBinder.class被手动删除了
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            return SUBST_FACTORY;
        }
        throw new IllegalStateException("Unreachable code");
    }

说明:

  1. performInitialization()方法为初始化代码入口
  2. StaticLoggerBinder.getSingleton().getLoggerFactory()返回初始化后的LoggerContext对象
  3. StaticLoggerBinder的 SINGLETON 属性为单例的全局静态变量, 之后获取logger对象都是通过该对象的LoggerContext属性获取

进入LoggerFactory的performInitialization()方法

    private final static void performInitialization() {
        // 核心代码: 执行绑定(初始化)
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            // 初始化成功后, 校验logback-classic版本是否和slf4j-api版本匹配, 不匹配输出预警信息(不展开讲)
            versionSanityCheck();
        }
    }

进入LoggerFactory的bind()方法

    private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // 安卓环境下跳过
            if (!isAndroid()) {
                // 获取可能的StaticLoggerBinder路径. 对类加载器感兴趣的可自行读源码 (不展开讲)
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                // 找到多个StaticLoggerBinder时, 输出预警信息, 并输出所有StaticLoggerBinder的路径 (不展开讲)
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // 核心代码: 执行初始化
            // 注: 多个StaticLoggerBinder时有人说是随机取一个, 有人说是取staticLoggerBinderPathSet中加载的第一个(实测多次都是取第一个)
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            // 打印出实际使用的StaticLoggerBinder类的ContextSelectorStaticBinder的类型
            reportActualBinding(staticLoggerBinderPathSet);
        } catch (NoClassDefFoundError ncde) {
        } catch (NoSuchMethodError nsme) {
        } catch (Exception e) {
        } finally {
            // 初始化替代记录器,同时输出初始化替代记录器的日志事件, 置空初始化替代记录器的信息 (不展开讲)
            postBindCleanUp();
        }
    }

说明: 核心代码: StaticLoggerBinder.getSingleton()

进入StaticLoggerBinder的getSingleton()方法

public class StaticLoggerBinder implements LoggerFactoryBinder {
    private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
    // 加载StaticLoggerBinder类到jvm中, 执行状态代码块
    static {
    	// 核心代码: 初始化
        SINGLETON.init();
    }

    public static StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }
}

说明:

  1. 通过调用StaticLoggerBinder.getSingleton()方法, 加载StaticLoggerBinder类到jvm中
  2. 加载StaticLoggerBinder类时,

进入StaticLoggerBinder的init()方法

    void init() {
        try {
            try {
                // 核心代码: 读取配置, 完成LoggerContext初始化
                new ContextInitializer(defaultLoggerContext).autoConfig();
            } catch (JoranException je) {
                Util.report("Failed to auto configure default logger context", je);
            }
            // 有状态监听器时并且阈值为错误或警告时, 打印LoggerContext的状态内容 (不展开讲)
            if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
                StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
            }
            // 重要代码: 这里初始化contextSelectorBinder的contextSelector属性, contextSelector属性中包含了LoggerContext.
            //   1. 之前的StaticLoggerBinder.getSingleton().getLoggerFactory()方法获取的就是contextSelector属性中的LoggerContext
            //   2. contextSelector的默认实现为DefaultContextSelector. 可以通过配置指定实现, 例如logback.ContextSelector=JNDI(实现为ContextJNDISelector)
            //   3. DefaultContextSelector的getLoggerContext()直接返回defaultLoggerContext,
            //   4. ContextJNDISelector的getLoggerContext()可能为不同环境(contextName)提供不同的LoggerContext对象
            contextSelectorBinder.init(defaultLoggerContext, KEY);
            initialized = true;
        } catch (Exception t) {
            Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
        }
    }

说明:

  1. 核心初始化代码在 ContextInitializer的autoConfig()方法中执行
  2. ContextInitializer的autoConfig()会完成LoggerContext内属性的初始化
  3. contextSelectorBinder.init(…)方法作用是指定其属性contextSelector的实现. 若配置文件指定logback.ContextSelector=JNDI, 则为不同的context-name返回不同的LoggerContext对象(日志分离), 这里不展开

进入ContextInitializer的autoConfig()方法

    public void autoConfig() throws JoranException {
        // 若配置了logback.statusListenerClass, 则为loggerContext添加状态监听器 (不展开)
        StatusListenerConfigHelper.installIfAsked(loggerContext);
        // 获取配置文件URL
        URL url = findURLOfDefaultConfigurationFile(true);
        if (url != null) {
            // 核心代码: 通过配置文件完成配置
            configureByResource(url);
        } else {
            // 使用SPI方式加载Configurator的实现类. (不展开) spi拓展: https://blog.csdn.net/blueheartstone/article/details/128005322
            Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
            if (c != null) {
                try {
                    c.setContext(loggerContext);
                    c.configure(loggerContext);
                } catch (Exception e) {
                    throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass().getCanonicalName() : "null"), e);
                }
            } else {
                // 重要代码: 使用默认的配置BasicConfigurator. 默认配置见 [补充: 默认配置BasicConfigurator类]
                BasicConfigurator basicConfigurator = new BasicConfigurator();
                basicConfigurator.setContext(loggerContext);
                basicConfigurator.configure(loggerContext);
            }
        }
    }

说明:

  1. 先通过配置文件的方式获取配置信息, 获取到则执行configureByResource(url)方法完成配置
  2. 没有找到配置文件, 则使用SPI方式加载Configurator的实现类完成配置
  3. 上述两种都找不到, 则使用BasicConfigurator 默认配置

进入ContextInitializer的findURLOfDefaultConfigurationFile(.)方法

    public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
        ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
        // 获取指定的配置文件. 使用-Dlogback.configurationFile=xxx.xml 指定使用的配置文件(注, 必须的xml结尾的)
        URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
        if (url != null) {
            return url;
        }

        // 有logback-test.xml配置文件, 则取它
        url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
        if (url != null) {
            return url;
        }

        // 前面都没找到, 则找logback.xml配置文件
        return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
    }

说明:
读取配置文件有优先级. 下面按优先级高到低枚举, 有高优先级的则直接返回

  1. 获取指定的配置文件. 使用-Dlogback.configurationFile=xxx.xml 指定使用的配置文件(非xml结尾的文件无效)
  2. logback-test.xml
  3. logback.xml
    补充: springboot项目初始化也是这样执行, 但初始化执行到spring的监听器时, 会使用带有spring字眼的logback配置文件重新初始化LoggerContext. 详情见 [SpringBoot中logback的初始化流程]

进入ContextInitializer的configureByResource(URL url)方法

    public void configureByResource(URL url) throws JoranException {
        if (url == null) {
            throw new IllegalArgumentException("URL argument cannot be null");
        }
        final String urlString = url.toString();
        // 配置文件必须为xml结尾的文件
        if (urlString.endsWith("xml")) {
            // 使用JoranConfigurator加载配置文件, 完成配置
            // 注: springboot项目有logback-spring.xml等配置文件时, 在监听器初始化时使用SpringBootJoranConfigurator完成配置. 详情见 [SpringBoot中logback的初始化流程]
            JoranConfigurator configurator = new JoranConfigurator();
            configurator.setContext(loggerContext);
            // 核心代码: doConfigure(URL url)
            configurator.doConfigure(url);
        } else {
            throw new LogbackException("Unexpected filename extension of file [" + url.toString() + "]. Should be .xml");
        }
    }

进入GenericConfigurator的doConfigure(URL url)方法

了解: [GenericConfigurator类图]

    public final void doConfigure(URL url) throws JoranException {
        InputStream in = null;
        try {
            // 获取文件流
            informContextOfURLUsedForConfiguration(getContext(), url);
            URLConnection urlConnection = url.openConnection();
            urlConnection.setUseCaches(false);
            in = urlConnection.getInputStream();
            // 核心代码: doConfigure(InputStream inputStream, String systemId)
            doConfigure(in, url.toExternalForm());
        } catch (IOException ioe) {
        } finally {
        }
    }

进入GenericConfigurator的doConfigure(InputStream inputStream, String systemId)方法

    public final void doConfigure(InputStream inputStream, String systemId) throws JoranException {
        InputSource inputSource = new InputSource(inputStream);
        inputSource.setSystemId(systemId);
        // 核心代码: doConfigure(final InputSource inputSource)
        doConfigure(inputSource);
    }

进入GenericConfigurator的doConfigure(final InputSource inputSource)方法

    public final void doConfigure(final InputSource inputSource) throws JoranException {
        long threshold = System.currentTimeMillis();
        // 使用SAXParser解析配置文件, 得到saxEventList. (不展开)
        SaxEventRecorder recorder = new SaxEventRecorder(context);
        recorder.recordEvents(inputSource);
        // 核心代码: 执行配置
        doConfigure(recorder.saxEventList);
        // 没有XML解析错误发生时, 将当前配置注册为安全回退点. (不展开)
        StatusUtil statusUtil = new StatusUtil(context);
        if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
            addInfo("Registering current configuration as safe fallback point");
            registerSafeConfiguration(recorder.saxEventList);
        }
    }

进入GenericConfigurator的doConfigure(final List eventList)方法

    public void doConfigure(final List<SaxEvent> eventList) throws JoranException {
        // 重要代码: 构建解析器 (注: 假设GenericConfigurator的实现类为JoranConfigurator (默认值))
        buildInterpreter();
        // 核心代码: 上锁, 解析一个个SaxEvent事件(以栈的格式逐个处理配置文件中的各个标签解析出来的内容), 完成LoggerContext初始化
        synchronized (context.getConfigurationLock()) {
            interpreter.getEventPlayer().play(eventList);
        }
    }

进入JoranConfiguratorBase的buildInterpreter()方法

注: 假设GenericConfigurator的实现类为JoranConfigurator (默认值)

// 先进入子类JoranConfiguratorBase
abstract public class JoranConfiguratorBase<E> extends GenericConfigurator {
    protected void buildInterpreter() {
        super.buildInterpreter();
        Map<String, Object> omap = interpreter.getInterpretationContext().getObjectMap();
        omap.put(ActionConst.APPENDER_BAG, new HashMap<String, Appender<?>>());
    }
}
// 再回到父类GenericConfigurator
    protected void buildInterpreter() {
        // 创建规则存储实现对象
        RuleStore rs = new SimpleRuleStore(context);
        // 重要代码: 添加实例的常规规则
        // 说明: 添加规则示例: configuration/appender -> AppenderAction, configuration/logger -> LoggerAction
        // 用途: 例如AppenderActionAction是用来完成<appender>标签的解析和appender的初始化的
        addInstanceRules(rs);
        // 初始化出解析器对象, 该对象完成核心的SaxEvent解析工作
        this.interpreter = new Interpreter(context, rs, initialElementPath());
        // 初始化出解析器上下文对象, 该对象存储解析过程重要的信息
        InterpretationContext interpretationContext = interpreter.getInterpretationContext();
        interpretationContext.setContext(context);
        // 添加隐含的规则 (不展开)
        // 说明: 添加Action: NestedComplexPropertyIA 或 NestedBasicPropertyIA
        // 用途: 完成嵌套属性值的初始化和设置绑定, 例如设置appender内的encoder
        addImplicitRules(interpreter);
        // 添加默认嵌套组件注册表 (不展开)
        // 说明: 指定嵌套组件的默认实现, 例如UnsynchronizedAppenderBase的encoder属性默认实现为PatternLayoutEncoder
        addDefaultNestedComponentRegistryRules(interpretationContext.getDefaultNestedComponentRegistry());
    }

说明:

  1. 获取实例的常规规则, 添加到JoranConfigurator的interpreter的ruleStore中
    常规规则用于解析 appender, logger 等常规标签, 完成这些组件的初始化和绑定
  2. 获取隐含的规则, 添加到JoranConfigurator的interpreter的implicitActions中
    隐含规则用于解析 例如appender的内嵌标签, 例如encoder标签
  3. 获取默认嵌套组件注册表, 添加到JoranConfigurator的interpreter的interpretationContext的defaultNestedComponentRegistry的defaultComponentMap中
    默认嵌套组件注册表指定部分嵌套组件的实现

进入JoranConfigurator的addInstanceRules(RuleStore rs)

注: 假设GenericConfigurator的实现类为JoranConfigurator (默认值)

    public void addInstanceRules(RuleStore rs) {
        // 添加父类的规则
        super.addInstanceRules(rs);

        // 添加标签<configuration>的解析规则ConfigurationAction
        rs.addRule(new ElementSelector("configuration"), new ConfigurationAction());

        rs.addRule(new ElementSelector("configuration/contextName"), new ContextNameAction());
        rs.addRule(new ElementSelector("configuration/contextListener"), new LoggerContextListenerAction());
        rs.addRule(new ElementSelector("configuration/insertFromJNDI"), new InsertFromJNDIAction());
        rs.addRule(new ElementSelector("configuration/evaluator"), new EvaluatorAction());

        rs.addRule(new ElementSelector("configuration/appender/sift"), new SiftAction());
        rs.addRule(new ElementSelector("configuration/appender/sift/*"), new NOPAction());
        // 添加标签<configuration>内的<logger>的解析规则LoggerAction
        rs.addRule(new ElementSelector("configuration/logger"), new LoggerAction());
        rs.addRule(new ElementSelector("configuration/logger/level"), new LevelAction());

        rs.addRule(new ElementSelector("configuration/root"), new RootLoggerAction());
        rs.addRule(new ElementSelector("configuration/root/level"), new LevelAction());
        rs.addRule(new ElementSelector("configuration/logger/appender-ref"), new AppenderRefAction<ILoggingEvent>());
        rs.addRule(new ElementSelector("configuration/root/appender-ref"), new AppenderRefAction<ILoggingEvent>());
        
        rs.addRule(new ElementSelector("*/if"), new IfAction());
        rs.addRule(new ElementSelector("*/if/then"), new ThenAction());
        rs.addRule(new ElementSelector("*/if/then/*"), new NOPAction());
        rs.addRule(new ElementSelector("*/if/else"), new ElseAction());
        rs.addRule(new ElementSelector("*/if/else/*"), new NOPAction());
        if (PlatformInfo.hasJMXObjectName()) {
            rs.addRule(new ElementSelector("configuration/jmxConfigurator"), new JMXConfiguratorAction());
        }
        rs.addRule(new ElementSelector("configuration/include"), new IncludeAction());
        rs.addRule(new ElementSelector("configuration/consolePlugin"), new ConsolePluginAction());
        rs.addRule(new ElementSelector("configuration/receiver"), new ReceiverAction());

    }

进入JoranConfiguratorBase的addInstanceRules(RuleStore rs)

注: 假设GenericConfigurator的实现类为JoranConfigurator (默认值)

    protected void addInstanceRules(RuleStore rs) {
        rs.addRule(new ElementSelector("configuration/variable"), new PropertyAction());
        rs.addRule(new ElementSelector("configuration/property"), new PropertyAction());

        rs.addRule(new ElementSelector("configuration/substitutionProperty"), new PropertyAction());
        rs.addRule(new ElementSelector("configuration/timestamp"), new TimestampAction());
        rs.addRule(new ElementSelector("configuration/shutdownHook"), new ShutdownHookAction());
        rs.addRule(new ElementSelector("configuration/define"), new DefinePropertyAction());
        
        rs.addRule(new ElementSelector("configuration/contextProperty"), new ContextPropertyAction());

        rs.addRule(new ElementSelector("configuration/conversionRule"), new ConversionRuleAction());
        rs.addRule(new ElementSelector("configuration/statusListener"), new StatusListenerAction());

        // 添加标签<configuration>内的<appender>的解析规则AppenderAction
        rs.addRule(new ElementSelector("configuration/appender"), new AppenderAction<E>());
        rs.addRule(new ElementSelector("configuration/appender/appender-ref"), new AppenderRefAction<E>());
        rs.addRule(new ElementSelector("configuration/newRule"), new NewRuleAction());
        rs.addRule(new ElementSelector("*/param"), new ParamAction(getBeanDescriptionCache()));
    }

进入EventPlayer的play(List aSaxEventList)方法

前置说明:

  1. 这部分代码为正式的初始化, 代码非常的多, 篇幅非常的长, 有兴趣的C友可以往下看, 觉得不需要了解这么深的可直接跳到[默认配置BasicConfigurator类] 继续阅读此文
  2. 从paly方法开始, 逐行解析xml标签, 很多地方使用到栈, 设计非常巧妙
  3. 解析流程主要借助了Action组件, 该组件就是执行的动作, 类图见 [Action类图].
  4. 由于这里为遍历事件重复调用, 这里直接给出主要类的源码. 这里Action仅给出ContextNameAction、AppenderAction和NestedBasicPropertyIA的源码

EventPlayer类主要源码

public class EventPlayer {
    // 解析器
    final Interpreter interpreter;
    // xml解析出来的事件列表
    List<SaxEvent> eventList;
    // 当前解析事件的索引
    int currentIndex;
    
    public void play(List<SaxEvent> aSaxEventList) {
        eventList = aSaxEventList;
        SaxEvent se;
        // 遍历SaxEventList. 可以理解为逐个解析xml中的每一个标签(以栈的形式处理)
        for (currentIndex = 0; currentIndex < eventList.size(); currentIndex++) {
            se = eventList.get(currentIndex);
            // 处理开始事件, 示例: 解析<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            if (se instanceof StartEvent) {
                // 核心代码: 解析标签开始元素
                interpreter.startElement((StartEvent) se);
                // 将当前事件添加到所有解析器的监听器中待后续使用 (非核心代码, 不展开)
                interpreter.getInterpretationContext().fireInPlay(se);
            }
            // 处理body事件, 示例: 解析<pattern>${log.pattern}</pattern>中的${log.pattern}
            if (se instanceof BodyEvent) {
                // 将当前事件添加到所有解析器的监听器中待后续使用 (非核心代码, 不展开)
                interpreter.getInterpretationContext().fireInPlay(se);
                // 核心代码: 解析标签体内的字符串
                interpreter.characters((BodyEvent) se);
            }
            // 处理结束时事件, 示例: 解析</appender>
            if (se instanceof EndEvent) {
                // 将当前事件添加到所有解析器的监听器中待后续使用 (非核心代码, 不展开)
                interpreter.getInterpretationContext().fireInPlay(se);
                // 核心代码: 解析标签结束元素
                interpreter.endElement((EndEvent) se);
            }

        }
    }
}

说明:

  1. 开始事件, 主要执行逻辑 interpreter.startElement((StartEvent) se)
  2. body事件, 主要执行逻辑 interpreter.characters((BodyEvent) se)
  3. 结束事件, 主要执行逻辑 interpreter.endElement((EndEvent) se)
  4. 下图为解析xml得到的事件列表
    在这里插入图片描述

Interpreter类主要源码

public class Interpreter {
    private static List<Action> EMPTY_LIST = new Vector<Action>(0);
    // 实例规则仓库. 示例: [configuration][appender] -> AppenderAction
    final private RuleStore ruleStore;
    // 解析器上下文, 存储解析过程重要的信息
    final private InterpretationContext interpretationContext;
    // 隐晦的规则列表, 用于处理ruleStore不包含的嵌套属性解析初始化和绑定等
    final private ArrayList<ImplicitAction> implicitActions;
    // 保留定位器信息的记录类 (不重要)
    final private CAI_WithLocatorSupport cai;
    // 当前解析标签的路径, 例如 [configuration] [appender]. 解析过程中类似栈的使用
    private ElementPath elementPath;
    // 当前解析位于xml文件的位置
    Locator locator;
    // 事件解析执行器, 用于解析SaxEvent事件
    EventPlayer eventPlayer;
    // action列表的栈, 用于记录解析过程中的解析规则
    Stack<List<Action>> actionListStack;

    // skip指定的标签, 它的所有嵌套元素都会被跳过
    ElementPath skip = null;

    /**
     * 添加空的action列表占位 (入栈占位)
     */
    private void pushEmptyActionList() {
        actionListStack.add(EMPTY_LIST);
    }

    /**
     * 解析标签头(入口)
     */
    public void startElement(StartEvent se) {
        // 记录当前解析 位于xml文件的位置
        setDocumentLocator(se.getLocator());
        startElement(se.namespaceURI, se.localName, se.qName, se.attributes);
    }

    /**
     * 解析标签头
     */
    private void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
        // localName有值取localName, 没值取qName. 拓展: 示例<a bb:ccc="123"/> 其中cc就是本地名称localName, bb:ccc就是限定名称qName
        String tagName = getTagName(localName, qName);
        // 使用elementPath, 类似栈的形式记录当前解析标签的层级. 例如解析到<configuration>内的<appender>标签, 则elementPath伪栈中有configuration和appender
        elementPath.push(tagName);

        // 解析标签头异常时使用skip跳过 异常解析的标签的内嵌标签
        if (skip != null) {
            // 添加空的action列表占位 (入栈占位)
            pushEmptyActionList();
            return;
        }

        // 重要代码: 获取适用的Action列表, 一般都是返回一个元素. 先从ruleStore找常规的Action, 找不到再到implicitActions中找
        List<Action> applicableActionList = getApplicableActionList(elementPath, atts);
        if (applicableActionList != null) {
            // action事件列表进栈, 以便处理body和end事件时使用
            actionListStack.add(applicableActionList);
            // 核心代码: 执行action列表的begin事件
            callBeginAction(applicableActionList, tagName, atts);
        } else {
            // 添加空的action列表占位 (入栈占位)
            pushEmptyActionList();
            String errMsg = "no applicable action for [" + tagName + "], current ElementPath  is [" + elementPath + "]";
            cai.addError(errMsg);
        }
    }

    /**
     * 执行action列表的begin事件
     */
    void callBeginAction(List<Action> applicableActionList, String tagName, Attributes atts) {
        if (applicableActionList == null) {
            return;
        }

        // 遍历执行action的begin事件, 一般一个标签都是只有一个Action的. 这里源码仅看AppenderAction
        Iterator<Action> i = applicableActionList.iterator();
        while (i.hasNext()) {
            Action action = (Action) i.next();
            try {
                // 核心代码
                action.begin(interpretationContext, tagName, atts);
            } catch (ActionException e) {
                // 解析标签开始时就异常则记录当前标签路径, 防止其内嵌的标签继续解析
                skip = elementPath.duplicate();
                cai.addError("ActionException in Action for tag [" + tagName + "]", e);
            } catch (RuntimeException e) {
                // 解析标签开始时就异常则记录当前标签路径, 防止其内嵌的标签继续解析
                skip = elementPath.duplicate();
                cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
            }
        }
    }


    /**
     * 解析标签体 (标签体就是 字符串)
     */
    public void characters(BodyEvent be) {
        // 记录当前解析 位于xml文件的位置
        setDocumentLocator(be.locator);

        String body = be.getText();
        // 读出startElement时加入的栈顶Action列表. 注意, 这里仅读出
        List<Action> applicableActionList = actionListStack.peek();

        if (body != null) {
            body = body.trim();
            if (body.length() > 0) {
                // 核心代码: 执行action列表的body事件
                callBodyAction(applicableActionList, body);
            }
        }
    }

    /**
     * 核心代码: 执行action列表的body事件
     */
    private void callBodyAction(List<Action> applicableActionList, String body) {
        if (applicableActionList == null) {
            return;
        }
        // 遍历执行action的body事件, 一般一个标签都是只有一个Action的. 这里源码仅看ContextNameAction
        Iterator<Action> i = applicableActionList.iterator();
        while (i.hasNext()) {
            Action action = i.next();
            try {
                // 核心代码
                action.body(interpretationContext, body);
            } catch (ActionException ae) {
                cai.addError("Exception in end() methd for action [" + action + "]", ae);
            }
        }
    }


    /**
     * 解析标签尾(入口)
     */
    public void endElement(EndEvent endEvent) {
        // 记录当前解析 位于xml文件的位置
        setDocumentLocator(endEvent.locator);
        endElement(endEvent.namespaceURI, endEvent.localName, endEvent.qName);
    }

    /**
     * 解析标签尾
     */
    private void endElement(String namespaceURI, String localName, String qName) {
        // 弹出startElement时加入的栈顶Action列表
        List<Action> applicableActionList = (List<Action>) actionListStack.pop();

        // 若当前标签路径和skip中记录的解析异常标签头路径一致时, 则表示该标签闭环了(内嵌都遍历完了), 设置skip为空
        if (skip != null) {
            if (skip.equals(elementPath)) {
                skip = null;
            }
        } else if (applicableActionList != EMPTY_LIST) {
            // 核心代码: 执行action列表的end事件
            callEndAction(applicableActionList, getTagName(localName, qName));
        }

        // 当前解析的标签路径出栈 (父层级还是保留的)
        elementPath.pop();
    }

    /**
     * 执行action列表的end事件
     */
    private void callEndAction(List<Action> applicableActionList, String tagName) {
        if (applicableActionList == null) {
            return;
        }

        // 遍历执行action的end事件, 一般一个标签都是只有一个Action的. 这里源码仅看AppenderAction
        Iterator<Action> i = applicableActionList.iterator();
        while (i.hasNext()) {
            Action action = i.next();
            try {
                // 核心代码
                action.end(interpretationContext, tagName);
            } catch (ActionException ae) {
                cai.addError("ActionException in Action for tag [" + tagName + "]", ae);
            } catch (RuntimeException e) {
                cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
            }
        }
    }


    /**
     * 到implicitActions中查找符合的Action. 有返回时仅返回一个元素
     */
    List<Action> lookupImplicitAction(ElementPath elementPath, Attributes attributes, InterpretationContext ec) {
        int len = implicitActions.size();
        // 遍历列表, 其实列表就只有NestedComplexPropertyIA和NestedBasicPropertyIA
        for (int i = 0; i < len; i++) {
            ImplicitAction ia = (ImplicitAction) implicitActions.get(i);
            // 若属性为简单类型 或 静态set和add方法属性则返回NestedBasicPropertyIA, 反之返回NestedComplexPropertyIA
            // 这里的简单类型包含八大基本类型, Void, Enum, Charset, java.lang包下对象等
            if (ia.isApplicable(elementPath, attributes, ec)) {
                List<Action> actionList = new ArrayList<Action>(1);
                actionList.add(ia);
                return actionList;
            }
        }
        return null;
    }

    /**
     * 获取适用的Action列表
     */
    List<Action> getApplicableActionList(ElementPath elementPath, Attributes attributes) {
        // 通过当前解析标签的路径, 从ruleStore中获取匹配的Action列表
        List<Action> applicableActionList = ruleStore.matchActions(elementPath);

        // ruleStore找不到, 则到implicitActions中查找, 根据上级标签映射的对象和当前标签的名字查找. 这里返回的列表只有一个元素
        if (applicableActionList == null) {
            applicableActionList = lookupImplicitAction(elementPath, attributes, interpretationContext);
        }

        return applicableActionList;
    }
}

InterpretationContext类主要源码

解析器上下文作为仅存储解析过程重要的信息, 可以理解为缓存工具类

public class InterpretationContext extends ContextAwareBase implements PropertyContainer {
    // 对象栈, 存储记录解析过程中标签解析出来的对象, 完成该对象所有属性赋值则出栈
    Stack<Object> objectStack;
    // 记录临时的标签对象. 例如先解析出Appender, 则会暂存在map中, 键值对为 APPENDER_BAG -> {console=consoleAppender对象, ...}
    Map<String, Object> objectMap;
    // 记录解析出来的<property>标签的属性. 示例: log.path -> /logs/logback-study
    Map<String, String> propertiesMap;
    // 解析器对象
    Interpreter joranInterpreter;
    // 监听器列表, 用于执行inlay
    final List<InPlayListener> listenerList = new ArrayList<InPlayListener>();
    // 默认嵌套组件注册表. 指定嵌套组件如AppenderBase的layout, encoder以及SSl等属性的默认实现
    DefaultNestedComponentRegistry defaultNestedComponentRegistry = new DefaultNestedComponentRegistry();
    
    /**
     * 添加property属性, 存在则覆盖
     */
    public void addSubstitutionProperty(String key, String value) {
        if (key == null || value == null) {
            return;
        }
        value = value.trim();
        propertiesMap.put(key, value);
    }

    /**
     * 批量添加property属性
     */
    public void addSubstitutionProperties(Properties props) {
        if (props == null) {
            return;
        }
        for (Object keyObject : props.keySet()) {
            String key = (String) keyObject;
            String val = props.getProperty(key);
            addSubstitutionProperty(key, val);
        }
    }

    /**
     * 获取property属性值
     */
    public String getProperty(String key) {
        String v = propertiesMap.get(key);
        if (v != null) {
            return v;
        } else {
            return context.getProperty(key);
        }
    }

    /**
     * 从上下文, propertiesMap等中获取属性值
     * 可以解析{}内的内容出来
     */
    public String subst(String value) {
        if (value == null) {
            return null;
        }
        return OptionHelper.substVars(value, this, context);
    }

    public boolean isListenerListEmpty() {
        return listenerList.isEmpty();
    }

    public void addInPlayListener(InPlayListener ipl) {
        if (listenerList.contains(ipl)) {
            addWarn("InPlayListener " + ipl + " has been already registered");
        } else {
            listenerList.add(ipl);
        }
    }

    public boolean removeInPlayListener(InPlayListener ipl) {
        return listenerList.remove(ipl);
    }

    void fireInPlay(SaxEvent event) {
        for (InPlayListener ipl : listenerList) {
            ipl.inPlay(event);
        }
    }
}

ContextNameAction类主要源码

public class ContextNameAction extends Action {
    @Override
    public void begin(InterpretationContext ec, String name, Attributes attributes) {
    }
    
    @Override
    public void body(InterpretationContext ec, String body) {
        // 解析出消息体内的内容. subst方法可以解析出{}内的值
        String finalBody = ec.subst(body);
        try {
            // 设置LoggerContext的名字
            context.setName(finalBody);
        } catch (IllegalStateException e) {
        }
    }
    
    @Override
    public void end(InterpretationContext ec, String name) {
    }
}

AppenderAction类主要源码

public class AppenderAction1<E> extends Action {
    Appender<E> appender;
    private boolean inError = false;

    @Override
    public void begin(InterpretationContext ec, String localName, Attributes attributes) throws ActionException {
        appender = null;
        inError = false;
        // 获取<appender>的class属性
        String className = attributes.getValue(CLASS_ATTRIBUTE);
        if (OptionHelper.isEmpty(className)) {
            inError = true;
            return;
        }

        try {
            // 反射创建Appender对象
            appender = (Appender<E>) OptionHelper.instantiateByClassName(className, Appender.class, context);
            appender.setContext(context);

            // 获取<appender>的name属性并设置
            String appenderName = ec.subst(attributes.getValue(NAME_ATTRIBUTE));
            if (OptionHelper.isEmpty(appenderName)) {
            } else {
                appender.setName(appenderName);
            }

            // 将appender寄存到 InterpretationContext的objectMap中. 键值对为 APPENDER_BAG -> {console=consoleAppender对象, ...}
            HashMap<String, Appender<E>> appenderBag = (HashMap<String, Appender<E>>) ec.getObjectMap().get(ActionConst.APPENDER_BAG);
            appenderBag.put(appenderName, appender);
            // 同时将appender对象压入objectStack栈中待后续处理内嵌标签时使用
            ec.pushObject(appender);
        } catch (Exception oops) {
            inError = true;
            throw new ActionException(oops);
        }
    }

    /**
     * 子元素都被解析之后,现在激活appender其他属性
     */
    @Override
    public void end(InterpretationContext ec, String name) {
        if (inError) {
            return;
        }
        if (appender instanceof LifeCycle) {
            // 执行appender的start方法, 不展开
            ((LifeCycle) appender).start();
        }

        // 当前操作的appender和objectStack栈顶一致时, 弹出
        Object o = ec.peekObject();
        if (o != appender) {
        } else {
            ec.popObject();
        }
    }
}

NestedBasicPropertyIA类主要源码

public class NestedBasicPropertyIA extends ImplicitAction {
    // 记录action的解析时需要使用到数据的栈.
    //   其中IADataForBasicProperty包含当前action要解析的属性 的类型、名称、父类的bean构建的PropertySetter
    Stack<IADataForBasicProperty> actionDataStack = new Stack<IADataForBasicProperty>();
    // bean描述器的缓存器
    private final BeanDescriptionCache beanDescriptionCache;

    public NestedBasicPropertyIA1(BeanDescriptionCache beanDescriptionCache) {
        this.beanDescriptionCache = beanDescriptionCache;
    }

    /**
     * 判断NestedBasicPropertyIA是否适用
     */
    @Override
    public boolean isApplicable(ElementPath elementPath, Attributes attributes, InterpretationContext ec) {
        // 获取当前标签的名字
        String nestedElementTagName = elementPath.peekLast();
        if (ec.isEmpty()) {
            return false;
        }

        // 获取当前action要解析的属性 的父类bean, 构建PropertySetter对象, 用于判断属性参数类型和反射赋值
        Object o = ec.peekObject();
        PropertySetter parentBean = new PropertySetter(beanDescriptionCache, o);
        parentBean.setContext(context);

        // 获取当前action要解析的属性的类型 (不展开)
        AggregationType aggregationType = parentBean.computeAggregationType(nestedElementTagName);

        // NestedBasicPropertyIA仅支持处理 属性为简单类型的 或 静态set和add方法属性
        //   这里的简单类型包含八大基本类型, Void, Enum, Charset, java.lang包下对象等
        switch (aggregationType) {
        case NOT_FOUND:
        case AS_COMPLEX_PROPERTY:
        case AS_COMPLEX_PROPERTY_COLLECTION:
            return false;

        case AS_BASIC_PROPERTY:
        case AS_BASIC_PROPERTY_COLLECTION:
            // 构建IADataForBasicProperty, 缓存到actionDataStack栈中, 供NestedBasicPropertyIA此Action执行start、body、end方法时使用
            IADataForBasicProperty ad = new IADataForBasicProperty(parentBean, aggregationType, nestedElementTagName);
            actionDataStack.push(ad);
            return true;
        default:
            addError("PropertySetter.canContainComponent returned " + aggregationType);
            return false;
        }
    }

    @Override
    public void begin(InterpretationContext ec, String localName, Attributes attributes) {
        // NOP
    }

    public void body(InterpretationContext ec, String body) {
        // 获取具体的属性值和actionData
        String finalBody = ec.subst(body);
        IADataForBasicProperty actionData = (IADataForBasicProperty) actionDataStack.peek();
        switch (actionData.aggregationType) {
        case AS_BASIC_PROPERTY:
            // 根本进不来这个方法
            actionData.parentBean.setProperty(actionData.propertyName, finalBody);
            break;
        case AS_BASIC_PROPERTY_COLLECTION:
            // 只会执行该case. 反射的方式设置对象的属性值
            actionData.parentBean.addBasicProperty(actionData.propertyName, finalBody);
            break;
        default:
            addError("Unexpected aggregationType " + actionData.aggregationType);
        }
    }

    public void end(InterpretationContext ec, String tagName) {
        // 处理结束, 弹出缓存的action内的IADataForBasicProperty数据
        actionDataStack.pop();
    }
}

提一嘴NestedComplexPropertyIA

  1. NestedComplexPropertyIA的代码和NestedBasicPropertyIA类似
  2. NestedComplexPropertyIA多出的部分就是会为属性实例化和绑定部分值, 还有最重要的是在end方法中会执行属性的start方法, 这里就涉及到了一个很重要的(Appender的encoder重要属性layout初始化), 请看[Appender的encoder重要属性layout初始化源码]

补充: Appender的encoder重要属性layout初始化源码

layout初始化入口

public class NestedComplexPropertyIA extends ImplicitAction {
    public void end(InterpretationContext ec, String tagName) {
        IADataForComplexProperty actionData = (IADataForComplexProperty) actionDataStack.pop();
        // ...

		// 获取解析出来的属性对象. 这里假设属性对象为PatternLayoutEncoder实例
        Object nestedComplexProperty = actionData.getNestedComplexProperty();
        if (nestedComplexProperty instanceof LifeCycle && NoAutoStartUtil.notMarkedWithNoAutoStart(nestedComplexProperty)) {
        	// 执行PatternLayoutEncoder的start()方法
            ((LifeCycle) nestedComplexProperty).start();
        }

        Object o = ec.peekObject();
		// ...
    }
}

说明:

  1. 解析appender的encoder结束时, 会执行encoder的start()方法
  2. 未声明encoder的实现类时, 属于UnsynchronizedAppenderBase的Appender其encoder实现类都是PatternLayoutEncoder
  3. 未声明layout的实现类时, 属于UnsynchronizedAppenderBase的Appender其layout实现类都是PatternLayout(本文未涉及)
  4. 下面进入PatternLayoutEncoder的start()方法

进入atternLayoutEncoder的start()方法

public class PatternLayoutEncoder extends PatternLayoutEncoderBase<ILoggingEvent> {
    @Override
    public void start() {
        // 创建PatternLayout对象
        PatternLayout patternLayout = new PatternLayout();
        patternLayout.setContext(context);
        patternLayout.setPattern(getPattern());
        patternLayout.setOutputPatternAsHeader(outputPatternAsHeader);
        // 初始化layout
        patternLayout.start();
        this.layout = patternLayout;
        super.start();
    }
}

进入patternLayout父类PatternLayoutBase的start()方法

abstract public class PatternLayoutBase<E> extends LayoutBase<E> {
    // 转换器执行链. 用于日志输出时拼接出最终的日志内容
    Converter<E> head;
    // xml配置的日志模式
    String pattern;

	abstract public Map<String, String> getDefaultConverterMap();

    public void start() {
        if (pattern == null || pattern.length() == 0) {
            addError("Empty or null pattern.");
            return;
        }
        try {
            // 由于此文篇幅太长, Parser类的代码不展开
            // 初始化布局解析器, 使用状态机模式完成Parser的tokenList初始化(将pattern解析成一个个token对象). 不展开
            Parser<E> p = new Parser<E>(pattern);
            if (getContext() != null) {
                p.setContext(getContext());
            }
            // 将Parser的tokenList解析成Node链表. 不展开
            Node t = p.parse();
            // 核心代码: 获取有效的关键字映射的转换器全限定类名, 使用Node链表 解析出 转换器链. compile方法不展开
            this.head = p.compile(t, getEffectiveConverterMap());
            if (postCompileProcessor != null) {
                // 没有异常转换器则在转换器链尾添加一个
                postCompileProcessor.process(context, head);
            }
            // 设置所有转换器上下文属性
            ConverterUtil.setContextForConverters(getContext(), head);
            // 设置所有转换器start属性为true
            ConverterUtil.startConverters(this.head);
            super.start();
        } catch (ScanException sce) {
            StatusManager sm = getContext().getStatusManager();
            sm.add(new ErrorStatus("Failed to parse pattern \"" + getPattern() + "\".", this, sce));
        }
    }

    public Map<String, String> getEffectiveConverterMap() {
        Map<String, String> effectiveMap = new HashMap<String, String>();
        // 添加默认的转换器实现映射 (主要来源)
        Map<String, String> defaultMap = getDefaultConverterMap();
        if (defaultMap != null) {
            effectiveMap.putAll(defaultMap);
        }
        // 添加初始化过程中添加到LoggerContext的objectMap中key为PATTERN_RULE_REGISTRY的转换器实现映射
        Context context = getContext();
        if (context != null) {
            @SuppressWarnings("unchecked")
            Map<String, String> contextMap = (Map<String, String>) context.getObject(CoreConstants.PATTERN_RULE_REGISTRY);
            if (contextMap != null) {
                effectiveMap.putAll(contextMap);
            }
        }
        // 添加实例的转换器实现映射. 例如使用了SyslogAppender, 就会添加syslogStart -> ch.qos.logback.classic.pattern.SyslogStartConverter
        effectiveMap.putAll(instanceConverterMap);
        return effectiveMap;
    }
}

说明:

  1. 在start方法中, 会先实例化一个布局解析器Parser对象, 并使用交换机模式解析pattern属性设置到Parser的tokenList属性上
  2. 通过调用Parser的parse方法, 将tokenList属性解析成Node链表
  3. 获取有效的关键字 与 转换器全限定名的映射, 调用Parser的compile方法解析出 转换器执行链, 绑定到appender的encoder的layout上
  4. 其中getDefaultConverterMap()获取到了什么呢, 我们进入PatternLayout瞧瞧

进入PatternLayout的getDefaultConverterMap()方法

public class PatternLayout1 extends PatternLayoutBase<ILoggingEvent> {
    // 默认的转换器实现映射. 关键字 -> 转换器实现全限定类名
    public static final Map<String, String> DEFAULT_CONVERTER_MAP = new HashMap<String, String>();
    // 转换器实现全限定类名 -> 关键字
    public static final Map<String, String> CONVERTER_CLASS_TO_KEY_MAP = new HashMap<String, String>();

	public Map<String, String> getDefaultConverterMap() {
        return DEFAULT_CONVERTER_MAP;
    }

    // 详情配置可查看官方文档: https://logback.qos.ch/manual/layouts.html
    static {
        // 添加Parser中的两个. BARE -> ch.qos.logback.core.pattern.IdentityCompositeConverter, replace -> ch.qos.logback.core.pattern.ReplacingCompositeConverter
        DEFAULT_CONVERTER_MAP.putAll(Parser.DEFAULT_COMPOSITE_CONVERTER_MAP);
        // 输出时间. 使用示例: %d, %date{HH:mm:ss}. 默认时间格式: yyyy-MM-dd HH:mm:ss,SSS
        DEFAULT_CONVERTER_MAP.put("d", DateConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("date", DateConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(DateConverter.class.getName(), "date");
        // 输出当前输出日志时间 - 日志系统启动后的时间差. 示例示例: %r, %relative
        DEFAULT_CONVERTER_MAP.put("r", RelativeTimeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("relative", RelativeTimeConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(RelativeTimeConverter.class.getName(), "relative");
        // 输出日志等级. 示例: %level, %5le %-5level
        DEFAULT_CONVERTER_MAP.put("level", LevelConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("le", LevelConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("p", LevelConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(LevelConverter.class.getName(), "level");
        // 输出日志所在的线程. 示例: %t, %thread
        DEFAULT_CONVERTER_MAP.put("t", ThreadConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("thread", ThreadConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(ThreadConverter.class.getName(), "thread");
        // 输出logger名字. 示例: %lo, %logger, %c{36}
        DEFAULT_CONVERTER_MAP.put("lo", LoggerConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("logger", LoggerConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("c", LoggerConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(LoggerConverter.class.getName(), "logger");
        // 输出用户添加的原日志内容. 示例: %m, %msg, %message
        DEFAULT_CONVERTER_MAP.put("m", MessageConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("msg", MessageConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("message", MessageConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(MessageConverter.class.getName(), "message");
        // 输出发出日志请求的类的全限定名称. 示例: %C, %class{20}
        DEFAULT_CONVERTER_MAP.put("C", ClassOfCallerConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("class", ClassOfCallerConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(ClassOfCallerConverter.class.getName(), "class");
        // 输出发出日志请求的方法名. 示例: %M, %method
        DEFAULT_CONVERTER_MAP.put("M", MethodOfCallerConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("method", MethodOfCallerConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(MethodOfCallerConverter.class.getName(), "method");
        // 输出发出日志请求所在的行号. 示例: %L, %line
        DEFAULT_CONVERTER_MAP.put("L", LineOfCallerConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("line", LineOfCallerConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(LineOfCallerConverter.class.getName(), "line");
        // 输出发出日志请求的 Java 源文件名. 示例: %F, %file
        DEFAULT_CONVERTER_MAP.put("F", FileOfCallerConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("file", FileOfCallerConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(FileOfCallerConverter.class.getName(), "file");
        // 输出映射调试上下文信息, 常用于日志追踪. 示例: %X{traceId}, %mdc{traceId:-默认值}. mdc拓展: https://blog.csdn.net/Erica_java/article/details/128616137
        DEFAULT_CONVERTER_MAP.put("X", MDCConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("mdc", MDCConverter.class.getName());
        // 输出与日志记录事件关联的异常的堆栈跟踪(如果有的话). 示例: %ex, %exception{2}
        DEFAULT_CONVERTER_MAP.put("ex", ThrowableProxyConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("exception", ThrowableProxyConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("rEx", RootCauseFirstThrowableProxyConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("rootException", RootCauseFirstThrowableProxyConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("throwable", ThrowableProxyConverter.class.getName());
        // 输出与日志记录事件关联的异常的堆栈跟踪(如果有的话)和类封装信息. 示例: %xEx, %xException{2}
        DEFAULT_CONVERTER_MAP.put("xEx", ExtendedThrowableProxyConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("xException", ExtendedThrowableProxyConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("xThrowable", ExtendedThrowableProxyConverter.class.getName());
        // 此转换器不输出任何数据(?多余的东西). 示例: %nopex, %nopexception
        DEFAULT_CONVERTER_MAP.put("nopex", NopThrowableInformationConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("nopexception", NopThrowableInformationConverter.class.getName());
        // 输出logger上下文名称. 示例: %cn, %contextName
        DEFAULT_CONVERTER_MAP.put("cn", ContextNameConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("contextName", ContextNameConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(ContextNameConverter.class.getName(), "contextName");
        // 输出生成日志的调用者所在的位置信息. 示例: %caller, %caller{2}
        DEFAULT_CONVERTER_MAP.put("caller", CallerDataConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(CallerDataConverter.class.getName(), "caller");
        // 输出与记录器请求关联的标记. 示例: %marker
        DEFAULT_CONVERTER_MAP.put("marker", MarkerConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(MarkerConverter.class.getName(), "marker");
        // 输出属性 key 所对应的值, 先从日志记录器上下文找, 找不到再从系统属性中找. 示例: %property{user.name}
        DEFAULT_CONVERTER_MAP.put("property", PropertyConverter.class.getName());
        // 输出当前平台的换行符. 示例: %n
        DEFAULT_CONVERTER_MAP.put("n", LineSeparatorConverter.class.getName());
        // 以给定的颜色样式输出内容. 示例: %yellow(%d), %yellow(中国人不打中国人)
        DEFAULT_CONVERTER_MAP.put("black", BlackCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("red", RedCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("green", GreenCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("yellow", YellowCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("blue", BlueCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("magenta", MagentaCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("cyan", CyanCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("white", WhiteCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("gray", GrayCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("boldRed", BoldRedCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("boldGreen", BoldGreenCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("boldYellow", BoldYellowCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("boldBlue", BoldBlueCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("boldMagenta", BoldMagentaCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("boldCyan", BoldCyanCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("boldWhite", BoldWhiteCompositeConverter.class.getName());
        DEFAULT_CONVERTER_MAP.put("highlight", HighlightingCompositeConverter.class.getName());
        // 输出自增序列, 初始值为该转换器实例化时的时间戳. 示例: %lsn
        DEFAULT_CONVERTER_MAP.put("lsn", LocalSequenceNumberConverter.class.getName());
        CONVERTER_CLASS_TO_KEY_MAP.put(LocalSequenceNumberConverter.class.getName(), "lsn");
        // 对于%prefix括号包含的所有子转换器,在每个转换器的输出前加上转换器的名称. 示例: %prefix(%d)
        DEFAULT_CONVERTER_MAP.put("prefix", PrefixCompositeConverter.class.getName());

    }
}

说明:

  1. 通过源码可见, 指定了一个个关键字的转换器实现类, 这里列出了各个转换器的使用和含义
  2. 若想进一步了解每个转换器如何使用, 读者们可以自行查看源码或查看官方文档(https://logback.qos.ch/manual/layouts.html)了解使用

补充: 默认配置BasicConfigurator类

public class BasicConfigurator extends ContextAwareBase implements Configurator {
    public BasicConfigurator() {
    }
    public void configure(LoggerContext lc) {
        addInfo("Setting up default configuration.");
        // 初始化一个Appender -> ConsoleAppender
        ConsoleAppender<ILoggingEvent> ca = new ConsoleAppender<ILoggingEvent>();
        ca.setContext(lc);
        ca.setName("console");
        // 初始化encoder对象
        LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<ILoggingEvent>();
        encoder.setContext(lc);
        
        // 设置encoder的布局layout属性
        // TTLLLayout 类似于使用PatternLayout(其中pattern = %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n)
        TTLLLayout layout = new TTLLLayout();
        layout.setContext(lc);
        layout.start();
        encoder.setLayout(layout);
        
        // 设置ConsoleAppender的encoder
        ca.setEncoder(encoder);
        ca.start();
        // 初始化根logger并设置appender
        Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
        rootLogger.addAppender(ca);
    }
}

补充: BasicConfigurator 的配置等价于下面的xml配置

<configuration>
    <!-- 模拟logback默认配置 ch.qos.logback.classic.BasicConfigurator -->

	<!-- 控制台输出 -->
	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
		<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
			<layout class="ch.qos.logback.classic.layout.TTLLLayout"/>
		</encoder>
	</appender>

	<!--系统操作日志-->
    <root level="debug">
        <appender-ref ref="console" />
    </root>
</configuration>

进入LoggerContext的getLogger(String name)方法

前置说明:

  1. logger是链式结构, 所有的非根logger其最顶的祖先都是根logger
  2. 每个logger都有一个属性childrenList, 维护其下的所有直属子logger
  3. 每个logger都有一个AppenderAttachableImpl aai属性, 维护其绑定的appender列表
  4. 以上关系较为简单, 就不画类图展示了, 我们直接看源码

入口说明:

  1. 在LoggerFactory中调用了getILoggerFactory()方法
  2. 初始化成功后, 会返回StaticLoggerBinder.getSingleton().getLoggerFactory(), 这里返回的就是LoggerContext
  3. 然后iLoggerFactory.getLogger(name)即LoggerContext的getLogger(String name)方法
public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle {
    // 根logger
    final Logger root;
    // logger总数
    private int size;
    // logger缓存
    private Map<String, Logger> loggerCache;

    public LoggerContext() {
        //...
        this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
        this.root.setLevel(Level.DEBUG);
        //...
    }

    @Override
    public final Logger getLogger(final String name) {
        if (name == null) {
            throw new IllegalArgumentException("name argument cannot be null");
        }
        // 如果获取的是根logger则直接返回. 根logger在LoggerContext构造器中就创建出来(name=ROOT, level=DEBUG)
        if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
            return root;
        }

        // 缓存有则从缓存中获取
        Logger childLogger = (Logger) loggerCache.get(name);
        if (childLogger != null) {
            return childLogger;
        }

        // 根据入参name获取logger. 示例name=com.chenlongji.Test, 假设初始化时只存在一个根root
        //    则这里会先按"."切割name, 依次得到com, com.chenlongji, com.chenlongji.Test
        //    这里会先创建出name为com的logger, 作为root的子logger.
        //    再创建name为com.chenlongji的logger, 作为logger(com)的子logger, 直到创建出目标logger(com.chenlongji.Test)
        int i = 0;
        Logger logger = root;
        String childName;
        while (true) {
            // 按照"."切割name(示例: name=com.chenlongji.Test). 循环中依次获得com, com.chenlongji, com.chenlongji.Test
            int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
            if (h == -1) {
                childName = name;
            } else {
                childName = name.substring(0, h);
            }
            // 查找"."的索引右移
            i = h + 1;
            synchronized (logger) {
                // 根据childName获取当前节点的子节点(不展开), 没有则创建
                childLogger = logger.getChildByName(childName);
                if (childLogger == null) {
                    // 根据名字创建出新的节点, 并写入缓存
                    childLogger = logger.createChildByName(childName);
                    loggerCache.put(childName, childLogger);
                    // 累计上下文中logger的个数
                    incSize();
                }
            }
            // 将子logger切换为当前循环引用对象, 继续找
            logger = childLogger;
            // 已经创建出或查找到目标logger, 返回
            if (h == -1) {
                return childLogger;
            }
        }
    }

    private void incSize() {
        size++;
    }
}

说明:

  1. 逻辑较简单, 直接看注释

进入Logger的createChildByName(final String childName)方法

    Logger createChildByName(final String childName) {
        // 判断当前logger的name 和 要创建logger的name之间不能存在多余的"."
        // 例如当前logger(com), 要创建的logger(com.Test), 从Test的T开始往后是没有多余的"."了
        int i_index = LoggerNameUtil.getSeparatorIndexOf(childName, this.name.length() + 1);
        if (i_index != -1) {
            throw new IllegalArgumentException("For logger [" + this.name + "] child name [" + childName
                    + " passed as parameter, may not include '.' after index" + (this.name.length() + 1));
        }
        // 子logger列表为空, 为其初始化
        if (childrenList == null) {
            childrenList = new CopyOnWriteArrayList<Logger1>();
        }
        // 创建子节点, 并绑定到childrenList上
        Logger childLogger = new Logger(childName, this, this.loggerContext);
        childrenList.add(childLogger);
        // 子类继承父类的有效level等级
        childLogger.effectiveLevelInt = this.effectiveLevelInt;
        return childLogger;
    }

说明:

  1. 逻辑较简单, 直接看注释

四. 输出日志源码

入口

前置说明

  1. 这里仅从info方法开始看源码, 其他方法也是一样的
public class Test {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Test1.class);
        // 入口
        logger.info("我是error级别的日志");
    }

}

进入Logger的info(String msg)方法

    public void info(String msg) {
        // 过滤和输出日志
        filterAndLog_0_Or3Plus(FQCN, null, Level.INFO, msg, null, null);
    }

进入Logger的filterAndLog_0_Or3Plus(…)方法

    /**
     * 了解: 时间优化到极致的体现
     * filterAndLog有三个方法:
     *  filterAndLog_0_Or3Plus
     *  filterAndLog_1
     *  filterAndLog_2
     * 其中filterAndLog_1和filterAndLog_2中创建了一个Object[]新对象, filterAndLog_0_Or3Plus中不需要创建该对象
     * 节省Object[]对象的创建可以节省约20纳秒
     */
    private void filterAndLog_0_Or3Plus(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
                                        final Throwable t) {
        // 判断是否可以通过上下文的过滤器. logback初始化完成后该方法都是返回NEUTRAL, 不展开
        final FilterReply decision = loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg, params, t);

        if (decision == FilterReply.NEUTRAL) {
            // logback初始化完成后都会进入该位置. 这里判断当前logger的level是否 大于 输出日志的level, 大于则结束
            if (effectiveLevelInt > level.levelInt) {
                return;
            }
        } else if (decision == FilterReply.DENY) {
            return;
        }
        // 核心代码: 构建LoggingEvent并输出日志
        buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
    }

进入Logger的buildLoggingEventAndAppend(…)方法

    private void buildLoggingEventAndAppend(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
                                            final Throwable t) {
        // 构建LoggingEvent对象, 该对象包含了日志内容、日志级别、当前logger对象、线程名称等信息
        LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
        // 设置标记信息(较少使用)
        le.setMarker(marker);
        // 核心代码: 调用appender执行日志输出
        callAppenders(le);
    }

进入Logger的callAppenders(ILoggingEvent event)方法

    public void callAppenders(ILoggingEvent event) {
        int writes = 0;
        // 若没有提前中断, 则递归当前logger的执行链, 一直到root
        for (Logger1 l = this; l != null; l = l.parent) {
            // 核心代码: 获取当前logger下的所有appender, 遍历输出日志
            writes += l.appendLoopOnAppenders(event);
            // 若当前logger的additive属性为false, 则不再调用父类logger输出日志
            if (!l.additive) {
                break;
            }
        }
        // 递归的logger都没找到一个appender, 则添加警告信息(系统启动后仅能添加一次). 输出警告信息需要配置configuration下的statusListener
        if (writes == 0) {
            loggerContext.noAppenderDefinedWarning(this);
        }
    }

进入AppenderAttachableImpl的appendLoopOnAppenders(E e)方法

    public int appendLoopOnAppenders(E e) {
        int size = 0;
        // 从appenderList拷贝出appender数组, 通过fresh原子属性控制安全性, 感兴趣的可以看下, 这里不展开
        final Appender<E>[] appenderArray = appenderList.asTypedArray();
        final int len = appenderArray.length;
        for (int i = 0; i < len; i++) {
            // 核心代码: 遍历执行一个个appender的doAppend方法
            // 住: 这里我们仅看下RollingFileAppender是如何处理的
            appenderArray[i].doAppend(e);
            size++;
        }
        // 返回当前logger的appender个数
        return size;
    }

进入UnsynchronizedAppenderBase的doAppend(E eventObject)方法

前置说明:

  1. 这里我们仅看RollingFileAppender的处理逻辑
  2. appender相关的类图请看[Appender类图]
  3. 异步的日志输出器AsyncAppender功能请看[Appender的UnsynchronizedAppenderBase的实现类]简单描述
    public void doAppend(E eventObject) {
        // 使用ThreadLocal确保当前doAppend防重入 (不改源码正常调用其实不会出现重入问题)
        if (Boolean.TRUE.equals(guard.get())) {
            return;
        }

        try {
            // 设置为true防重入
            guard.set(Boolean.TRUE);
            // 确保当前appender已完成初始化
            if (!this.started) {
                if (statusRepeatCount++ < ALLOWED_REPEATS) {
                    addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this));
                }
                return;
            }
            // 获取当前appender的所有过滤器, 判断是否可以通过. 较简单不展开
            if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
                return;
            }

            // 核心代码: 执行日志输出
            this.append(eventObject);
        } catch (Exception e) {
            if (exceptionCount++ < ALLOWED_REPEATS) {
                addError("Appender [" + name + "] failed to append.", e);
            }
        } finally {
            // 重置为false, 允许进入
            guard.set(Boolean.FALSE);
        }
    }

进入OutputStreamAppender的append(E eventObject)方法

    protected void append(E eventObject) {
        // 确保当前appender已完成初始化 (因为其他线程使用该appender出错时start属性就变成了false)
        if (!isStarted()) {
            return;
        }
        // 核心代码: 执行日志输出
        subAppend(eventObject);
    }

进入RollingFileAppender的subAppend(E event)方法

    protected void subAppend(E event) {
        // 同步代码块确保判断和滚动时线程安全
        synchronized (triggeringPolicy) {
            // 重要代码: 判断是否达到滚动时机
            if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event)) {
                // 重要代码: 滚动文件
                rollover();
            }
        }
        // 核心代码: 执行父类代码进行日志输出
        super.subAppend(event);
    }

注: 假设指定了rollingPolicy为TimeBasedRollingPolicy类型, 没有指定triggeringPolicy, 则RollingFileAppender下主要属性关系如下图
在这里插入图片描述
在这里插入图片描述

进入TimeBasedRollingPolicy的isTriggeringEvent(File, E)方法

public class TimeBasedRollingPolicy<E> extends RollingPolicyBase implements TriggeringPolicy<E> {
    TimeBasedFileNamingAndTriggeringPolicy<E> timeBasedFileNamingAndTriggeringPolicy;
    
	public void start() {
	    //...
        if (timeBasedFileNamingAndTriggeringPolicy == null) {
        	// 指定TimeBasedFileNamingAndTriggeringPolicy的实现类
            timeBasedFileNamingAndTriggeringPolicy = new DefaultTimeBasedFileNamingAndTriggeringPolicy<E>();
        }
        timeBasedFileNamingAndTriggeringPolicy.setContext(context);
        // 缓存了TimeBasedRollingPolicy到其tbrp字段上
        timeBasedFileNamingAndTriggeringPolicy.setTimeBasedRollingPolicy(this);
        timeBasedFileNamingAndTriggeringPolicy.start();
        //...
    }
	
    public boolean isTriggeringEvent(File activeFile, final E event) {
    	// 调用DefaultTimeBasedFileNamingAndTriggeringPolicy的isTriggeringEvent(File, E)方法
        return timeBasedFileNamingAndTriggeringPolicy.isTriggeringEvent(activeFile, event);
    }
}

进入DefaultTimeBasedFileNamingAndTriggeringPolicy的isTriggeringEvent(File, E)方法

    public boolean isTriggeringEvent(File activeFile, final E event) {
        long time = getCurrentTime();
        // 判断时间是否超过 上次算出来的下次滚动时间. 这里是根据输入的滚动文件时间格式获取滚动时间间隔, 逻辑与log4j类似, 这里不展开
        //   获取时间间隔单位逻辑(RollingCalendar.computePeriodicityType): 
        //      遍历的时间单位从小到大判断(毫秒->秒...->月)
        //          取一个1970年0点时间epoch, 按照配置的滚动日期格式格式化得到ro,
        //          epoch加上遍历单位1个单位的值, 再按照配置的滚动日期格式格式化得到r1,
        //          判断两个日期是否相等, 如果不相等, 则返回当前遍历的时间单位 (类似于整数舍弃余数的方式 判断累加数是否会使整数值变化)
        if (time >= nextCheck) {
            // 获取上次的进入isTriggeringEvent方法获取的时间戳time
            Date dateOfElapsedPeriod = dateInCurrentPeriod;
            addInfo("Elapsed period: " + dateOfElapsedPeriod);
            // 获取需要滚动文件的替换文件名. 这里涉及到了转换器Converter的初始化和使用, 本文讲述layout的地方也讲到, 故不展开
            elapsedPeriodsFileName = tbrp.fileNamePatternWithoutCompSuffix.convert(dateOfElapsedPeriod);
            // 刷新dateInCurrentPeriod=time, 不展开
            setDateInCurrentPeriod(time);
            // 刷新下次滚动时间 nextCheck, 不展开
            computeNextCheck();
            return true;
        } else {
            return false;
        }
    }

进入RollingFileAppender的rollover()方法

    public void rollover() {
        // 上锁. 关流和打开文件必须在同一个代码块内, 不关流(打开的文件)无法完成重命名
        lock.lock();
        try {
            // 关闭输出流. 不展开
            this.closeOutputStream();
            // 核心代码: 尝试滚动文件
            attemptRollover();
            // 打开新文件, 设置输出流. 不展开
            attemptOpenFile();
        } finally {
            lock.unlock();
        }
    }

进入RollingFileAppender的attemptRollover()方法

    private void attemptRollover() {
        try {
            // 滚动
            rollingPolicy.rollover();
        } catch (RolloverFailure rf) {
            addWarn("RolloverFailure occurred. Deferring roll-over.");
            this.append = true;
        }
    }

进入TimeBasedRollingPolicy的rollover()方法

    public void rollover() throws RolloverFailure {
        // 取出需要滚动文件的替换文件名, 在前面isTriggeringEvent方法已经得到该名字了
        String elapsedPeriodsFileName = timeBasedFileNamingAndTriggeringPolicy.getElapsedPeriodsFileName();
        // 获取elapsedPeriodsFileName中最后"/"后的文件名
        String elapsedPeriodStem = FileFilterUtil.afterLastSlash(elapsedPeriodsFileName);
        // 配置的滚动文件名没有gz. zip后缀, 则不需要压缩
        if (compressionMode == CompressionMode.NONE) {
            // getParentsRawFileProperty()获取的是<file>标签体内的文件名
            if (getParentsRawFileProperty() != null) {
                // 直接修改文件名. 注意: 系统禁止的文件名符号重命名会失败, 例如window系统下":"
                renameUtil.rename(getParentsRawFileProperty(), elapsedPeriodsFileName);
            }
        } else {
            if (getParentsRawFileProperty() == null) {
                // 使用压缩器进行文件压缩. 不展开
                //    这里使用日志上下文中的线程池异步进行文件压缩
                //    gz包使用java的GZIPOutputStream压缩, zip包使用java的ZipOutputStream压缩
                compressionFuture = compressor.asyncCompress(elapsedPeriodsFileName, elapsedPeriodsFileName, elapsedPeriodStem);
            } else {
                // 先将滚动文件改为临时文件名, 再进行压缩(执行compressor.asyncCompress), 不展开
                compressionFuture = renameRawAndAsyncCompress(elapsedPeriodsFileName, elapsedPeriodStem);
            }
        }

        if (archiveRemover != null) {
            Date now = new Date(timeBasedFileNamingAndTriggeringPolicy.getCurrentTime());
            // 使用归档删除器将过期的文件删除掉, future用于appender销毁时阻塞线程. 不展开
            //   这里会使用日志上下文中的线程池异步进行文件删除.
            //   删除过程会调用clean方法删除超出时间范围内的文件(文件保留数为maxHistory+1), capTotalCap会删除超过总文件大小的旧文件(需配置maxHistory和totalSizeCap才有效)
            this.cleanUpFuture = archiveRemover.cleanAsynchronously(now);
        }
    }

说明:

  1. compressor和archiveRemover都是使用异步方式执行, 感兴趣的可自行看下这部分代码, 篇幅问题不再展开

回到OutputStreamAppender的subAppend(E event)方法

    protected void subAppend(E event) {
        // 确保当前appender已完成初始化(因为其他线程使用该appender出错时start属性就变成了false)
        if (!isStarted()) {
            return;
        }
        try {
            // 设置LoggerEvent的内容, 线程名, 和mdc属性值. 不展开
            if (event instanceof DeferredProcessingAware) {
                ((DeferredProcessingAware) event).prepareForDeferredProcessing();
            }

            // 使用编码器的解析出最终的日志内容. 这里由于每个转换器都保证了线程安全, 故该方法不用上锁
            // 注: 后续假设Encoder的实现类为PatternLayoutEncoder
            byte[] byteArray = this.encoder.encode(event);
            // 使用流输出日志信息. 为避免多线程出现写入写出, 该方法需要使用同步锁
            writeBytes(byteArray);
        } catch (IOException ioe) {
            // 异常则该appender就关闭掉了
            this.started = false;
            addStatus(new ErrorStatus("IO failure in appender", this, ioe));
        }
    }

进入LayoutWrappingEncoder的encode(E event)方法

前置说明:

  1. 假设Encoder的实现类为PatternLayoutEncoder (UnsynchronizedAppenderBase下appender的encoder的默认实现类)
    public byte[] encode(E event) {
        // 使用layout解析出最终的日志内容
        String txt = layout.doLayout(event);
        // 将最终的日志内容转为byte数组返回. 不展开
        return convertToBytes(txt);
    }

进入PatternLayout的doLayout(ILoggingEvent event)方法

前置说明:

  1. 假设Layout的实现类为PatternLayout(UnsynchronizedAppenderBase下appender的encoder的layout的默认实现类)
    public String doLayout(ILoggingEvent event) {
        // 确保layout已启动
        if (!isStarted()) {
            return CoreConstants.EMPTY_STRING;
        }
        // 使用转化器链获取实际日志内容
        return writeLoopOnConverters(event);
    }

进入PatternLayoutBase的writeLoopOnConverters(E event)方法

    protected String writeLoopOnConverters(E event) {
        StringBuilder strBuilder = new StringBuilder(INTIAL_STRING_BUILDER_SIZE);
        // 遍历转换器执行链解析日志内容
        Converter<E> c = head;
        while (c != null) {
            // 使用该转换器 获取该转换器 转换出来的部分内容, 加入strBuilder中. 下面我们找一个DateConverter看下源码
            c.write(strBuilder, event);
            c = c.getNext();
        }
        return strBuilder.toString();
    }

进入DateConverter父类FormattingConverter的write(StringBuilder buf, E event)方法

前置说明:

  1. 后面的Converter示例都是使用DateConverter来演示
    final public void write(StringBuilder buf, E event) {
        // 执行子类convert方法获取内容
        String s = convert(event);
        // 根据配置的格式格式化内容
        // FormatInfo属性
        //  min: 小数点前的正整数, 表示最小长度. min前面的"-"表示leftPad取反, 即左对齐
        //  max: 小数点后的正整数, 表示最大长度. max前面的"-"表示leftTruncate取反, 即截掉右边, 保留左边(长度超出时)
        //  leftPad: 左边加空格, 即右对齐
        //  leftTruncate: 截掉左边, 保留右边(长度超出时)
        //  示例说明:
        //      %-2.5method表示输出的方法名 最小长度为2, 最大长度为5, 左对齐(小于最小长度右边补空格), 超出长度截掉左边, 保留右边
        //          "s"方法                    -> "s "
        //          "soLongMethodNameIsMy"方法 -> "eIsMy"
        //      %2.-5method表示输出的方法名 最小长度为2, 最大长度为3, 右对齐(小于最小长度左边补空格), 超出长度截掉右边, 保留左边
        //          "s"方法                    -> " s"
        //          "soLongMethodNameIsMy"方法 -> "soLon"
        if (formattingInfo == null) {
            // 没有配置格式化信息, 直接返回
            buf.append(s);
            return;
        }
        // 小数点左边的数字 (示例%-2.5level中的 2)
        int min = formattingInfo.getMin();
        // 小数点右边的数字 (示例%-2.5level中的 5)
        int max = formattingInfo.getMax();

        if (s == null) {
            if (0 < min) {
                // 补充min个空格. 不展开
                SpacePadder.spacePad(buf, min);
            }
            return;
        }

        int len = s.length();
        // max控制最大长度. 超出长度则截取
        if (len > max) {
            if (formattingInfo.isLeftTruncate()) {
                // 截掉左边, 保留右边
                buf.append(s.substring(len - max));
            } else {
                // 截掉右边, 保留左边
                buf.append(s.substring(0, max));
            }
        // min控制最小长度. 小于最小长度则添加空格
        } else if (len < min) {
            if (formattingInfo.isLeftPad()) {
                // 右对齐, 则左边加空格. 不展开
                SpacePadder.leftPad(buf, s, min);
            } else {
                // 左对齐, 则右边加空格. 不展开
                SpacePadder.rightPad(buf, s, min);
            }
        } else {
            buf.append(s);
        }
    }

说明:

  1. 格式化示例说明
    [%-2.5method]: 输出方法名, 长度在[2, 5]范围内, 长度小于2则右边补空格, 长度超过5则截左边留右边
    [s] -> [s ]
    [abcdef] -> [bcdef]
    [%2.-5method]: 输出方法名, 长度在[2, 5]范围内, 长度小于2则左边补空格, 长度超过5则截右边留左边
    [s] -> [ s]
    [abcdef] -> [abcde]

进入DateConverter的convert(ILoggingEvent le)方法

    public String convert(ILoggingEvent le) {
        long timestamp = le.getTimeStamp();
        // 使用之前就设置好的DateFormatter格式化时间
        return cachingDateFormatter.format(timestamp);
    }

进入OutputStreamAppender的writeBytes(byte[] byteArray)方法

    private void writeBytes(byte[] byteArray) throws IOException {
        if(byteArray == null || byteArray.length == 0) {
            return;
        }
        // 上锁, 避免多线程写入写出时出现错误
        lock.lock();
        try {
            // 写入输出流中
            this.outputStream.write(byteArray);
            // 若immediateFlush=true, 立即写出. 该属性默认值为true
            if (immediateFlush) {
                this.outputStream.flush();
            }
        } finally {
            lock.unlock();
        }
    }

五. 拓展

1. SpringBoot中logback的初始化流程

见另一篇文章: SpringBoot中logback的初始化流程(待出)

2. 为什么使用lombok的@Slf4j注解后就可以直接使用log对象打印日志

简而言之, 就是贴有@Slf4j注解的在编译是会为该类生产一个成员变量log
在这里插入图片描述

本文不讲具体内容, 刚兴趣请查看为什么加了@slf4j注解 就可以直接使用log:

六. 附件

1. SpringBoot环境下logback-spring.xml配置文件一般配置

<?xml version="1.0" encoding="UTF-8"?>
<!-- 配置详情: https://blog.csdn.net/weixin_41377777/article/details/120962037 -->
<!-- 拷贝即可使用: 额外需要关注的就三个点, 1.配置spring.application.name可指定文件前缀名 2.修改dev的springProfile中自定义logger的name指定当前项目 3.修改rollingPolicy的MaxHistory确定日志保留数 -->

<configuration>
	<property name="LOG_PATH" value="/apps/logs" />
	<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %highlight(%level) [%C{36}.%M:%line] tid=%X{traceId}, %msg%n" />
	<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="defaultAppName"/>

	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>${LOG_PATTERN}</pattern>
		</encoder>
	</appender>

	<!--本地环境 -->
	<springProfile name="dev">
		<logger name="com.chenlongji" level="DEBUG" />
		<!--<logger name="org.mybatis" level="DEBUG" />-->

		<root level="INFO">
			<appender-ref ref="CONSOLE" />
		</root>
	</springProfile>

	<!--测试环境 -->
	<springProfile name="test">
		<!-- 备注: 若springProfile仅激活一个, 那appender的name重复也不影响, 因为没激活的profile里面的appender不会进行初始化 -->
		<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
			<!-- layout使用encoder配置应该也行, 如CONSOLE的配置 -->
			<layout class="ch.qos.logback.classic.PatternLayout">
				<pattern>${LOG_PATTERN}</pattern>
			</layout>
			<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
				<FileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.log</FileNamePattern>
				<MaxHistory>1</MaxHistory>
			</rollingPolicy>
			<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
				<!-- 过滤的级别: info及以上 -->
				<level>INFO</level>
			</filter>
		</appender>

		<root level="INFO">
			<appender-ref ref="FILE" />
		</root>
	</springProfile>

	<!--生产环境 -->
	<springProfile name="prd">
		<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
			<layout class="ch.qos.logback.classic.PatternLayout">
				<pattern>${LOG_PATTERN}</pattern>
			</layout>
			<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
				<FileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.log</FileNamePattern>
				<MaxHistory>7</MaxHistory>
			</rollingPolicy>
			<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
				<!-- 过滤的级别: info及以上 -->
				<level>INFO</level>
			</filter>
		</appender>

		<root level="INFO">
			<appender-ref ref="FILE" />
		</root>
	</springProfile>

</configuration>
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值