环境: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),又没有默认值,就出现这个问题
logback-spring.xml示例(部分说明来自网络)
<?xml version="1.0" encoding="UTF-8"?>
<!-- 分级别异步文件日志输出配置 -->
<!-- 级别从高到低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
<!-- 日志输出规则 根据当前ROOT 级别,日志输出时,级别高于root默认的级别时 会输出 -->
<!-- 以下 每个配置的 filter 是过滤掉输出文件里面,会出现高级别文件,依然出现低级别的日志信息,通过filter 过滤只记录本级别的日志 -->
<!-- scan 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 -->
<!-- scanPeriod 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
<!--<contextListener class="xxx.xxxxxx.listener.LogbackStartupListener"/>-->
<!-- SpringBoot自带的logback配置文件,包含 CONSOLE 和 FILE 两个appender -->
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<!--
说明:
1. 文件的命名和加载顺序有关
logback.xml早于application.yml加载,logback-spring.xml晚于application.yml加载
如果logback配置需要使用application.yml中的属性,需要命名为logback-spring.xml
2. logback使用application.yml中的属性
使用springProperty才可使用application.yml中的值 可以设置默认值
3、springProperty的source属性对应application.yml的键,需注意的是,需要使用全小写名称(有下划线的去掉)
4、springProperty标签在重新加载(scan为true)导致no applicable action for [springProperty], current ElementPath is [[configuration][springProperty]],进而导致设置的变量都是无定义的
5、通过监听器注入相关的变量
-->
<!--<property name="logPath4399" value="F:/temp/logs/4399"/>
<property name="logPattern4399" value="%date{yyyy-MM-dd HH:mm:ss}|%msg%n"/>-->
<springProperty scope="context" name="logPath4399" source="logback.prop.platform.4399.path" defaultValue="./"/>
<springProperty scope="context" name="logPattern4399" source="logback.prop.platform.4399.pattern" defaultValue="%date{yyyy-MM-dd HH:mm:ss}|%msg%n"/>
<springProperty scope="context" name="logMaxHistory4399" source="logback.prop.platform.4399.maxhistory" defaultValue="15"/>
<springProperty scope="context" name="logPath4399WYY" source="logback.prop.platform.4399.app.wyy.path" defaultValue="${logPath4399}"/>
<springProperty scope="context" name="logPattern4399WYY" source="logback.prop.platform.4399.app.wyy.pattern" defaultValue="${logPattern4399}"/>
<springProperty scope="context" name="log4399WYYSimpleName" source="logback.prop.platform.4399.app.wyy.simplename"/>
<springProperty scope="context" name="log4399WYYLogName" source="logback.prop.platform.4399.app.wyy.logname"/>
<!-- 动态日志级别:指的是可以通过jconsole等工具进行设置 -->
<!--<jmxConfigurator/>-->
<!--输出到文件,只记录INFO级别信息-->
<appender name="FILE_4399_WYY" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 4399聊天日志: {game}_{YYYYMMdd}.log的形式 -->
<!-- file不支持%d{yyyyMMdd} -->
<!--<file>${logPath4399WYY}${log4399WYYSimpleName}_%d{yyyyMMdd}.log</file>-->
<!-- 日志输出格式 -->
<encoder>
<pattern>${logPattern4399WYY}</pattern>
<charset>utf-8</charset>
</encoder>
<!--
TimeBasedRollingPolicy基于时间的滚动策略
timeBasedFileNamingAndTriggeringPolicy标签: 设置触发滚动时机,放在的子节点的位置,基于实践策略的触发滚动策略
SizeAndTimeBasedRollingPolicy基于时间和文件大小的触发策略,相当于 TimeBasedRollingPolicy+timeBasedFileNamingAndTriggeringPolicy的SizeAndTimeBasedFNATP
注: SizeAndTimeBasedRollingPolicy(TimeBasedRollingPolicy+timeBasedFileNamingAndTriggeringPolicy)必须指定%d日期和%i索引,
-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logPath4399WYY}${log4399WYYSimpleName}_%d{yyyyMMdd}.log</fileNamePattern>
<!-- 日志文档保留数量(按天滚动相当于天数) -->
<maxHistory>${logMaxHistory4399}</maxHistory>
<!--<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>-->
</rollingPolicy>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<!--<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!– 每天日志归档路径以及格式: %d{yyyy-MM-dd}指定日期格式,%i指定索引 –>
<fileNamePattern>${logPath4399WYY}${log4399WYYSimpleName}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<!– 日志文档保留数量(按天滚动相当于天数) –>
<maxHistory>15</maxHistory>
<!–<totalSizeCap>10GB</totalSizeCap>–>
</rollingPolicy>-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 此日志文件只记录info级别的 -->
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--
logger标签用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
logger标签仅有一个name属性, 一个可选的level和一个可选的additivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
如果未设置此属性,那么当前logger将会继承上级的级别。
additivity:是否向上级logger传递打印信息,默认是true
-->
<logger name="com.origin.remote.chat.provider.ChatFor4399Provider.${log4399WYYLogName}" level="INFO" additivity="false">
<!-- 引用的appender,类似于spring的ref -->
<appender-ref ref="FILE_4399_WYY" />
</logger>
</configuration>