Appearance
acuity-log-starter 实现了几个功能:
功能
- MDC 参数
- @WebLog 记录操作日志
- defaults.xml、 defaults-dev.xml、 defaults-prod.xml、 全局的 logback 日志基础模版
MDC 参数
logback 内置的日志字段还是比较少,如果我们需要打印有关业务的更多的内容,包括自定义的一些数据,需要借助 logback MDC 机制,MDC 为“Mapped Diagnostic Context”(映射诊断上下文),即将一些运行时的上下文数据通过 logback 打印出来;此时我们需要借助 org.sl4j.MDC 类。
MDC 类基本原理其实非常简单,其内部持有一个 InheritableThreadLocal 实例,用于保存 context 数据,MDC 提供了 put/get/clear 等几个核心接口,用于操作 ThreadLocal 中的数据;ThreadLocal 中的 K-V,可以在 logback.xml 中声明,最终将会打印在日志中。
可以看到本系统中打印的日志参数格式为:
java
[${spring.application.name}:${server.port}:%X{tenant}:%X{userid}] %d{yyyy-MM-dd HH:mm:ss.SSS}[%5p] ${PID} [%X{trace}] [%t:%r] [%logger{50}.%M:%L] %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
[${spring.application.name}:${server.port}:%X{tenant}:%X{userid}] %d{yyyy-MM-dd HH:mm:ss.SSS}[%5p] ${PID} [%X{trace}] [%t:%r] [%logger{50}.%M:%L] %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
其中 %X{tenant}
、%X{userid}
、%X{trace}
三个参数就是通过 MDC 传递给 logback 的, 他们分别表示 租户编码、用户 id、请求 ID
实现步骤:
- 项目启动时,会加载 spring.factories 文件中的 AcuityMdcAdapterInitializer 类, 该类对 AcuityMdcAdapter 进行初始化(执行其 static 代码库)
- 在 HeaderThreadLocalInterceptor 拦截器中对每个请求调用 MDC.put 方法,对上面的三个参数进行设值
java
MDC.put(ContextConstants.LOG_TRACE_ID, StrUtil.isEmpty(traceId) ? StrUtil.EMPTY : traceId);
MDC.put(ContextConstants.JWT_KEY_TENANT, getHeader(request, ContextConstants.JWT_KEY_TENANT));
MDC.put(ContextConstants.JWT_KEY_USER_ID, getHeader(request, ContextConstants.JWT_KEY_USER_ID));
MDC.put(ContextConstants.LOG_TRACE_ID, StrUtil.isEmpty(traceId) ? StrUtil.EMPTY : traceId);
MDC.put(ContextConstants.JWT_KEY_TENANT, getHeader(request, ContextConstants.JWT_KEY_TENANT));
MDC.put(ContextConstants.JWT_KEY_USER_ID, getHeader(request, ContextConstants.JWT_KEY_USER_ID));
@WebLog 记录操作日志
- 配置文件介绍
yaml
acuity:
log:
enabled: false # false=禁用操作日志 true=开启
type: DB # 日志存储位置 DB=数据库 LOGGER=日志文件
acuity:
log:
enabled: false # false=禁用操作日志 true=开启
type: DB # 日志存储位置 DB=数据库 LOGGER=日志文件
- 注解属性介绍详看@WebLog 中的注释! 其中 value 属性是支持 SpEL 表达式的. (不懂 SpEL 的百度一下)
- 注解使用举例
java
// 禁用操作日志
@WebLog(enabled = false)
// response = false不记录方法的响应参数
@WebLog(value = "'分页列表查询:第' + #params?.current + '页, 显示' + #params?.size + '行'", response = false)
// requestByError = false 表示, 方法报错时, 也不记录请求参数
@WebLog(value = "'保存订单:订单编码' + #data?.code + ', 订单Id:' + #data?.name", request = false, requestByError = false)
// 禁用操作日志
@WebLog(enabled = false)
// response = false不记录方法的响应参数
@WebLog(value = "'分页列表查询:第' + #params?.current + '页, 显示' + #params?.size + '行'", response = false)
// requestByError = false 表示, 方法报错时, 也不记录请求参数
@WebLog(value = "'保存订单:订单编码' + #data?.code + ', 订单Id:' + #data?.name", request = false, requestByError = false)
- 原理: 通过 AOP 拦截标记了@WebLog 的方法, 拦截后, SysLogAspect 中的方法用于获取被拦截方法的入参和返回值. 然后发布一个 SysLogEvent 事件 (为了让操作日志尽可能少的影响方法调用时长,采用事件方式来异步处理, 大家也可以改成消息队列之类的), SysLogListener 监听器接收到事件后, 调用
consumer.accept
方法, 让对操作日志进行存储.
defaults.xml 全局的 logback 日志基础模版
这个 logback 的配置文件将整个项目中最常用的日志配置抽取到这里.
- 通过 springProperty 标签设置的参数, 可以在项目的 application.yml 配置文件中, 设置 source 属性对其 defaultValue 进行覆盖
yaml
log.path 属性来源于 yml配置中的 logging.file.path 属性, 若yml中没有配置 logging.file.path, 则取默认值/data/projects/logs ,在logback配置文件的任意地方, 都能使用 ${log.path}读取这个参数.
<springProperty scope="context" name="log.path" source="logging.file.path" defaultValue="/data/projects/logs"/>
yml:
logging:
file:
path: /root/logs # 在yml中设置这个参数后, 日志的生成路径就会到 /root/logs
log.path 属性来源于 yml配置中的 logging.file.path 属性, 若yml中没有配置 logging.file.path, 则取默认值/data/projects/logs ,在logback配置文件的任意地方, 都能使用 ${log.path}读取这个参数.
<springProperty scope="context" name="log.path" source="logging.file.path" defaultValue="/data/projects/logs"/>
yml:
logging:
file:
path: /root/logs # 在yml中设置这个参数后, 日志的生成路径就会到 /root/logs
- 文件中有类似
ASYNC_CONTROLLER_APPENDER
、CONTROLLER_APPENDER
之类的 Appender, 区别在于
ASYNC_
开头的 Appender 采用了异步输出日志
, 而没有ASYNC_
开头的 Appender 采用了实时输出日志
.- 异步输出 性能明显好于 实时输出,
- 异步输出有一些参数无法记录, 所以在生产环境打印的日志文件中, 有很多
?
.
txt
# 2个问号 表示那个方法, 那行 , 但由于使用了异步输出, 所以输出为 ?
[acuity-authority-server:8760:0000:2] 2020-12-15 17:53:12.191[ INFO] 22531 [50b3bd53c2f344e790834b587a7f1891] [task-13961:934466928] [top.acuity.commons.database.mybatis.WriteInterceptor.?:?] mapper id=top.acuity.box.authority.dao.common.OptLogExtMapper.insert, userId=0
# 2个问号 表示那个方法, 那行 , 但由于使用了异步输出, 所以输出为 ?
[acuity-authority-server:8760:0000:2] 2020-12-15 17:53:12.191[ INFO] 22531 [50b3bd53c2f344e790834b587a7f1891] [task-13961:934466928] [top.acuity.commons.database.mybatis.WriteInterceptor.?:?] mapper id=top.acuity.box.authority.dao.common.OptLogExtMapper.insert, userId=0
- defaults.xml 中配置了如下信息
- springProperty: spring 环境配置
- appender: 定义了 Controller、Service、Dao、第三方 jar、全局异常类、root 等 appender
defaults-dev.xml: 实时配置文件,用于配置项目在
开发环境
的全局日志输出规则。性能低,实时性高。defaults-prod.xml : 异步配置文件,用于配置项目在
生产环境
的全局日志输出规则。 性能高,实时性低。项目运行中的业务日志,请在各自项目的 resources 目录下配置 logback-spring.xml(生产) 或 logback-spring-dev.xml (开发)