引言
关于日志
我们日常在写代码的时候,出现了异常,都会在编译器的控制台上,找出问题。这些问题并不是我们盲猜的,而是控制台上有明显的日志打印,我们就可以根据日志打印发现问题、解决问题。所以说,日志是调试程序重要的一环。
此外,日志除了发现和定位问题外,它还有如下实用的功能:
- 记录用户登录日志, 方便分析用户是正常登录还是恶意破解用户。
- 记录系统的操作日志,方便数据恢复和定位操作人。
- 记录程序的执行时间, 方便为以后优化程序提供数据支持。
基于上面的介绍,本篇博客着重介绍 Spring Boot 提供的日志框架。
一、Spring Boot 日志的格式说明
通常来说,如果我们的 Sping Boot 项目没有出现异常或错误信息,那么当我们运行启动类后,就会出现下面的日志信息:一个 Spring 图标,和一些基本都是 info 级别的日志信息。所以在一般情况下,我们就是通过这样的打印日志,来判定一个项目是否真正的编译无误了。
那么,这些日志信息是什么意思呢?请看下图:
二、自定义打印日志
我们创建一个 Spring Boot 项目后,再创建一个 " UserController " 类,我们预期不给前端返回数据,只是最终能够在 IDEA 控制台显示日志。
@Controller @ResponseBody public class UserController { // 1. 先得到日志对象 private final static Logger log = LoggerFactory.getLogger(UserController.class); @RequestMapping("/hello") public void hello() { // 2. 使用日志对象提供的打印方法进行日志打印 log.trace("我是 trace"); log.debug("我是 debug"); log.info("我是 info"); log.warn("我是 warn"); log.error("我是 error"); } }
展示结果:
说明: 刚开始运行时,我们是看不到红色框框中的内容的,我们需要在前端输入 " hello " 这个 URL 后,才能看到下面的日志。这很好理解,因为日志打印的代码是我们自定义生成的,而且写在了 hello 方法内。 如下图所示:
然而,就算我们输入了 " URL ",我们最终发现只打印了三个自定义信息,但是,我们一开始在 hello 方法中,自定义的是五个日志信息,结果却少了两个信息。这就涉及到了【日志级别】的问题了,Spring Boot 框架默认的日志级别是 info,所以,只显示了 info、warn、error.这里不用担心,本篇博客后面会介绍到原因,请继续往下看。
解析代码
1. 先得到日志对象
private final static Logger log = LoggerFactory.getLogger(UserController.class);
对于上面这段代码,它是固定写法,但我们必须要知道,Logger 类、LoggerFactory 类,它们两是 Spring Boot 框架中 " slf4j " 这个包提供的。
关于 " slf4j ",我们在本篇博客的后面也会介绍到。
此外,使用 " getLogger " 这个方法的时候,我们要指定一个类对象,因为正是这个类对象告诉了 Spring Boot,之后的日志打印要精确到哪个类。
2. 使用日志对象提供的打印方法进行日志打印
下面的五个方法,也是 " slf4j " 这个包所提供的,五个方法实际上对应了五个日志级别,方法传入的参数,就是我们自定义的日志打印。这里需要明确,输出的日志和我们平时使用的 【 System.out.println(); 】不同,这里输出的日志包含在 Spring Boot 内置的框架中,也就是说,它是框架提供的功能。
log.trace("我是 trace"); log.debug("我是 debug"); log.info("我是 info"); log.warn("我是 warn"); log.error("我是 error");
三、日志级别
- trace:少许的日志 ( 级别最低 )
- debug:调试日志 ( 针对于开发人员调试的场景 )
- info:普通信息日志 ( Spring Boot 框架默认的级别 )
- warn:警告日志 ( 不影响使用,但仍需要注意问题 )
- error:错误日志 ( 级别较高 )
- fatal:致命 / 异常日志 ( 系统输出的日志,不能自定义打印 )
六种日志级别,由低到高,越往级别高的日志,接收到的信息越少。
比方说:
当你的日志级别设置为 info 的时候,你只能看到 info、warn、error、fatal.
当你的日志级别设置为 error 的时候,你只能看到 error、fatal.
这很好理解,假设你的项目出现了异常,那么它优先显示错误的日志,因为正常的数据信息与你无关,你也不会对正常的代码进行排查。
1. 通过配置文件设置日志级别
为 Spring Boot 项目设置日志级别,我们一般是将配置项放在配置文件中,下面是两种配置文件的配置格式。
2. 注意事项
(1) 我们既可以设置整个项目的日志级别 ( 全局配置 );也可以设置局部目录的日志级别。进行局部设置时,精确到某个类对应的目录即可,不需要精确到某个类。
(2) 刚刚上面所说的六种日志级别,都可以通过上图的格式进行配置。
(3) 假设我们配置如下代码:
# 全局配置日志 logging.level.root=warn
我们就可以看到输出日志,只有 warn 和 error,也就是我上面提到的,日志级别越高,接收到的信息就越少。
(4) 假设我们设置如下代码:
# 设置全局的日志级别 logging.level.root=info # 设置局部目录的日志级别 logging.level.com.example.demo=warn
当我们同时设置了全局日志和局部日志,那么,指定的局部目录的优先级就会大于全局日志的优先级。
四、日志持久化
日志持久化的意思就是将日志永久地保存到磁盘中。
为什么需要日志持久化呢?
答:我们平时利用 IDEA 随手写一个代码,再进行测试,是感知不到问题的,因为我们都是哪里出错了,再通过即时的日志信息来解决问题就可以了。所以说,IDEA 上的控制台程序只是临时的一份,一般来说,我们运行一次,就会出现一个新的日志信息。然而,在有些场景下,一个项目就需要将日志信息保存下来,以便于出现了问题,进行追溯。由于 Spring Boot 也是为我们后续的 Web 开发服务的,所以框架也实现了一个持久化存储的功能,它依然需要在配置文件中实现。
1. 日志持久化的两种方式
(1) 指定磁盘目录后,由框架自动生成一个文件,并把日志信息放入到此文件中。
logging.file.path=D:/Data
(2) 指定目录 + 文件名,框架会将日志信息放入自定义的文件中
logging.file.name=D:/Data/spring-boot.log
我们以第一种指定目录的方式为例,演示 " UserController " 类,最终发现,框架为我们生成了一个 " spring.log " 这样的文件,我们可以利用记事本的形式打开验证。
2. 注意事项
(1) 上面只演示了 properties 配置文件,实际上 yml 配置文件的思想是一样的,只是格式不同罢了。
但是经过我的测试发现,properties 配置文件的目录不能写成中文,否则日志文件就不会自动生成,但 yml 配置文件却可以配置成中文的。这可能是编码问题,也可能是 IDEA 版本问题。但是我认为,如果想避免此问题,还是将目录写成英文的好。实际上,很多场景下,目录和文件确实不宜写成中文。
(2) 框架为我们提供的日志持久化非常的人性化,我们不用担心,在日志文件中,后续的日志信息会将前面的日志信息覆盖。我们可以多运行几次启动类,结果都会发现,文件中的数据是以时间顺序保存着的,然而,文件始终就只有一份。
这让我想起了 Java 中的流对象的,它就可以实现将文本数据按照拼接字符串的方式,每一次生成的数据,就可以放在原先文件的最后方。而在这里,思想是一样的。
如下图所示:
(3) 也许你会担心如果一个大型项目的日志信息,多的有几个 G 怎么办?那个时候,光是找某个类的日志都会费好大的劲怎么办?
其实,这也不用担心。实际上,框架的底层为我们设计好了一个生成文件的最大内存不超过 10M. 万一超过 10M,就会以编号的方式,重新生成文件,放在同一个目录下。
以后在公司,公司或许有另类的规定,但在日常的学习中,自己做一个项目的话,即使你用到了日志持久化,一般情况下,10M 肯定也到不了。
五、利用 lombok 来做到更简单的日志输出
使用 lombok 这个依赖,添加 " @Slf4j " 注解,它可以替代我们之前的操作。
@Controller @ResponseBody @Slf4j // 替代了之前需要通过 LoggerFactory.getLogger 的操作 public class UserController2 { @RequestMapping("/hello2") public void hello2() { log.trace("我是 trace2"); log.debug("我是 debug2"); log.info("我是 info2"); log.warn("我是 warn2"); log.error("我是 error2"); } }
设置局部目录的日志级别为 " trace ",并访问 " hello2 " 这个 URL,查看输出结果:
1. lombok 的使用原理
请看下面的这幅图,它完美诠释了 lombok 的原理,左边是我们加上 " @Slf4j " 注解的代码,它是一个 " .java " 文件,右边代码就是左边 " .java " 文件经编译后,生成的一个 " .class " 文件。在 " .class " 文件中,我们可以清晰地看到,左边的注解已经被转换成了一个 " Logger " 对象了,这也是博客开篇,我们写的第一步。
这样一来我们就明白了,lombok 其实是在 " .java " 文件进行编译的时候,在底层做了一些事情,所以,这就是我们写注解方便的原因。
2. 注意事项
上面的 " .class " 文件来源于 【 target 】目录下,当我们创建好一个项目时,它并不是自动生成的,而是当我们点击 main 方法运行时,IDEA 自动帮我们生成的。IDEA 其实就是照着我们平时写的 " .java " 文件,又生成了一个同名的 " .class " 文件,只是我们感知不到而已。
我们知道,一个 Java 程序,最终是在 JVM 运行的,但 JVM 实际上只认识 " .class " 文件。所以,如果我们拆解来看,我们平时写的代码,就是放在 " .java " 文件中的,然后再通过编译,变成了 " .class " 文件,最终才给 JVM 运行。
然而,IDEA 帮我们省去了编译的过程,或者说,我们平时根本感受不到编译,只顾着写代码了,但确实,IDEA 帮我们实现了 " 编译 + 运行 " 的整个过程。不过 IDEA 有一点好处,先抛开结果是否符合预期不谈,IDEA 可以在我们写代码的时候,就能够判定是否能正常编译,这是一个非常强大的功能。
此外," .class " 文件,实际上是一个字节码文件,也就是说,它的内容实际上就是以二进制的形式呈现的,只不过,IDEA 帮我做了一些处理,让程序员能够看懂代码。如果我们利用记事本打开 " .class " 文件,就会发现里面的内容是乱码。
3. lombok 提供的更多注解
① 基本注解:
注解 | 作用 |
@Getter | 自动添加 getter 法 |
Setter | 自动添加 setter 法 |
@ToString | 自动添加 toString 法 |
@EqualsAndHashCode | 自动添加 equals 和 hashCode 法 |
@NoArgsConstructor | 自动添加无参的构造方法 |
@AllArgsConstructor | 自动添加全属性构造方法,顺序按照属性的定义顺序 |
@NonNull | 属性不能为 null |
@RequiredArgsConstructor | 添加必需属性的构造方法,final + @NonNull 的属性为必需 |
② 组合注解:
注解 | 作用 |
@Data | @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor + @NoArgsConstructor |
③ 日志注解:
注解 | 作用 |
@Slf4j | 自动添加一个名为 log 的日志 |
4. 在 Spring Boot 项目中临时添加 lombok
如果我们的 Spring Boot 项目,在刚开始创建的时候,我们选中我们想要的依赖,那么,我们不用重新创建一个项目,只需要临时添加依赖即可。
第一步,搜索插件。
第二步,在 pom.xml 文件中,点击 Generate 生成。
第三步,选择我们需要添加的依赖。选好后,依赖就会从 maven 仓库引入。
六、关于 Slf4j
" Slf4j " 是 lombok 这个依赖提供的包,不管是我们自己手动引入对象,还是通过注解的方式添加日志," Slf4j " 的核心就是下面这段代码。
private final static Logger log = LoggerFactory.getLogger(UserController.class);
所以,我们也可以说是 Spring Boot 内置了日志框架 " Slf4j " . 而 " Slf4j " 其实是遵循了门面模式,不管底层的实现代码怎么变," Slf4j " 提供的代码都是固定写法。
所以有时候,我们只需要会用上层的固定写法即可,底层实现的思想就算不理解,也能够将一个项目做的很好。 比方说:当前我们使用的就是 " Slf4j " 提供的一些方法,然而,它的日志实现是基于 " logback " 的。
这就好像," Slf4j " 是直播带货的," logback " 才是真正的生产厂家。
这里的门面模式,它和我们正儿八经写的 JDBC 代码差不多,不管我们连接的是 MySQL 数据库,还是 Oracle 数据库,还是其他的数据库…我们开发人员在用的时候,只关注 JDBC 是怎么用的,至于其他的,底层已经为我们封装好了。