日志这个事情,在项目中还真的是不能少,对于线上应用,如果出了什么问题,没有日志简直没有办法去排查!只能抓瞎了!
正好最近有项目组小伙伴,提了一个需求:如何实现日志的细粒度控制,即
- 将不同包,甚至不同类的日志输出到不同的日志文件中,方便日志分析排查问题
- 将不同日志级别的日志,输出到不同的日志文件中,方便日志分析排查问题
我们的应用都是基于springboot的应用,为了解决这个问题,简单整理了一个方案,并顺便分享出来。
1.日志组件介绍
java的世界,从来就不缺优秀的组件,在日志领域也是一样的。我们来数一数
- JUL:最正宗的,jdk自带的日志系统,从jdk1.4版本就有了。但是是认知度最低的,几乎没有什么存在感!估计也没有几个小伙伴认识,不必关心它!
- JCL:全称是Jakarta Commons Logging,是大名鼎鼎的Apache出品的通用日志API,小伙伴们肯定都很熟悉commons-logging依赖,指的就是它。需要注意的是JCL并不提供具体的实现,它只提供了接口,即提供了日志接口规范,方便我们在应用中按需切换具体的日志实现组件,比如log4j,jul等
- Log4j:这个我们都很熟悉了,是Apache提供的开源日志组件实现,支持细粒度控制日志生成过程,有丰富的日志级别,以及支持将日志输出到控制台、文件、数据库等
- Slf4j:简单日志门面,全称是The Simple Logging Facade For Java。Slf4j类似于JCL,只提供接口规范,不提供具体实现,都是为了方便在应用中按需切换具体日志组件而诞生的,简单理解就是为了解耦具体日志组件的
- Logback:可靠、通用、灵活的Java日志框架,有很多优点,它是Slf4j-api的天然实现,与Slf4j使用不需要任何桥接包;它还是springboot应用默认支持的日志组件
- Log4j2:Log4j2是Log4j 1.x与Logback的改进版本,采用了异步无锁化实现,吞吐量、性能都更好,是目前所有日志组件中的佼佼者!简直是降维打击啊!
介绍完日志组件,回到最初的需求场景,需要解决问题,你还记得上面的需求吗
- 将不同包,甚至不同类的日志输出到不同的日志文件中,方便日志分析排查问题
- 将不同日志级别的日志,输出到不同的日志文件中,方便日志分析排查问题
另外我们的应用都是基于springboot,案例环境就默认使用Slf4j + Logback的组合了。
2.案例搭建环境
2.1.引入依赖
需要引入spring-boot-starter-logging依赖
xml
<!--日志依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
如果是springboot web应用,当我们引入spring-boot-starter-web依赖后,已经依赖了日志starter组件,就不需要在额外引入
为了一会在代码中,使用日志方便,再引入lombok依赖
xml
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2.2.编写配置文件
要细粒度控制日志输出,需要在resources目录下,增加控制日志行为的配置文件,根据logback的约定,配置文件名称规范是:logback-xxx.xml,通常项目中我们就叫做:logback-spring.xml
2.2.1.logback-spring.xml
xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!--上下文名称-->
<contextName>follow-me-springboot-log</contextName>
<!--日志输出目录,以及日志文件名称-->
<property name="logback.logdir" value="D:\\nacos\\log"/>
<property name="logback.appname" value="followMeLog"/>
<!--自定义appender组件,将日志输出到文件:
1.只记录INFO级别日志,通过LevelFilter过滤处理
-->
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--如果只是想要 Info 级别的日志,只是过滤 info 还是会输出 Error 日志,因为 Error 的级别高,
所以我们使用下面的策略,可以避免输出 Error 的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--过滤 Error-->
<level>ERROR</level>
<!--匹配到就禁止-->
<onMatch>DENY</onMatch>
<!--没有匹配到就允许-->
<onMismatch>ACCEPT</onMismatch>
</filter>
<!--日志名称-->
<File>${logback.logdir}/info.${logback.appname}.log</File>
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式------把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>${logback.logdir}/info.${logback.appname}.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--只保留最近90天的日志-->
<maxHistory>90</maxHistory>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<!--日志输出编码,以及日志格式-->
<encoder>
<charset>UTF-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n</pattern>
</encoder>
</appender>
<!--自定义appender组件,将日志输出到文件:
1.只记录ERROR级别日志,通过ThresholdFilter过滤处理
-->
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>Error</level>
</filter>
<!--日志名称-->
<File>${logback.logdir}/error.${logback.appname}.log</File>
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式------把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>${logback.logdir}/error.${logback.appname}.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--只保留最近90天的日志-->
<maxHistory>90</maxHistory>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<!--<totalSizeCap>1GB</totalSizeCap>-->
</rollingPolicy>
<!--日志输出编码,以及日志格式-->
<encoder>
<charset>UTF-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n</pattern>
</encoder>
</appender>
<!--自定义appender组件,将日志输出到控制台 -->
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<!--展示格式 layout-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n</pattern>
</layout>
<!-- 筛选级别日志并输出到控制台,这里是INFO -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<!--自定义日志logger组件,且支持环境profile:开发环境,测试环境-->
<springProfile name="dev,test">
<!-- 细粒度控制日志,属性说明:
name:指定范围,可以到具体类名称,或者包名
level:日志级别
additivity:日志是否向上传递,true向上传递;false不向上传递
-->
<logger name="cn.edu.anan.controller" level="INFO" additivity="true">
<appender-ref ref="fileInfoLog"/>
<appender-ref ref="fileErrorLog"/>
</logger>
</springProfile>
<!--自定义日志logger组件,且支持环境profile:生产环境-->
<springProfile name="prod">
<!-- 细粒度控制日志,属性说明:
name:指定范围,可以到具体类名称,或者包名
level:日志级别
additivity:日志是否向上传递,true向上传递;false不向上传递
-->
<logger name="cn.edu.anan.controller" level="ERROR" additivity="false">
<appender-ref ref="fileInfoLog"/>
<appender-ref ref="fileErrorLog"/>
</logger>
</springProfile>
<!--配置根日志root组件,它引用输出到控制台appender组件-->
<root level="INFO">
<!--appender将会添加到这个logger-->
<appender-ref ref="consoleLog"/>
</root>
</configuration>
配置文件解读
- 配置了三个appender组件,分别将日志输出到控制台、文件
-
- fileInfoLog:输出INFO级别日志到文件
- fileErrorLog:输出ERROR级别日志到文件
- consoleLog:将日志输出到控制台
- 配置了两个SpringProfile组件,控制不同的环境,使用不同的profile特性
-
- springProfile name="dev,test":开发环境、测试环境,需要对应spring.profiles.active属性
- springProfile name="prod":生产环境,需要对应spring.profiles.active属性
- 配置了两个logger组件,用于组织appender组件,且控制是否要将日志向上传递到root组件
- 配置了root根日志组件,应用consoleLog组件,将日志输出到控制台
以上配置,在配置文件中有详细的注释,如果你不熟悉,可以仔细看一下
2.2.2.application.yml
在application.yml文件中,重点需要关注spring.profiles.active属性,它与logback-spring.xml文件中的SpringProfile组件对应
yaml
server:
port: 8080
spring:
application:
name: follow-me-springboot-log
profiles:
active: dev
2.3.编写应用
编写两个controller,分别开放不同的端点,测试日志效果
2.3.1.LogController
java
@RestController
@RequestMapping("log")
@Slf4j
public class LogController {
@RequestMapping("test")
public String test(){
// 1.记录INFO级别日志
log.info("LogController.日志级别:{}.日志内容:{}.", "INFO","记录INFO级别日志");
// 2.记录ERROR级别日志
log.error("LogController.日志级别:{}.日志内容:{}.", "ERROR","记录ERROR级别日志");
return "LogController.";
}
}
2.3.2.Log2Controller
java
@RestController
@RequestMapping("log")
@Slf4j
public class Log2Controller {
@RequestMapping("test2")
public String test2(){
// 1.记录INFO级别日志
log.info("Log2Controller.日志级别:{}.日志内容:{}.", "INFO","记录INFO级别日志");
// 2.记录ERROR级别日志
log.error("Log2Controller.日志级别:{}.日志内容:{}.", "ERROR","记录ERROR级别日志");
return "Log2Controller.";
}
}
3.实现效果
启动应用,分别访问端点,观察日志结果
端点一:http://127.0.0.1:8080/log/test
端点二:http://127.0.0.1:8080/log/test2
控制台
shell
2021-08-23 15:56:29 [http-nio-8080-exec-1] INFO cn.edu.anan.controller.LogController -LogController.日志级别:INFO.日志内容:记录INFO级别日志.2021-08-23 15:56:29 [http-nio-8080-exec-1] ERROR cn.edu.anan.controller.LogController -LogController.日志级别:ERROR.日志内容:记录ERROR级别日志.2021-08-23 15:56:36 [http-nio-8080-exec-3] INFO cn.edu.anan.controller.Log2Controller -Log2Controller.日志级别:INFO.日志内容:记录INFO级别日志.2021-08-23 15:56:36 [http-nio-8080-exec-3] ERROR cn.edu.anan.controller.Log2Controller -Log2Controller.日志级别:ERROR.日志内容:记录ERROR级别日志.
INFO日志文件
ERROR日志文件
通过以上执行效果,我们看到已经成功将INFO、ERROR日志实现分离,记录到不同的日志文件中
- error.followMeLog.log:错误日志文件
- info.followMeLog.log:info日志文件
这样一来,会非常方便进行不同的日志收集、分析事项。nice!对吧。
另外这里还有三个特性,我就留给你去实践了
- 将application.yml文件中spring.profiles.active属性,设置为prod看看效果
- 通过Logger组件的name属性,实现将LogController、Log2Controller日志更细粒度的分离
- 通过Logger组件的additivity属性,分别设置为true、false,观察控制台的输出
实践完成后,相信你一定会有收获!