Appearance
五、日志配置
在开发中,我们经常使用 System.out.println()
来打印一些信息,但是这样不好,因为大量的使用 System.out
会增加资源的消耗。我们实际项目中使用的是 slf4j 的 logback 来输出日志,效率挺高的,Spring Boot 提供了一套日志系统,logback 是最优的选择。
1、日志级别
几种常见的日志级别由低到高分为: TRACE
< DEBUG
< INFO
< WARN
< ERROR
< FATAL
。 如何理解这个日志级别呢?很简单,如果项目中的日志级别设置为 INFO
,那么比它更低级别的日志信息就看不到了,即是 TRACE
、 DEBUG
日志将会不显示。
2、Spring Boot 日志框架
Spring Boot默认的日志框架是 logback ,既然Spring Boot能够将其纳入的默认的日志系统,肯定是有 一定的考量的,因此实际开发过程中还是不要更换。
原则上需要使用logback,需要添加以下依赖,但是既然是默认的日志框架,当然不用重新引入依赖了。
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>2.6.7</version>
</dependency>
Spring Boot中默认的日志级别是 INFO
,启动项目日志打印如下:
从上图可以看出,输出的日志的默认元素如下:
- 时间日期:精确到毫秒
- 日志级别:ERROR, WARN, INFO, DEBUG , TRACE
- 进程ID
- 分隔符:— 标识实际日志的开始
- 线程名:方括号括起来(可能会截断控制台输出)
- Logger名:通常使用源代码的类名
- 日志内容
3、slf4j 介绍
引用百度百科里的一段话:
SLF4J,即简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。
这段的大概意思是:你只需要按统一的方式写记录日志的代码,而无需关心日志是通过哪个日志系统,以什么风格输出的。因为它们取决于部署项目时绑定的日志系统。例如,在项目中使用了 slf4j 记录日志,并且绑定了 log4j(即导入相应的依赖),则日志会以 log4j 的风格输出;后期需要改为以 logback 的风格输出日志,只需要将 log4j 替换成 logback 即可,不用修改项目中的代码。这对于第三方组件的引入的不同日志系统来说几乎零学习成本,况且它的优点不仅仅这一个而已,还有简洁的占位符的使用和日志级别的判断。
正因为 sfl4j 有如此多的优点,阿里巴巴已经将 slf4j 作为他们的日志框架了。在《阿里巴巴Java开发手册(正式版)》中,日志规约一项第一条就强制要求使用 slf4j:
1.【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
“强制”两个字体现出了 slf4j 的优势,所以建议在实际项目中,使用 slf4j 作为自己的日志框架。使用 slf4j 记录日志非常简单,直接使用 LoggerFactory 创建即可。
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Test {
private static final Logger log = LoggerFactory.getLogger(Test.class);
@Test
public void test(){
log.debug("输出DEBUG日志.......");
}
}
使用lombok
xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
使用也是很简单,只需要在类上标注一个注解 @Slf4j
即可,如下:
java
@Slf4j
public class Test {
@Test
public void test(){
log.debug("输出DEBUG日志.......");
}
}
4、application.yml、properties 中对日志的配置
1、如何指定日志级别
Spring Boot中默认的日志级别是INFO,但是可以自己定制日志级别,如下:
properties
logging.level.root=warn
# 指定包的日志级别
logging.level.com.xue=info
2、日志如何输出到文件中
其中有两个重要配置如下:
logging.file.path
:指定日志文件的路径logging.file.name
:日志的文件名,默认为spring.log
注意:官方文档说这两个属性不能同时配置,否则不生效,因此只需要配置一个即可。
properties
# springboot会每天打个包
logging.file.name=/Users/xueqimiao/springboot-test.log
3、如何定制日志格式
默认的日志格式在第一张图已经看到了,有时我们需要定制自己需要的日志输出格式,这样在排查日志 的时候能够一目了然。 定制日志格式有两个配置,分别是控制台的输出格式和文件中的日志输出格式,如下:
logging.pattern.console
:控制台的输出格式logging.pattern.file
:日志文件的输出格式
例如配置如下:
properties
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n
上面的配置编码的含义如下:
java
#%d{HH:mm:ss.SSS}——日志输出时间
#%thread——输出日志的进程名字,这在Web应用以及异步任务处理中很有用
#%-5level——日志级别,并且使用5个字符靠左对齐
#%logger- ——日志输出者的名字
#%msg——日志消息
#%n——平台的换行符
Spring Boot 对 slf4j 支持的很好,内部已经集成了 slf4j,一般我们在使用的时候,会对slf4j 做一下配置。application.yml
文件是 Spring Boot 中唯一一个需要配置的文件,一开始创建工程的时候是 application.properties
文件,个人比较细化用 yml 文件,因为 yml 文件的层次感特别好,看起来更直观,但是 yml 文件对格式要求比较高,比如英文冒号后面必须要有个空格,否则项目估计无法启动,而且也不报错。用 properties 还是 yml 视个人习惯而定,都可以。
我们看一下 application.yml 文件中对日志的配置:
yaml
logging:
config: logback.xml
level:
com.xue.dao: trace
logging.config
是用来指定项目启动的时候,读取哪个配置文件,这里指定的是日志配置文件是根路径下的 logback.xml
文件,关于日志的相关配置信息,都放在 logback.xml
文件中了。logging.level
是用来指定具体的 mapper 中日志的输出级别,上面的配置表示 com.xue.dao
包下的所有 mapper 日志输出级别为 trace,会将操作数据库的 sql 打印出来,开发时设置成 trace 方便定位问题,在生产环境上,将这个日志级别再设置成 error 级别即可。
5、logback.xml 配置文件解析
在上面 application.yml
文件中,我们指定了日志配置文件 logback.xml
,logback.xml
文件中主要用来做日志的相关配置。在 logback.xml
中,我们可以定义日志输出的格式、路径、控制台输出格式、文件大小、保存时长等等。下面来分析一下:
1、定义日志输出格式和存储路径
xml
<configuration>
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
<property name="FILE_PATH" value="D:/logs/course03/demo.%d{yyyy-MM-dd}.%i.log" />
</configuration>
我们来看一下这个定义的含义:首先定义一个格式,命名为 “LOG_PATTERN”,该格式中 %date
表示日期,%thread
表示线程名,%-5level
表示级别从左显示5个字符宽度,%logger{36}
表示 logger 名字最长36个字符,%msg
表示日志消息,%n
是换行符。
然后再定义一下名为 “FILE_PATH” 文件路径,日志都会存储在该路径下。%i
表示第 i 个文件,当日志文件达到指定大小时,会将日志生成到新的文件里,这里的 i 就是文件索引,日志文件允许的大小可以设置,下面会讲解。这里需要注意的是,不管是 windows 系统还是 Linux 系统,日志存储的路径必须要是绝对路径。
2、定义控制台输出
xml
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 按照上面配置的LOG_PATTERN来打印日志 -->
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
</configuration>
使用 <appender>
节点设置个控制台输出(class="ch.qos.logback.core.ConsoleAppender"
)的配置,定义为 “CONSOLE”。使用上面定义好的输出格式(LOG_PATTERN)来输出,使用 ${}
引用进来即可。
3、定义日志文件的相关参数
xml
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按照上面配置的FILE_PATH路径来保存日志 -->
<fileNamePattern>${FILE_PATH}</fileNamePattern>
<!-- 日志保存15天 -->
<maxHistory>15</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- 单个日志文件的最大,超过则新建日志文件存储 -->
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<!-- 按照上面配置的LOG_PATTERN来打印日志 -->
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
</configuration>
使用 <appender>
定义一个名为 “FILE” 的文件配置,主要是配置日志文件保存的时间、单个日志文件存储的大小、以及文件保存的路径和日志的输出格式。
4、定义日志输出级别
xml
<configuration>
<logger name="com.itcodai.course03" level="INFO" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
有了上面那些定义后,最后我们使用 <logger>
来定义一下项目中默认的日志输出级别,这里定义级别为 INFO,然后针对 INFO 级别的日志,使用 <root>
引用上面定义好的控制台日志输出和日志文件的参数。这样 logback.xml 文件中的配置就设置完了。
5、示例配置
logback-spring.xml(放入resource目录下即可,不需要额外的配置,只针对springboot默认的日志框架)
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<logger name="org.springframework.web" level="info"/>
<!-- 定义日志文件 输入位置 -->
<property name="logDir" value="./logs/project-name/" />
<!-- 日志最大的历史 30天 -->
<property name="maxHistory" value="30"/>
<!-- 控制台输出日志 -->
<!--<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger -%msg%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>-->
<!-- ERROR级别日志 -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logDir}\%d{yyyyMMdd}\error.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
<append>false</append>
<prudent>false</prudent>
</appender>
<!-- WARN级别日志 -->
<!--<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logDir}\%d{yyyyMMdd}\warn.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
<append>false</append>
<prudent>false</prudent>
</appender>-->
<!-- INFO级别日志 -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logDir}\%d{yyyyMMdd}\info.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
<append>false</append>
<prudent>false</prudent>
</appender>
<!-- DEBUG级别日志 -->
<!--<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logDir}\%d{yyyyMMdd}\debug.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
<append>false</append>
<prudent>false</prudent>
</appender>-->
<!-- root级别 DEBUG -->
<root level="DEBUG">
<!-- 控制台输出 -->
<!--<appender-ref ref="STDOUT" />-->
<!-- 文件输出 -->
<appender-ref ref="ERROR" />
<appender-ref ref="INFO" />
<!--<appender-ref ref="WARN" />
<appender-ref ref="DEBUG" />-->
</root>
</configuration>
6、使用Logger在项目中打印日志
在代码中,我们一般使用 Logger 对象来打印出一些 log 信息,可以指定打印出的日志级别,也支持占位符,很方便。
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
private final static Logger logger = LoggerFactory.getLogger(TestController.class);
@RequestMapping("/log")
public String testLog() {
logger.debug("=====测试日志debug级别打印====");
logger.info("======测试日志info级别打印=====");
logger.error("=====测试日志error级别打印====");
logger.warn("======测试日志warn级别打印=====");
// 可以使用占位符打印出一些参数信息
String str1 = "xue.com";
String str2 = "小薛博客";
logger.info("======个人博客:{};小程序:{}", str1, str2);
return "success";
}
}
启动该项目,在浏览器中输入 localhost:8080/test/log
后可以看到控制台的日志记录:
======测试日志info级别打印===== =====测试日志error级别打印==== ======测试日志warn级别打印===== ======个人博客:xue.com;小程序:小薛博客
因为 INFO 级别比 DEBUG 级别高,所以 debug 这条没有打印出来,如果将 logback.xml 中的日志级别设置成 DEBUG,那么四条语句都会打印出来,这个大家自己去测试了。同时可以打开 D:\logs\course03\ 目录,里面有刚刚项目启动,以后后面生成的所有日志记录。在项目部署后,我们大部分都是通过查看日志文件来定位问题。
7、切换日志框架
1、如何做到无感知切换
SLF4j是日志门面,无论什么日志框架都是基于SLF4j的API实现, 因此无论是代码打印日志还是 Lombok注解形式打印日志,都要使用的SLF4j的API,而不是日志框架的API ,这样才能解耦,做到无 感知。因为最终切换的框架只是对于SLF4j的实现,并不是切换SLF4j。
其实这一条在阿里开发手册中也是明确指出了,如下:
2、如何切换
Spring Boot默认是 Logback
日志框架,如果需要切换到其他的日志框架应该如何做?
首先我们先看官网的一张图,一切都在图中,如下:
SLF4j只是一个门面,共有两大特性。一是静态绑定、二是桥接。
什么是静态绑定?:我们以 log4j
为例。首先我们的application中会使用slf4j的api进行日志记录。我 们引入适配层的jar包 slf4j-log412.jar
及底层日志框架实现 log4j.jar
。简单的说适配层做的事情就是 把 slf4j
的api转化成 log4j
的api。通过这样的方式来屏蔽底层框架实现细节。
什么是桥接?:比如你的application中使用了 slf4j
,并绑定了 logback
。但是项目中引入了一个 A.jar
, A.jar
使用的日志框架是 log4j
。那么有没有方法让 slf4j
来接管这个 A.jar
包中使用 log4j
输出的日志呢?这就用到了桥接包。你只需要引入 log4j-over-slf4j.jar
并删除log4j.jar
就可以实现 slf4j
对 A.jar
中log4j
的接管.听起来有些不可思议。你可能会想如果删除log4j.jar
那 A.jar 不会报编译错误嘛?答案是不会。因为 log4j-over-slf4j.jar
实现了 log4j
几乎所有public
的API
。但关键方法都被改写了。不再是简单的输出日志,而是将日志输出指令委托给 slf4j
。
下面就以 log4j2
为例,切换Spring Boot的日志框架为 Log4j2
。
Spring Boot 默认是Logback日志框架,如果想要切换 log4j2
肯定是要将 Logback
的依赖移除,只需要 排除 web
模块中的 日志启动器
即可,如下:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 去掉springboot默认日志框架logback -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
排除了默认的logback依赖,肯定是需要引入 log4j2
的依赖,其实 log4j2
为了与Spring Boot适配也做 了个启动器,不需要在引入其他的jar包了,只需要添加如下依赖即可:
xml
<!-- 引入log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>