Spring Boot教程(18) – 管理日志

日志用来记录你程序运行的中的一些关键信息,方便你调试以及后期上线的时候查找问题。典型的用法就是应用出现5xx错误了之后,上日志里找找哪里抛出了异常,异常调用栈是什么。

混乱的日志框架

其实Java语言本身有着一套日志工具,在java.util.logging包下,简称JUL。JUL大家好像不怎么用,易用性不行,性能也没跟得上。大家都用Log4j,使用得也比较广泛,本身属于Apache软件基金会(ASF)。Log4j的作者Ceki后来搞了个Logback,弥补了Log4j的缺点。ASF后来也把Log4j升级成了Log4j2,前者于15年停止更新,后者对于Logback和Log4j也是取其精华,弃之糟粕(PS:Ceki和ASF绝对有过节)。所以目前可用的日志实现也就是JUL、Log4j2和Logback

日志框架这么多,想在不同的框架之间切换还挺不方便,然后就诞生了Commons LoggingSLF4J,他们都提供统一的API,你只针对API编程就行,底层的实现可以方便地切换。SLF4J的API对应的中央仓库模块是slf4j-api,SLF4J有一个是实现是slf4j-simple,一般不用。其实Log4j2也有两个模块,log4j-apilog4j-core,分别是API和具体实现,你用log4j-api来编写日志代码就行,如果你提供了log4j-core那就用真正的Log4j2的实现,如果提供了其他实现,就用其他的,如果任何实现都没提供,log4j-api里还包含一个简单的实现叫做SimpleLogger,一般不用。虽然Log4j2无意成为和Commons Logging和SLF4J一样的日志门面(Logging Facade),但他的确可以这么做。所以说目前可用的日志接口也就Commons Logging、slf4j-api和log4j-api

这世界要是这么简单美好就好了,可惜由于同一项目不同团队的选型不一致,或者项目本身和所依赖的库选型不一致,你需要将日志桥接起来。比如你的A项目使用了SLF4J,但是依赖的库B使用了JUL,你可能需要使用jul-to-slf4j将JUL的日志导流到SLF4J。诸如此类,导来导去的需求非常多,我曾经想把他们的关系理清楚,后来我的理智制止了我,并对我说:“你那几千行的小破项目,浪费时间干啥,Spring Boot日志默认的不够你用么?”

Spring Boot中的日志

Spring的内部使用了Commons Logging,如果你的项目里有Log4j2,那就用Log4j2;如果有SLF4J,那就用SLF4J;如果都没有,就用JUL。

如果你引入了Web Starter,它会自动引入入Logging Starter,后者会引入Logback以及SLF4J,你可以使用SLF4J的API来编写log代码,实际底层会调用Logback。另外Logging Starter还包含了log4j-api依赖,用此依赖你可以使用Log4j2的API来编写代码,但是Logging Starter并没有使用引入log4j-core这个具体实现,而是用了log4j-to-slf4j这个实现,它将Log4j2的调用转发到SLF4J上。

Logging Starter还包含了jul-to-slf4j依赖,它将JUL的日志转到的SLF4J。所以,总的来说,不管你写代码时候用的是Spring内部的Commons Logging,还是JUL,还是slf4j-api,还是log4j-api,最终都调用到了Logback。

那最终用哪个API好呢?我的建议是用SLF4J,如果感觉它的API(slf4j-api)不够用,比如你需要lambda来对日志参数进行延迟计算,再去换用Log4J2的API,也就是log4j-api模块。Commons Logging已经许久没有维护,Spring用它应该也是历史原因吧。SLF4J的API使用比较简单,如下图(实用主义者其实本文其他内容不用看,直接看了图,就能去代码里用了):

图中日志语句执行之后,会有如下输出:

在“Home Page Now”字符串的前面,还添加了许多信息,话说我没让日志框架输出这些东西,他咋就输出了呢?

因为Spring Boot已经默认配置好了输出的格式,你可以看到日志级别、时间、进程号、线程名等等。不光是Logback,Log4j2和JUL的配置工作Spring Boot也做好了,所以不管你用什么实现,最终输出的东西都是差不多的。

SLF4J日志级别有五个,从高到底分别为 ERROR、 WARN、 INFO、 DEBUG 和 TRACE。默认情况下,应用只会输出INFO及其以上的级别。如果你想查看DEBUG及以上的日志,可以给java -jar app.jar添加--debug参数,或者在application.properties添加debug=true属性,想看TRACE同理。不过这样只会控制Spring Boot内部以及tomcat或者hibernate等的日志,你自己编写的日志是管不到的。想控制全局默认日志输出级别?配置logging.level.root属性。

日志输出

默认情况下,日志会被输出到标准输出,也就是你在命令行里看到的。你还可以将日志同时输出到文件里。

你可以使用logging.file指明日志具体保存到哪个文件,可以是绝对路径或者相对路径。你可以使用logging.path指明具体哪个文件夹,日志文件会保存在这个文件夹下,并命名为spring.log,文件夹可以是绝对路径或者相对路径。logging.filelogging.path只需要设置一个就行。

默认情况下,日志每天都会打包一下, 压缩成.gz文件保存下来。同时你可以使用logging.file.max-size可以指定最大的日志文件大小,比如100MB,如果到达这个大小之后,就会被压缩成.gz文件保存下来,一天中保存的多个压缩文件会用序号来区分,如上图。logging.file.max-history属性可以设置压缩文件最多可以保存多少天,超过期限的就删掉。以上规则Spring Boot文档中其实写的不是很清楚,我是看看了源码才搞懂的。

Spring Boot中很多日志属性是和Logback严格相关的,如果你感兴趣,可以查看org.springframework.boot:spring-boot:2.1.7.RELEASE源码里的org.springframework.boot.logging.logback包下的相关配置文件defaults.xml和base.xml等等。实在不行还可以看Logback文档。真要自定义一些配置的话,可以在src/main/resources目录下新建logback.xml或者logback-spring.xml文件,Spring Boot推荐使用logback-spring.xml,方便它做一些初始化的工作。

总结

日志框架会用就行,真的需要定制之前别钻太深。

发表评论

电子邮件地址不会被公开。