环境:springboot-2.3.1
加载日志监听器初始化日志框架
SpringApplication#prepareEnvironment
SpringApplicationRunListeners#environmentPrepared
EventPublishingRunListener#environmentPrepared
SimpleApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType)
获取各类监听器(其中就有一个 LoggingApplicationListener 监听器),执行onApplicationEvent
方法
如何获取各类监听器?该怎么添加自定义监听器?
看源码。是通过this.defaultRetriever.applicationListeners匹配过滤后得到的,这个是通过spring.factories机制获取org.springframework.context.ApplicationListener 得到并在 SpringApplication 构造函数中调用设置
因此,自定义时,在自己项目classpath路径创建META-INF/spring.factories文件,内容为
org.springframework.context.ApplicationListener=自定义监听器的全限定名
上述监听器是参考AnsiOutputApplicationListener。自定义监听器最终能够实现org.springframework.context.ApplicationListener<E extends ApplicationEvent>
接口即可(一般需要排序,再加上org.springframework.core.Ordered接口),具体依据实际需求来定义。
刚启动时,LoggingApplicationListener 就会执行 onApplicationEnvironmentPreparedEvent --> initialize
日志框架加载
LoggingApplicationListener#initialize
LoggingSystemProperties#apply(LogFile logFile) 中预置了一些系统属性,所以在一些日志中才有${XXX}
引入有效,如${PID}
LoggingApplicationListener#initializeSystem
默认读取classpath日志配置文件,如logback日志系统默认配置(下图所示,读取第一个存在的配置;否则在此基础上,读取含-spring的对应配置,如logback-spring.xml);若是配置了logging.config,直接读取该配置文件。
logback日志
在logback-spring.xml中,使用
${key:-默认值}
形式表示获取变量key
的值或者默认值(通过:-
分隔),其中key
就是普通字符串,像那些xxx.xxx.xxx形式的可以,本身名称就是这个(一般而言将json结构扁平化处理就是这种格式),并不是代表获取json结构(如Map嵌套)中对应层的值(刚开始以为可以这样的😂 看源码实际并不支持)
LogbackLoggingSystem#loadConfiguration
LogbackLoggingSystem#configureByResourceUrl
GenericConfigurator#doConfigure(java.net.URL)
… 经过一些列doConfigure重载方法
GenericConfigurator#doConfigure(java.util.List<ch.qos.logback.core.joran.event.SaxEvent>)
EventPlayer#play
Interpreter#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
这里根据标签获取对应的Action来解析属性,如xml中的标签对应着[configuration]、[configuration][property]、[configuration][appender]、[configuration][root]、[configuration][logger][appender-ref]等key,都有关联这个一个处理类
Interpreter#callBeginAction
property标签对应[configuration][property],关联PropertyAction类
PropertyAction#begin
InterpretationContext#subst
OptionHelper#substVars(java.lang.String, PropertyContainer, PropertyContainer)
NodeToStringTransformer#substituteVariable
NodeToStringTransformer#transform
NodeToStringTransformer#compileNode
NodeToStringTransformer#handleVariable
compileNode方法和handleVariable存在着递归:节点属于变量就会调用handleVariable;属于文本就直接拼接
NodeToStringTransformer#lookupKey
private String lookupKey(String key) {
// 从ch.qos.logback.core.joran.spi.InterpretationContext.getProperty上下文中获取
String value = this.propertyContainer0.getProperty(key);
if (value != null) {
return value;
} else {
if (this.propertyContainer1 != null) {
// 从ch.qos.logback.classic.LoggerContext.getProperty上下文中获取
value = this.propertyContainer1.getProperty(key);
if (value != null) {
return value;
}
}
// 从系统属性System.getProperty获取
value = OptionHelper.getSystemProperty(key, (String)null);
if (value != null) {
return value;
} else {
// 从系统环境变量System.getenv获取
value = OptionHelper.getEnv(key);
return value != null ? value : null;
}
}
}
logback出现“
变量名_IS_UNDEFINED
”情况,就是在handleVariable方法处理时,既没有变量值(lookupKey方法返回null),又没有默认值,就出现这个问题