使用logback时,由于配置缘故出现No appenders present in context [default] for logger [xxxxx]的问题,导致通过getLogger(class)获取到的logger没有打印日志。问题部分配置如下:STDOUT是ConsoleAppender省略了
<logger name="tom.vertx" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
先说结论:
该问题出现的原因是我配置了tom.vertx包下面的类对应的logger,然后该包下的类通过getLogger(class)获取logger时,获取到的logger是logger名称为tom.vertx的子孙,但是由于additivity是false,所以不会使用上级logger的appender,而通过getLogger(class)获取到的logger默认在初始化创建时并没有指定appender,所以就出现No appenders...的问题,解决方式就是additivity设置为true或者getLogger(name)方式直接通过logger名字获取已配置的logger。
Slf4j-Logback执行流程
结合实际使用简单说一下执行流程。在通过LoggerFactory.getLogger(...)时,第一次时会有以下执行流程对logback进行初始化操作。(slf4j-api 2.0.0-alpha1 logback-core/classic 1.3.0-alpha5)
- (LoggerFactory) getILoggerFactory() 获取logger工厂类
- (LoggerFactory) getProvider() 同步初始化
- (LoggerFactory) performInitialization() 初始化操作
- (LoggerFactory) bind() 查找并绑定provider
- (LoggerFactory) findServiceProviders() 通过jdk spi技术 ServiceLoader加载位于META-INF/services/下的接口定义文件,获取SLF4JServiceProvider实实现类,logback是LogbackServiceProvider
- (LoggerFactory) 调用provider.initialize()方法初始化LoggerContext等信息
- (LogbackServiceProvider) initializeLoggerContext() 初始化logback配置
- (ContextInitializer) new ContextInitializer(context).autoConfig() 扫描配置文件,如果-Dlogback.configurationFile=xx.xml/groovy配置i文件直接使用配置文件,否则查找classpath下logback.groovy、logback.xml、logback-test.xml配置文件,如果都没有找到,使用spi技术查找Configurator的配置实现类,如果还没有则使用BasicConfigurator初始化基本配置,设置默认root logger的appender为ConsoleAppender并设置loggerContext。
- (ContextInitializer) configureByResource(url) 如果找到配置文件使用该方法读取解析配置文件,对于xml使用JoranConfigurator生成配置类,并初始化配置
- (JoranConfigruator) doConfigure(url),初始化配置,实际上调用的父类GenericConfigurator的doConfigure(url)方法
- (GenericConfigurator) playEventsAndProcessModel(event), 处理生成配置 、规则
- (GenericConfigurator) processModel(), 生成配置模型Model(xml中的每个标签都对应一个模型类),注册模型处理器(通过JoranConfigurator.buildDefaultProcessor方法), 并通过模型对应的handler.handle方法处理模型生成模型处理类如appender、logger等。
- (DefaultProcessor) process(), 模型处理,实例化模型handler,并通过handler.handle和handler.postHandle处理model模型并初始化。
- (JoranConfigruator) doConfigure(url),初始化配置,实际上调用的父类GenericConfigurator的doConfigure(url)方法
- (ContextInitializer) configureByResource(url) 如果找到配置文件使用该方法读取解析配置文件,对于xml使用JoranConfigurator生成配置类,并初始化配置
- (ContextInitializer) new ContextInitializer(context).autoConfig() 扫描配置文件,如果-Dlogback.configurationFile=xx.xml/groovy配置i文件直接使用配置文件,否则查找classpath下logback.groovy、logback.xml、logback-test.xml配置文件,如果都没有找到,使用spi技术查找Configurator的配置实现类,如果还没有则使用BasicConfigurator初始化基本配置,设置默认root logger的appender为ConsoleAppender并设置loggerContext。
- (LogbackServiceProvider) initializeLoggerContext() 初始化logback配置
- (LoggerFactory) bind() 查找并绑定provider
- (LoggerFactory) performInitialization() 初始化操作
- (LoggerFactory) getProvider() 同步初始化
- (LoggerFactory) getLogger(class) 通过LoggerFactory获取logger,此时的LoggerFactory是LoggerContext对象
- (LoggerContext) getLogger(name) 通过class.getName的类名调用getLogger(name)获取logger
- 首先判断name==ROOT,如果是直接返回,否则继续
- 通过loggerCache获取name对应的logger,如果有直接返回(loggerCache是个Map),否则继续
- 循环通过( . 或者 $ )符号分割解析name,从root logger开始,通过logger.getChildByName获取子logger,如果没有则创建并加入到当前logger的孩子列表。例如:
-
# 假如传入的类为tom.vertx.util.MainRunnerTest,则最终生成的logger如下: ROOT :parent=> null tom :parent=> Logger[ROOT] tom.vertx :parent=> Logger[tom] tom.vertx.util :parent=> Logger[tom.vertx] tom.vertx.util.MainRunnerTest :parent=> Logger[tom.vertx.util] # 通过getLogger返回的logger为最后生成的logger,name是tom.vertx.util.MainRunnerTest
-
name解析完成返回传入name对应的logger对象。
- (LoggerContext) getLogger(name) 通过class.getName的类名调用getLogger(name)获取logger
-
(Logger) log.debug/info/warn/error/fatal 方法调用,最终会将传入的参数包装为一个LoggingEvent对象传给所有logger
-
(Logger) callAppenders(event) 事件分发,根据additive属性判断是否事件向上传递。true是,false否。
-
public void callAppenders(ILoggingEvent event) { int writes = 0; //循环从当前logger获取其上级logger,并调用该链条上logger的appendLoopOnAppenders方法 for (Logger l = this; l != null; l = l.parent) { //循环将事件发给logger内的所有appender,每次调用返回值就是有多少个appender处理了该事件 //如果返回0,证明该logger没有配置appender writes += l.appendLoopOnAppenders(event); //如果logger的additive属性是false,则事件不会再往上传递,跳出循环 if (!l.additive) { break; } } // No appenders in hierarchy // 如果writes=0,说明log事件没有被任何一个appender处理,就导致了No appenders pres... if (writes == 0) { loggerContext.noAppenderDefinedWarning(this); } } private int appendLoopOnAppenders(ILoggingEvent event) { //aai这个就是一个持有当前logger内所有appender的对象 //如果当前logger配置了appender则aai不为空,否则为空 //通过class类名获取时,没有配置class类对应的logger时,aai总是为空 //所以如果没配置class对应的logger并且additive=false,则该logger aai总是null //由于additive为false,不会再向上传递事件,就会导致日志没打印,有WARN: No appenders..问题 if (aai != null) { return aai.appendLoopOnAppenders(event); } else { return 0; } }
-