日志是我们程序重要组成部分,它是程序在运行过程当中输出的一些提示或异常信息,我们可以通过日志来观察程序执行的情况,如果程序出现 Bug,我们可以根据日志去发现和排查程序的 Bug。
SpringBoot 项目在启动的时候,就会有默认的日志输出,如下图所示:
之所以会有上面的输出,是因为 SpringBoot 中内置了日志框架。
SpringBoot 中内置了 SLF4J 和 logback 两个日志框架,用户层面并不是直接操作具体的日志对象,而是使用 SLF4J 提供给用户的 API 进而由 logback 操作具体的日志对象实现日志。
SLF4J 这类框架是使用“门面模式”来实现的,SLF4J 其实和 JDBC 很像,我们知道使用 JDBC 操作数据库,一套 JDBC 代码就可以操作很多种数据库,可以是 MySQL,Oracle,DB2,SqlServer等,而这个JDBC 就相当于代理一样,来代理去操作数据库,我们不必关心各种数据库的实现,只需关注 JDBC 提供给我们的的 API 即可。
类似的,SLF4J 中也并不是真正完成了日志实现的框架,它只是一个门面或者一个代理,我们调用 SLF4J 的 API,SLF4J 中还会去调用 logback 这样的日志实现框架,此时我们就不必关心日志实现的细节,这一层面我们是感知不到的,这就是门面模式所带来的好处,还有一个好处就是如果日志实现层出现了漏洞,只需要修改更换日志实现的框架即可,而 SLF4J 可以匹配相应的日志框架,此时虽然日志实现的框架发生了改变,但我们写的代码仍然不受影响,这样就使得系统的依赖性降低,利于系统更新和维护。
使用日志框架可以配置日志的级别来控制日志的输出,而我们日常使用System.out.printf
来输出日志是无法做到这一点的。
对于控制台输出的日志,它的各部分含义如下:
信息中的包名有部分是简写,取的是包名的第一个字母;
根据这些信息,我们就可以知道日志是发生在什么时间,在哪个线程,哪个类,以及具体的日志信息。
1️⃣第一步,在类中先获取到日志对象,这个日志对象来自于日志框架SLF4J
。
假设我们在类LogController
设置自定义日志的输出,则日志对象创建代码如下:
每一个类都对应一个日志对象,可以通过日志工厂LoggerFactory
获取的,导包的时候要注意Logger
对象是在org.slf4j
包下的,不要导错。
// 获取日志对象
private static Logger log =
LoggerFactory.getLogger(LogController.class);
getLogger()
一般传入传入当前类的类型,这里的参数用来定位日志的归属类,以方便日志输出,输出的日志信息中有了日志的定位, 才能更方便、更直观的定位到问题类。
2️⃣第二步,使用日志对象提供的方法来实现自定义日志的打印。
日志对象提供的方法有很多,可以设定不同级别的日志信息输出。
示例代码:
@RestController
相当于是将@Controller
和@ResponseBody
这两个注解合起来的效果。
package com.example.springboot2.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogController {
// 获取日志对象
private static Logger log =
LoggerFactory.getLogger(LogController.class);
@RequestMapping("/log")
public void log() {
log.trace("我是trace");
log.debug("我是debug");
log.info(("我是info"));
log.warn("我是warn");
log.error("我是error");
}
}
我们启动程序后,访问http://127.0.0.1:8080/log
后,控制台就有相应的日志输出了。
观察输出结果,我们的代码是写了 5 个级别的日志输出的,为什么这里结果只有 3 个结果呢?这是因为SpringBoot 项目下,默认日志级别为info
,低于info
日志级别的都不会输出。
日志级别分为一下几种:
SpringBoot 项目默认的日志级别是info
,那么比info
日志级别低的是不能输出的,也就是debug
与trace
的日志是不会输出的,只有大于等于info
级别的日志才会输出。
日志级别的作用:
简单来说,就是过滤信息,将业务不需要的日志屏蔽掉。
日志级别是可以通过配置文件进行设置的,日志级别包括两类,一类是全局日志级别,另一类就是局部日志级别,全局日志级别可以影响全局日志的输出,而局部日志级别只会影响一个局部的日志输出,并且局部日志级别配置大于全局日志级别的配置。
全局日志级别配置,如修改默认日志级别为trace
:
properties 日志格式:
# 设置全局日志级别
logging.level.root=trace
yml 日志格式:
logging:
level:
root: trace
运行上面我们的自定义日志代码,此时就会发现,我们所有的自定义日志信息都会输出,并且随着程序启动相比较默认情况下会有更多的日志信息输出,毕竟这里设置的是全局的配置文件,会影响全局。
下面来介绍局部日志级别的配置,我们将LogController
类的日志级别设置为warn
,此时只会LogController
类中日志输出级别,并不会影响其他位置的日志信息输出。
properties 格式配置文件:
# 设置夹局部的日志级别 (包名/包名+类名)
logging.level.com.example.demo.controller.LogController=warn
yml 格式配置文件:
logging:
level:
com:
example:
demo:
controller:
LogController :warn
控制台结果:
yml 格式配置文件可以一步设置全局和局部的日志级别,比如:
logging:
level:
root: info
com:
example:
springboot2:
controller: error
service: warn
root
设置的是全局日志级别,项目中所有日志级别都是info
;和root
同级下可设置局部具体类的日志级别m这里就是com.example.springboot2.controller
和com.example.springboot2.service
包下的类,级别分别为error
和warn
。
前面的介绍都是将日志输出在控制台上的,然而我们在生产环境下往往需要将日志保存下来,以便于后续出现问题时去追溯原因,将日志保存下来的过程就称之为持久化。
实现日志持久化的方式就是将日志信息保存到磁盘,同样可以通过设置配置文件实现。
方式1:设置日志保存路径
properties 格式配置文件:
logging.file.path=D:\\bit\\logs
yml 格式配置文件:
logging:
file:
path: D:\\bit\\logs
启动程序,此时 SpringBoot 就会将控制台中打印的日志写到对应的目录了,日志文件默认名的为spring.log
。
方式2:设置日志保存文件
properties 格式配置文件:
logging.file.name=D:\\bit\\log\\logging.log
yml 格式配置文件:
logging:
file:
name: D:\\bit\\log\\logging.log
还要注意,多次访问程序产生的日志是以追加的形式保存到日志文件当中的,SpringBoot 默认保存日志文件的大小为10MB
**,**超出范围就会自动创建新的日志文件,然后保存到新的日志文件当中。
当然,也可以通过配置文件自定义日志文件的大小,配置方式如下:
# properties格式
logging.logback.rollingpolicy.max-file-size=10MB
# yml格式
logging:
logback:
rollingpolicy:
max-file-size: 10MB
每次都使⽤ LoggerFactory.getLogger(xxx.class) 很繁琐,且每个类都添加⼀遍,也很麻烦,在 Lombok 中有一个@Slf4j
注解,可以使用该注解更简单的输出日志。
准备工作,首先要确保你的 IDEA 中有 Lombok 这个插件,没有的话去下载一下。
然后,添加 Lombok 依赖,可以使用 Edit Starters 插件快捷添加。
使用方法:在想打印日志的类上加上 @Slf4j 注解即可,它会为当前类提供一个 log 对象。
要注意使用 @Slf4j 注解,在程序中使用 log 对象即可输⼊⽇志,并且只能使⽤ log 对象才能输出,这是 lombok 提供的对象名;代码中输入 log 后就会有相关方法的提醒(前提是安装了 lombok 插件)。
package com.example.springboot2.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j // 当前的类中就可以直接使用 log 对象, @Slf4j 产生一个 log 对象, 直接使用即可
public class LogController2 {
@RequestMapping("/log2")
public void log2(){
log.trace("我是trace");
log.debug("我是debug");
log.info(("我是info"));
log.warn("我是warn");
log.error("我是error");
}
}
启动程序,访问路路径http://127.0.0.1:8080/log2
,同样可以输出日志,代码更简单。
@Slf4j 注解替代了日志对象获取的代码:
private final static Logger log = LoggerFactory.getLogger(LogController2.class);
Lombok 框架其实在编译的时候,将依据注解去替生成对应的代码,比如就像上面的日志对象创建,加上一个 @Slf4j 注解后,在编译时就会将类似与上面创建日志对象的代码生成到我们所写代码里面去,包括Lombok 其他的注解,如生成 Setter 的注解 @Setter,生成 Getter 的注解 @Getter 等都是通过编译时期间实现的。
比如上面我们写的代码最终生成相应的字节码文件就在 target 目录中,target 目录中的代码才是项目最终执行的代码,查看 target 目录如下:
我们来对比一下我们LogController2.java
原文件代码和程序运行起来后生成LogController2.class
文件中的代码(由 IDEA 反编译显示)。
发现代码中的 @Slf4j 注解就不存存在了,被 log 对象替代了,所以,Lombok 是不会影响程序运行的信能的,它要完成的工作都是在编译生成字节码文件前完成的。
其他常用注解:
基本注解:
注解 | 作用 |
---|---|
@Setter | ⾃动添加 setter 方法 |
@Getter | ⾃动添加 getter 方法 |
@ToString | ⾃动添加 toString 方法 |
@EqualsAndHashCode | ⾃动添加 equals 和 hashCode 方法 |
@NoArgsConstructor | ⾃动添加⽆参构造方法 |
@AllArgsConstructor | ⾃动添加全属性构造⽅法,顺序按照属性的定义顺序 |
@NonNull | 属性不能为 null |
@RequireArgsConstructor | ⾃动添加必需属性的构造方法,final + @NonNull 的 属性为必需 |
组合注解:
注解 | 作用 |
---|---|
@Data | @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor + @NoArgsConstructor |
日志注解:
注解 | 作用 |
---|---|
@Slf4j | 添加⼀个名为 log 的日志,使用 slf4j |
使用 Lombok 提供给我们的这些注释,可以很好的帮助我们消除项目中大量冗余的代码,可以使得我们的 Java 类可以看起来非常的干净整洁。