本文基于slf4j 1.7.25
目录
logback妙用之自定义Converter、异步打印日志
logback源码解析分为两条线:一、加载解析配置文件;二、使用Logger打印日志。
0. 基本概念介绍
- Appender:定义日志的格式和输出目的地。
- Logger:输出日志的对象,关联上Appender执行相关操作。(一个Logger可以关联多个Appender)
1. 简单实用示例
一般我们会配置一个logback.xml,在程序中使用。
-
代码示例
public class Test { private final static Logger LOGGER = LoggerFactory.getLogger(Test.class); public void test(){ LOGGER.info("xxx"); } }
-
logback.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="false"> <springProperty scope="context" name="logLevel" source="logging.level.root"/> <property name="log.pattern" value="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%t] [%level] [%logger{80}] [%X{transactionId}] [%X{spanId}] [%X{parentId}] [%X{serviceId}] [%X{protocol}] [%X{logType}] - %m%n"/> <property name="additivity.value" value="false"/> <!-- 标准 --> <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>${log.pattern}</pattern> </layout> </appender> <appender name="accessapender" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${log.access}/access.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern> ${log.access}/access.log.%d{yyyy-MM-dd-HH}.%i </FileNamePattern> <TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <MaxFileSize>5MB</MaxFileSize> </TimeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <encoder> <pattern>${log.pattern}</pattern> <charset>UTF-8</charset> </encoder> </appender> <logger name="access" additivity="${additivity.value}" level="${logLevel}"> <appender-ref ref="accessapender"/> </logger> <root level="${logLevel}"> <appender-ref ref="accessapender"/> </root> </configuration>
2. 加载解析配置logback配置文件源码解析
-
我们先从private final static Logger LOGGER = LoggerFactory.getLogger(Test.class);跟进去
LoggerFactory
public final class LoggerFactory { public static Logger getLogger(Class<?> clazz) { //跟进去,内部方法 Logger logger = getLogger(clazz.getName()); return logger; } public static Logger getLogger(String name) { //获取日志工厂,我们看一下 ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); } public static ILoggerFactory getILoggerFactory() { //判断状态,如果没初始化,先初始化,不过多关注 if (INITIALIZATION_STATE == UNINITIALIZED) { //... } switch (INITIALIZATION_STATE) { case SUCCESSFUL_INITIALIZATION: //初始化成功,获取日志工厂.我们看看StaticLoggerBinder这个单例类 return StaticLoggerBinder.getSingleton().getLoggerFactory(); //... } throw new IllegalStateException("Unreachable code"); } }
-
StaticLoggerBinder
public class StaticLoggerBinder implements LoggerFactoryBinder { //日志上下文 private LoggerContext defaultLoggerContext = new LoggerContext(); //该类有静态代码块,先执行 static { SINGLETON.init(); } void init() { //ContextInitializer,初始化日志上下文,解析logback.xml文件,我们看看autoConfig new ContextInitializer(defaultLoggerContext).autoConfig(); contextSelectorBinder.init(defaultLoggerContext, KEY); initialized = true; } }
-
ContextInitializer
public class ContextInitializer { public void autoConfig() throws JoranException { StatusListenerConfigHelper.installIfAsked(loggerContext); // 从classpath下获取logback配置文件的地址,默认按顺序查找logback-test.xml,logback.groovy,logback.xml // 如果为空,则默认返回一个( // SpringBoot的是自定义了一个查找解析配置文件的类,继承了logback的,所以此处初始化解析类流程有些许不同 URL url = findURLOfDefaultConfigurationFile(true); if (url != null) { //加载解析配置文件,看看 configureByResource(url); } else { //... } } public void configureByResource(URL url) throws JoranException { //... //如果是xml后缀的logback文件,利用JoranConfigurator来解析(SpringBoot是继承了该类来解析) else if (urlString.endsWith("xml")) { JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(loggerContext); //我们就不进去看了 configurator.doConfigure(url); } } }
-
加载解析配置文件后续不看了,但是我们总结一下加载解析配置文件(GenericConfigurator类):
-
用sax解析xml,并将解析事件转换成SaxEvent(StartEvent:开始标签,BodyEvent:标签属性,EndEvent:结束标签),放入saxEventList。
-
创建Interpreter,遍历saxEventList,匹配相应SaxEvent执行Interpreter中相应的动作(标签中的class实例化成对象,子标签会当成父标签的属性设置进去;标签里面的属性也会设置到标签对象中)
-
不同的标签会对应不同的Action(AppenderAction,ConfigurationAction,LoggerAcction,RootLoggerAction,PropertyAction),JoranConfigurator中也注册了很多Action(保存在RuleStore中)。执行相应Action的操作,我们可以看到Action中begin、body、end方法就是对应开始标签、标签属性、结束标签执行的动作。
public abstract class Action extends ContextAwareBase { public abstract void begin(InterpretationContext ic, String name, Attributes attributes) throws ActionException; public void body(InterpretationContext ic, String body) throws ActionException { } public abstract void end(InterpretationContext ic, String name) throws ActionException; }
-
-
所以到此结束后,logback.xml文件解析完了。其中定义的类实例化了,属性也赋值了 。
-
获取Logger,我们从获取LoggerFactory代码可以看到获取的LoggerFactory其实就是LoggerContext。然后从LoggerContext获取相应的Logger。
3. 加载解析配置文件拓展点
上面我们知道每个xml标签都可以对应一个Action来处理,如果我们要自定义自己的标签,也可以对应一个Action来处理。
3.1 标签对应复杂对象,默认用NestedComplexPropertyIA解析执行
-
logback.xml。如下自定义Appender,自定义标签testService。TestService有一个属性url,TestAppender有一个属性TestService。
<appender name="test" class="com.zeng.utils.TestAppender"> <File>${log.collection}/spring.log</File> <encoder> <pattern>${log.pattern}</pattern> </encoder> <testService class="com.zeng.utils.TestService"> <url>http://xxx</url> </testService> </appender>
-
TestService
public class TestService { private String url; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
-
TestAppender
public class TestAppender extends RollingFileAppender { private TestService testService; public TestService getTestService() { return testService; } public void setTestService(TestService testService) { this.testService = testService; } }
-
不是应该在RuleStore中添加一个testService标签对应一个Action吗?其实也不一定需要。如果testService标签对应是一个复杂对象,logback默认会使用NestedComplexPropertyIA来解析该标签。我们可以看到如下图解析出来的对象都已经赋值好了。
3.2 标签对应的是简单对象,默认用NestedBasicPropertyIA解析执行
就不举例了,只需吧上面例子中TestService换成一个简单的String类型即可。
4. 使用Logger打印流程解析及拓展点
-
通过Logger打印日志,我们就不做解析了。画一个图解析一下
我们通过上面的图EnCoder.doEncoder用来编码,Layout.doLayout用来格式化日志。我们重点关注红色框中的subAppend。因为我们如果要自定义一个Appender,主要逻辑也是在subAppend中实现
-
示例(我们把上面的例子改一下)
TestService
public class TestService { private String url; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } //发送http请求,代码省略 public void httpRequest() { } }
TestAppender
public class TestAppender extends RollingFileAppender { private TestService testService; public TestService getTestService() { return testService; } public void setTestService(TestService testService) { this.testService = testService; } //重写subAppend方法,写日志时,发送请求,event中包含日志相关信息。 @Override protected void subAppend(Object event) { testService.httpRequest(); super.subAppend(event); } }
5. 总结
我们通过以上的源码解析,大致了解了logback的配置解析加载,和logback打印日志。
清楚了如果自定义logback.xml标签和自定义Appender。