场景:对Springboot中原有的Logback框架进行封装,通过AOP针对自定义注释进行日志输出,并配置输出的log文件路径,对于某些必要的日志,需要将其信息存储至数据库。
本文主要对Logback框架做一个简单的学习总结,并且记录了自定义配置Logback框架过程中的主要思想和关键步骤代码。
一、Logback介绍
Spring Boot默认使用LogBack日志系统,且默认是将日志打印到控制台上。
Logback是有自己的Maven地址的。
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
但该包通常被spring-boot-starter
包含了,因此一般无需自行导入。
1.1 Logback层级
如果一个logger的名字加上一个.作为另一个logger名字的前缀,那么该logger就是另一个logger的祖先。如果一个logger与另一个logger之间没有其它的logger,则该logger就是另一个logger的父级。
举例: 名为的com.example的logger是名为com.example.service的logger的父级。
名为com的logger是名为com.example的logger的父级,是名为com.example.service的logger的祖先
1.2 Logback输出等级
Logback的日志输出等级分为:TRACE, DEBUG, INFO, WARN, ERROR。
如果一个给定的logger没有指定一个日志输出等级,那么它就会继承离它最近的一个祖先的层级。
为了确保所有的logger都有一个日志输出等级,root logger会有一个默认输出等级 — DEBUG。
1.3 Logback初始化流程
以下来自ChatGPT:
在 Spring Boot 中,默认情况下,Logback 在初始化时会读取以下两个配置文件:
logback-spring.xml: 这是主要的 Logback 配置文件,它位于类路径的根目录下。Spring Boot 使用 logback-spring.xml 作为 Logback 的默认配置文件,并且提供了一些额外功能,例如可以在配置文件中使用 Spring的属性占位符 ${} 来引用外部配置文件中的值,以及可以根据不同的环境(如开发、生产等)加载不同的配置。
logback.xml: 如果没有找到 logback-spring.xml 配置文件,Logback 将尝试读取 logback.xml 作为备用配置文件。这个文件也位于类路径的根目录下。但需要注意的是,使用 logback.xml配置文件时,将无法使用 Spring 的属性占位符和环境特定的配置功能。
Spring Boot 推荐使用 logback-spring.xml 作为 Logback 的配置文件,因为它可以更好地集成到 Spring Boot 的环境中,充分利用 Spring Boot 提供的特性和功能。
二、Logback自定义输出配置
在resources下编写logback配置文件logback-base.xml和logback-spring.xml,在初始化时Logback框架即可自动导入配置。
2.1 logback-base.xml
<?xml version="1.0" encoding="UTF-8"?>
<included>
<contextName>logback</contextName>
<!--
name的值是变量的名称,value的值时变量定义的值
定义变量后,可以使“${}”来使用变量
-->
<property name="log.path" value="d:\\logs" />
<!-- 彩色日志 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule
conversionWord="clr"
converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule
conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!--输出到控制台-->
<appender name="LOG_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--输出到文件-->
<appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/logback.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
</appender>
</included>
2.2 logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--引入其他配置文件-->
<include resource="logback-base.xml" />
<!--
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、
以及指定<appender>。<logger>仅有一个name属性,
一个可选的level和一个可选的addtivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
如果未设置此属性,那么当前logger将会继承上级的级别。
addtivity:是否向上级logger传递打印信息。默认是true。
-->
<!--开发环境-->
<springProfile name="dev">
<logger name="cn.itcast.controller" additivity="false" level="debug">
<appender-ref ref="LOG_CONSOLE"/>
</logger>
</springProfile>
<!--生产环境-->
<springProfile name="pro">
<logger name="cn.itcast.controller" additivity="false" level="info">
<appender-ref ref="LOG_FILE"/>
</logger>
</springProfile>
<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
level:设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF 默认是DEBUG
可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-->
<root level="info">
<appender-ref ref="LOG_CONSOLE" />
<appender-ref ref="LOG_FILE" />
</root>
</configuration>
三、封装Logger模块
新建一个Logger模块,主要利用AOP和SpringEvent机制来进行自定义日志输出。
3.1 SpringEvent介绍
Spring Event是Spring的事件通知机制,可以将相互耦合的代码解耦,从而方便功能的修改与添加。Spring Event是监听者模式的一个具体实现。
监听者模式包含了监听者Listener、事件Event、事件发布者EventPublish,过程就是EventPublish发布一个事件,被监听者捕获到,然后执行事件相应的方法。
3.2 模块开发步骤
1、定义日志操作事件类SysLogEvent。
2、定义@SysLog注解,用于在Controller的方法上标注当前方法需要进行操作日志的保存处理。
3、定义切面类SysLogAspect。
4、在切面类SysLogAspect中定义切点,拦截Controller中添加@SysLog注解的方法。
5、在切面类SysLogAspect中定义前置通知,在前置通知方法recordLog中收集操作日志相关信息封装为OptLogDTO对象并保存到ThreadLocal中。
6、在切面类SysLogAspect中定义后置通知,在后置通知方法doAfterReturning中通过ThreadLocal 获取OptLogDTO并继续设置其他的操作信息到OptLogDTO。
7、在切面类SysLogAspect的后置通知方法doAfterReturning中发布事件SysLogEvent。
8、定义监听器SysLogListener,监听日志发布事件SysLogEvent,这里用的Consumer有点类似于策略模式。
代码中Consumer接口的作用是允许开发人员传递一个行为或操作,对给定的输入对象进行处理。它可以用于各种场景,例如集合的遍历、数据的转换、事件处理等。通过定义一个Consumer 对象,可以将一段逻辑或操作封装起来,并在需要的时候传递给其他方法或函数。
/**
* 异步监听日志事件
*/
@Slf4j
@AllArgsConstructor
public class SysLogListener {
private Consumer<OptLogDTO> consumer;
@Async
@Order
@EventListener(SysLogEvent.class)
public void saveSysLog(SysLogEvent event) {
OptLogDTO optLog = (OptLogDTO) event.getSource();
//BaseContextHandler.setDatabase(database);
consumer.accept(optLog);
}
}
9、定义配置类LogAutoConfiguration,用于自动配置切面SysLogAspect对象。
四、自定义Logger模块使用
该模块为具体的业务模块,具体使用过程如下:
4.1 创建logback配置文件logback-base.xml和logback-spring.xml
4.2 创建LogService,自定义对日志的操作流程
@Service
@Slf4j
public class LogService {
//将日志信息保存到数据库
public void saveLog(OptLogDTO optLogDTO){
//此处只是将日志信息进行输出,实际项目中可以将日志信息保存到数据库
log.debug("保存日志信息:" + optLogDTO);
}
}
4.3 创建配置类,将SysLogListener事件监听器注入框架,并配置了LogService处理日志。这里LogService作为Consumer的策略实现。
/*
*日志配置类
*/
@Configuration
public class LogAutoConfiguration {
//自动配置日志监听器组件
@Bean
@ConditionalOnMissingBean
public SysLogListener sysLogListener(LogService logService){
return new SysLogListener(optLogDTO -> logService.saveLog(optLogDTO));
}
}