📣 大家好,我是Zhan,一名个人练习时长两年的大三后台练习生🏀
📣 这篇文章是 Java 日志框架 的第二篇学习笔记📙
📣 如果有不对的地方,欢迎各位指正🙏🏼
📣 以始为终 ------ Just do it! 🫵🏼🫵🏼🫵🏼
👉 引言
上篇文章中 阿里面试:说了多少遍要用门面模式的日志框架? - 掘金 (juejin.cn) 我们简单介绍了日志框架的生态,更像一个前置知识,但是本文我们将走进开发,把日志框架用到 Springboot 项目中,实现日志的多环境配置、根据时间进行分割 ,并简单介绍两种记录日志的方式:Aspect + Annotation 和 ELK + Beats 进行日志的记录和可视化
一、Application 的简单配置
在 SpringBoot 中配置日志输出之前,我们先进行一个方案的选型:LogBack vs Log4j
- LogBack :性能优秀 ,具有出色的吞吐量和低延迟。更灵活 ,输出的格式和选项更多。还有在上篇文章中提到的,它与 SLF4J 紧密集成,这可以让它与其他的日志框架兼容
- Log4j :
- Log4j1 :它的优点就是历史悠久(高情商说话),缺点就是过时(低情商说话)。除非原有的架构使用的就是它,否则几乎不会选择它作为日志框架
- Log4j2 :它支持异步日志记录 ,可以提高应用程序的性能。拥有丰富的插件生态系统,可以轻松扩展功能。
Attention :由于后续关于"ELK 日志"
的博客(如下图)也会使用到 LogBack
的流进行传输,因此下面我们使用 LogBack + SLF4J
的组合进行日志框架的学习和使用
引入依赖
在上篇文章 阿里面试:说了多少遍要用门面模式的日志框架?中我们说过如何选择 SpringBoot 的日志框架,也就是在引入依赖的时候对
starter
的选择:
xml
<dependencies>
<!-- 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
我们也可以打开源码找找它的依赖 spring-boot-starter-logging
,不难发现这个依赖是被包含于 spring-boot-starter
,然后被包含于上面我们引入的 MVC 依赖:
Application.yml 配置
在配置文件中我们可以对日志文件、日志级别、日志格式三部分进行配置:
🎯 日志文件:也就是日志的输出文件,我们可以把日志以文件的方式存储起来,主要是通过下面这两个配置:
logging.file.name
: C:\Users\24907\Desktop\log\test.loglogging.file.path
: C:\Users\24907\Desktop\log- 区别:
- 第一种方式,我们可以指定日志的名称
- 第二种方式会自动创建,日志的名称默认为
spring.log
- 🧨 踩雷: 如果你和我一样使用了中文的目录,你就会发现项目启动不起来哈哈
- ❗ 注意: 如果同时配置了上述两个属性,
name
的优先级更高
🚩 日志级别 :我们的日志输出级别总共有 TRACE
、DEBUG
、INFO
、WARN
、ERROR
、FATAL
、OFF
这些日志级别,我们可以通过设置日志级别来看到我们希望看到的信息。例如开发环境 我们希望看到更多的信息,可以使用 DEBUG
级别,而生产环境 希望尽量的节省空间,可以使用 INFO
级别。
logging.level.root
:INFO- 注意: 这里的
root
指的是日志记录器的根记录器 ,也就是设置了所有的日志级别 - 这个配置允许你在应用程序级别设置默认的日志级别,然后你可以在特定的包、类或模块中进一步细化日志级别,以满足不同部分的日志需求
🏴 日志格式 :日志格式分为 控制台 和 文件 输出的格式,通常来说它可以帮助我们对日志格式进行简单的要求:
logging.pattern.console
: 控制台日志的格式。logging.pattern.file
: 文件日志的格式。
二、LogBack.xml 的详细配置
既然 Application.xml 可以配置 LogBack 的输出信息,为什么还需要
LogBack.xml
呢?因为 LogBack.xml 提供了更高级的日志框架配置选项,允许你详细控制日志的行为,实现对日志框架的高度定制 ,我们以生产环境中常用的 按天分割日志文件 进行举例:
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- <1> -->
<!-- 引入 Spring Boot 默认的 logback XML 配置文件 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- <2.1> -->
<!-- 控制台 Appender --> <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志的格式化 -->
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 2.2 -->
<!-- 从 Spring Boot 配置文件中,读取 spring.application.name 应用名 -->
<springProperty name="applicationName" scope="context" source="spring.application.name" />
<!-- 日志文件的路径 -->
<property name="LOG_FILE" value="C:\\Users\\24907\\Desktop\\log\\${applicationName}.log"/>
<!-- 日志文件 Appender --> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<!--滚动策略,基于时间 + 大小的分包策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
<maxHistory>7</maxHistory>
<maxFileSize>10KB</maxFileSize>
</rollingPolicy>
<!-- 日志的格式化 -->
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 3.1 -->
<!-- 测试环境,独有的配置 -->
<springProfile name="dev">
<!-- 设置 Appender --> <root level="INFO">
<appender-ref ref="console"/>
</root>
<!-- 设置 "cn.iocoder.springboot.lab37.loggingdemo" 的 Logger 的日志级别为 DEBUG --> <logger name="cn.iocoder.springboot.lab37.loggingdemo" level="DEBUG"/>
</springProfile>
<!-- 3.2 -->
<!-- 生产环境,独有的配置 -->
<springProfile name="test">
<!-- 设置 Appender --> <root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
</springProfile>
</configuration>
对于这一类的配置文件,我们作为开发者不需要去学习怎么把它写出来,更多的是要学会怎么把它用到自己的项目上面去 ,针对于上面的配置,我们主要需要改动哪些部分呢?
⚒️ 运行模式
xml
<springProfile name="test">
<!-- 设置 Appender --> <root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
</springProfile>
这里就是指定我们此时有效的配置文件为test,也就是,并指定了当前日志记录的方式为 console
控制台日志打印、file
文件日志输出两种方式,而对于 dev
的配置文件,只是进行控制台的打印:
🔗 文件输出地址
<File>${LOG_FILE}</File>
:这里指定的是日志输出的地方,也就是说所有的日志都会输出在这个文件中<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
: 这里指定的是分割日志的存储路径,但是注意的是这里产生的是 gz 文件(节省空间),解压出来之后就可以查询。- 注意: 如果两个都进行配置 ,你将会同时保留一个主要的日志文件,同时将日志按照一定的条件(通常是时间或大小)滚动到新的文件中,以便进行日志的归档和管理。
📜 文件输出大小
<maxHistory>7</maxHistory>
是Logback配置中用于控制滚动日志文件保留历史文件的数量的选项。它代表我们最多保存 7 个历史日志文件<maxFileSize>10KB</maxFileSize>
表明了当日志文件超过 10KB 的时候就会自动分割,产生下一个日志文件,但是要注意的是我们是产生的
🔎 指定配置文件名
在配置完 LogBack-Spring.xml 这一配置文件后,我们最好是在 Application.yml 指定使用这一配置文件:
yml
#配置日志
logging:
config: classpath:loggback-spring.xml
🧨 踩雷: 最好是使用 LogBack-Spring.xml
这个文件名,而不是 LogBack.xml
这个文件名,否则在启动 Springboot 的时候就会爆红:
java
ERROR in ch.qos.logback.core.joran.spi.Interpreter@54:32 - no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]]
💻 使用效果
至此就可以完成 LogBack.xml
的全部配置,此处的数据只有单天的数据
❗特别提示: 看了 LogBack.xml
的配置,也不要觉得 Application.yml
没有作用,他们有着各自的优势和用途,因此我们通常会一起使用二者,而非替代彼此:
application.yml
用于简单的配置一些基本的日志属性Logback.xml
用于高级和详细的日志框架配置
三、Actuator 动态修改日志级别
对于线上的项目,我们有时候会需要对日志记录的级别进行修改,例如本来是 INFO 级别的日志,但是现在需要排查问题,也就是需要把日志的记录级别修改为 DEBUG,重启项目这种方式显然不合适,一点都不优雅
于是我们可以使用 spring-boot-starter-actuator
依赖动态的修改日志的级别:
xml
|<!-- 实现对 Actuator 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然后再配置文件中,增加 Actuator 的简单配置,让 Actuator 生效:
yml
management:
endpoints:
web:
exposure:
include: '*'
至此我们就可以通过请求修改 Logger 的日志级别,使用的:
- 请求地址为:
POST http://127.0.0.1:8080/actuator/loggers/cn.iocoder.springboot.lab37.loggingdemo.controller/
- 参数为:key 为
configuredLever
,value 为INFO
四、Aspect 统一处理异常信息
从开发的角度来讲,直接使用 log.info 这样的方式进行日志的处理,很明显和项目的耦合度很高 ,如何让操作日志 不和业务逻辑耦合,简单、方便、优雅地接入操作日志呢?使用 Aspect 和 注解 就可以帮助你优雅的记录日志
这里由于篇幅问题,我们不展开讲解,只是做一个简单的示例和介绍,大家感兴趣可以去看美团技术团队这一篇文章如何优雅地记录操作日志? - 美团技术团队 (meituan.com),写的很好,虽然它更多的是记录的操作日志,而非系统日志 ,但是这种方法都是很值得学习的,如果不想自己实现的话,经过我的检索,这个仓库 mouzt/mzt-biz-log(github.com)基本实现了美团技术团队中的这项技术。
其实主要实现的思路就是,我们可以通过在方法上写注解,例如:
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemOperationLog {
/**
*操作描述
*/
String value() default "";
}
然后我们可以写一个切面,在方法执行后去通过反射获取方法上面的注解以及内容,从而把这个内容持久化到数据库或者打印出来,这样我们就不用在每个方法中进行日志的打印,而是统一在切面中进行打印:
java
@After("pointCut()")
public void doAfter(JoinPoint joinPoint) throws Exception {
if(Status.ERROR == isDoAop()){
//判断操作日志是否需要被记录
return;
}
OperationLog op = new OperationLog();
User user = TokenUtil.verifyForUser(getToken());
assert user != null;
//设置操作者姓名
op.setOperatorName(user.getUserName());
Integer userId = getOperationObject();
if(null == userId){
return;
}
User object = userService.getById(userId);
//设置操作对象姓名
op.setOperationObject(object.getUserName());
//设置操作内容
op.setOperationContent(getOperationContent(joinPoint));
//设置操作时间
op.setOperationTime(TimeUtil.getNowTimestamp());
boolean result = this.operationLogService.save(op);
if(Status.ERROR == result){
throw new Exception("操作日志记录失败");
}
}
PS: 其实这种方法就偏开发者层面了,而不是框架的使用层面,避免本文偏题,因此这里也没有展开讲解,感兴趣可以去看美团技术团队的那篇文章
五、还有高手?
刚刚有说到可以通过切面和注解结合 的方式进行操作日志的记录以及持久化,如果想要持久化到数据库,其实 LogBack 提供的
DBAppender
也可以把日志写入到数据库
而不知道读者有没有听过这样的技术:ELK
,它是由三门技术组成的:ElasticSearch、LogStash、Kibana,其中 ES 就是一个数据库,而 LogStash 是一个数据过滤和处理的中间件,Kibana 则是可以把 ES 的数据可视化,以图表的方式展开。
而我们在 ES 的可视化网页 Kibana 可以看到如下的场景:
只能说相当的哇塞,这也是我们下篇文章会介绍的 ELK + Beats 记录日志,通过它我们可以利用 ElasticSearch 的特性,对于我们的系统日志进行分类、筛选、快速检索,这谁看了不迷糊!
💬 总结
上篇文章 阿里面试:说了多少遍要用门面模式的日志框架? - 掘金 (juejin.cn) 我们介绍了日志框架的生态,大家感兴趣可以去看一下,而本文主要是讲解了在 Springboot 中日志框架的使用:
- 从 Application.yml 中的简单配置日志的输出级别、文件地址、日志格式
- 然后到 LogBack-Spring.xml 的详细配置,并使用开发环境和测试环境分离 、根据文件大小和时间进行日志的分割,方便我们进行日志的检索,避免出现几G的大文件📁
- 为了更小代价对记录日志的级别进行更新,我们引入了 Actuator 对日志级别进行动态的修改
- 而在系统中更容易理解的操作日志 ,我们简单举了例子以及讲解了它实际上是由 Aspect 和 注解 共同实现
- 最后我们卖了一个关子,也就是下篇文章我们要讲到的 ELK + Beats 实现日志可视化
👉🏼创作不易,还忘大家点赞、收藏本文、专栏👈🏼
🍁 友链
- 阿里面试:说了多少遍要用门面模式的日志框架? - 掘金 (juejin.cn)
- 【系统学习SpringBoot】SpringBoot日志之日志分割
- 芋道 Spring Boot 日志集成 Logging 入门 | 芋道源码
- 如何优雅地记录操作日志? - 美团技术团队 (meituan.com)
- Maven Repository: org.springframework.boot >> spring-boot-starter-logging )
- mouzt/mzt-biz-log: 支持Springboot,基于注解的可使用变量、可以自定义函数的通用操作日志组件 (github.com)
- Kibana:数据的探索、可视化和分析 | Elastic
✒写在最后
都看到这里啦~,给个点赞再走呗~,也欢迎各位大佬指正以及补充,在评论区一起交流,共同进步!也欢迎加微信一起交流:Goldfish7710。