SpringBoot--学会配置日志

理解SpringBoot 的日志设计

在项目中导入spring-boot-starter.jar依赖,它会传递 导入spring-boot-starter-logging.jar依赖,依赖关系如下图:

spring-boot-starter-logging.jar依赖三个jar包:

  1. logback-classic.jar:它传递依赖于logback-core.jar和slf4j-api.jar
  2. log4j-to-slf4j.jar:它传递依赖于log4j-api.jar和slf4j-api.jar
  3. jul-to-slf4j.jar:它传递依赖于slf4j-api.jar

java的日志框架比较多,常见的包括:SLF4J、Log4j、Log4j2、Logback、common-logging(JCL)、java.util.logging(JUL)、JBoss Logging等,这些日志框架又分:

  1. 门面类(抽象层):SLF4J、JCL、JBoss Logging
  2. 日志实现:Log4j、Log4j2、Logback、common-logging(JCL)

SpringBoot默认使用SLF4J+Logback组合,SLF4J作为日志门面(应用程序输出日志时应该面向改API),Logback作为日志实现。

由于SpringBoot要整合大量的第三方框架,这些框架可能使用JCL、Log4j、JUL等。因此SpringBoot提供对应的日志路由,将其他框架生成的日志信息统一路由给SLF4J处理。从上面依赖关系可以看出:

  1. log4j-to-slf4j.jar:负责将Log4j日志路由到SLF4J
  2. jul-to-slf4j.jar:负责将JUL日志路由到SLF4J

虽然SpringBoot默认采用Logback作为底层日志实现,但通过配置允许将底层日志实现改为其他框架。SpringBoot允许将Logback依赖排除出去,添加其他日志实现(比如log4j)的依赖。

需要注意的是:当吧SpringBoot应用部署到Web服务器或应用服务器上时,JUL生成的日志不会被路由到SpringBoot应用的日志中,这是为了避免将服务器或者服务器上的其他应用的日志也路由到SpringBoot的日志中,否则会造成日志混乱。

日志级别与格式

代码示例:控制器类

java 复制代码
@RestController
public class HelloController
{
   Logger logger = LoggerFactory.getLogger(this.getClass());
   @GetMapping
   public Map<String, Object> hello()
   {
      logger.trace("-------TRACE级别的日志-------");
      logger.debug("-------DEBUG级别的日志-------");
      logger.info("-------INFO级别的日志-------");
      logger.warn("-------WARN级别的日志-------");
      logger.error("-------ERROR级别的日志-------");
      return Map.of("hello", "Hello");
   }
}

日志级别主要分为(级别由低到高):

  1. all:输出所有日志
  2. trace
  3. debug
  4. info
  5. warn
  6. error
  7. fatal:log4j增加的一种日志级别,代表"致命错误",比error级别更高。(由于SpringBoot不支持此级别,因此会被自动转换为error级别)
  8. off:关闭所有日志

日志系统有一个规则:当日志的输出方法的级别高于或等于日志的设置级别,该日志才会实际输出。

比如日志级别设为info,当程序使用info()、warn()、error()输出时,日志才会实际输出;使用trace()、debug()输出的日志会被忽略。

因此,日志级别越高,输出日志就越精简,性能开销越小;日志级别越低,输出日志就越详细,性能开销越大。一般项目处于开发、测试、试运行阶段,日志级别设置的低;在项目实际运行阶段,日志级别设置的高。

由上图可知,SpringBoot默认的日志级别是Info。

SpringBoot输出的日志包括如下信息:

  1. 日期和时间:精确到毫秒
  2. 日志级别
  3. 进程ID
  4. 分隔符:三个减号(---)
  5. 线程名:方括号里面的内容(在控制台输出时可能会被截断)
  6. 日志名:通常是完整类名(为了便于阅读,包名经常简写)
  7. 日志信息

设置日志级别的几种方式:

  1. 通过debug=true或trace=true等属性(可通过配置文件、命令行参数、系统变量、OS环境变量等方式)改变整个SpringBoot核心的日志级别
  2. 通过logging.level. = 属性(可通过配置文件、命令行参数、系统变量等方式)设置日志级别。其中 代表日志名,通常是包名或全限定类名,而Level可以是各种日志级别。

需要注意的是,当启用trace或debug模式时,SpringBoot的核心日志(包括嵌入式容器、Hibernate和SpringBoot)被设为对应的级别,但是其他程序组件不会被设为对应级别。

例如:添加命令行参数: --trace 。 启动上面代码运行结果如下:

可见,程序组件本身的日志级别没有改变。

那么要设置程序组件的日志级别,要通过logging.level. = 属性来设置

yaml 复制代码
logging:
  level:
    # 将org.crazyit.app包及其子包下所有日志级别设为TRACE
    org.crazyit.app: trace

运行结果如下

学会之后解决以下几个问题就很简单了。

  1. 让Mybatis输出SQL语句(logging.level.<mapper组件所在的包>=debug)
  2. 输出Redis的详细执行过程(logging.level.io.lettuce.core=debug)
  3. 输出MongoDB的详细执行过程(logging.level.com.mongodb=debug)

SpringBoot允许通过spring.output.ansi.enabled属性设置是否用不同颜色来区分不同级别日志,该属性支持以下属性值:

  1. always:总是启用
  2. detect:自动检查。如果控制台支持ansi颜色特性,则启用。这是默认值。
  3. never:不启用

如果要改变控制台的日志格式,可通过logging.pattern.console属性进行设置。其默认值是:

%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint}

%clr(${LOG_LEVEL_PATTERN:-%5p})

%clr(${PID:- }){magenta}

%clr(---){faint}

%clr([%15.15t]){faint}

%clr(%-40.40logger{39}){cyan}

%clr(😃{faint}

%m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}

上面配置由多个"%clr(输出内容){颜色值} "片段组成,每个片段代表一个输出元素,其中{颜色值} 用于指定该片段的颜色。此处颜色值支持如下几个值:

  1. blue:蓝色
  2. cyan:青色
  3. faint:原色
  4. green:绿色
  5. magenta:紫红色
  6. red:红色
  7. yellow:黄色

如果不指定颜色之后,直接使用"%clr(输出内容)",表面使用默认的颜色。

比如上面设置包含以下片段:

  1. %clr(${LOG_LEVEL_PATTERN:-%5p}) :表明以日志级别对应的颜色来输出
  2. %clr(${PID:- }){magenta} :以紫红色输出进程ID
  3. %clr(---){faint} :以原色来输出三个减号(---)

假如输出日志不要显示日期、时间,设置时去掉"%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} "片段即可。

需要注意的是:logging.pattern.console属性仅当使用Logback日志实现时才有效。

输出日志到文件

要将日志输出到文件,设置如下两个属性之一:

  1. logging.file:设置日志文件
  2. logging.path:设置日志文件目录。使用默认的spring.log作为文件名。

使用logging.file或logging.path属性输出日志到文件

logging.file.name logging.file.path 示例 描述
只输出到控制台
指定文件 my.log 输出到特定文件,文件路径可以是绝对或相对路径
指定目录 /f:/log 写入指定路径下的spring.log文件,该路径可以是绝对或相对路径

SpringBoot默认只将info、warn、error三个级别的日志输出到文件。

当日志文件达到10MB时,会自动使用新文件。若要改变这个设置,对于Logback日志实现(SpringBoot默认),可直接使用application.properties(或application.yml)设置;对于其他日志实现,需要对应的日志设置文件来设置。比如log4j,需要使用log4j.xml设置。

Logback的日志设置

属性名称 描述
logging.logback.rollingpolicy.file-name-pattern 设置对日志归档的文件名模版
logging.logback.rollingpolicy.clean-history-on-start 应用启动时是否清除日志文档
logging.logback.rollingpolicy.max-file-size 日志文件归档之前的最大大小
logging.logback.rollingpolicy.total-size-cap 日志归档被删除之前所能容纳的最大大小
logging.logback.rollingpolicy.max-history 设置保留多少天的日志归档(默认7天)

代码示例:控制器类org.crazyit.app.controller

java 复制代码
@RestController
@Slf4j
public class HelloController
{
   @GetMapping
   public Map<String, Object> hello()
   {
      log.trace("-------TRACE级别的日志-------");
      log.debug("-------DEBUG级别的日志-------");
      log.info("-------INFO级别的日志-------");
      log.warn("-------WARN级别的日志-------");
      log.error("-------ERROR级别的日志-------");
      return Map.of("hello", "Hello");
   }
}

上面的控制器类使用@Slf4j注解修饰,里面的方法可直接使用 log.trace()等方法来输出日志,那这个log对象是从哪来的?

因为本例使用Lombok工具,这个工具专门通过各种注解来生成常用的代码,比如以下常用的注解:

  1. @Getter:为所有实例变量生成getter方法
  2. @Setter:为所有非final实例变量生成setter方法
  3. @ToString:自动生成toString()方法
  4. @EqualsAndHashCode:自动生成equals()和hashCode()方法
  5. @AllArgsConstructor:自动生成带所有参数的构造器
  6. @NoArgsConstructor:自动生成无参构造器
  7. @Data:自动生成一个数据类,相当于@Getter、@Setter、@ToString、@EqualsAndHashCode、@AllArgsConstructor、@NoArgsConstructor等注解的组合
  8. @Log、@Log4j、@Log4j2、@Slf4j、@CommonsLog、@JBossLog、@Flogger:为对应的日志实现生成一个日志对象

为了在应用中使用Lombok,需要做一下两件事:

  1. 添加Lombok依赖:

    xml 复制代码
    <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.16</version>
    </dependency>
  2. 为IDEA添加Lombok插件,通过主菜单FIle-》Settings-》Plugins-》搜索插件,然后安装即可。

代码示例:application-addition.yml

yaml 复制代码
logging:
  level:
    # 将org.crazyit.app包及其子包下所有日志级别设为TRACE
    org.crazyit.app: trace
  file:
    # 指定日志文件的输出目录,在应用的根目录下生成logs文件夹,该文件夹下生成默认文件名为spring.log
#    path: logs/
    # 指定日志文件,生成在应用的根目录下
    name: my.log

日志组

日志组就是将多个包、类组合在一起,起一个名字,以后可直接对改组设置日志级别,对改组设置就相当于同时为改组内的所有包及其子包、类统一设置了日志级别。

代码示例:控制器类,为上面的控制器类再增加这个控制器类org.fkjava.app.controller

java 复制代码
@RestController
@Slf4j
public class FkController
{
   @GetMapping("/fk")
   public Map<String, Object> hello()
   {
      log.trace("-------TRACE级别的日志-------");
      log.debug("-------DEBUG级别的日志-------");
      log.info("-------INFO级别的日志-------");
      log.warn("-------WARN级别的日志-------");
      log.error("-------ERROR级别的日志-------");
      return Map.of("hello", "Hello");
   }
}

代码示例:application-addition.yml

yaml 复制代码
logging:
  group:
    # 将org.crazyit.app和org.fkjava.app两个包定义成fkapp组
    fkapp: org.crazyit.app, org.fkjava.app
  level:
    # 将fkapp组对应的包及其子包的所有日志级别设为TRACE
    fkapp: trace

关闭控制台日志

如果想改变SpringBoot的底层日志实现(放弃Logback),则需要如下2步:

  1. 去掉Logback依赖库,添加新日志实现的依赖库
  2. 在类加载路径的根路径下为新日志提供对应的配置文件

SpringBoot默认从类加载路径的根路径下加载日志框架的配置文件,也可通过logging.config属性来设置新的加载路径。

SpringBoot既可以根据底层依赖库自动选择合适的日志实现,也可通过org.springframework.boot.logging.LoggingSystem属性显式指定日志实现。属性值可以是LoggingSystem实现类的全限定类名(比如Log4J2LoggingSystem, LobbackLoggingSystem, JavaLoggingSystem等类的全限定类名);也可设置为none,也就彻底关闭SpringBoot的日志系统了。

注意:由于日志初始化会在ApplicantContext创建之前完成,因此不能通过SpringBoot的配置文件来配置logging.config、org.springframework.boot.logging.LoggingSystem等日志控制属性,只能通过系统属性来设置。

不同日志系统对应的配置文件

日志系统 配置文件
Logback logback-spring.xml, logback-spring.groovy, logback.xml或logback.groovy
Log4j2 log4j2-spring.xml或log4j2.xml
JDK(JUL) logging,properties

注意:SpringBoot推荐使用带-spring的配置文件,比如Logback日志系统,使用 logback-spring.xml更好。此外尽量避免使用JUL日志系统,因此JUL的类加载机制会导致一些问题。

对日志进行定制的属性

SpringBoot属性 系统属性 说明
logging.exception-conversion-word LOG_EXCEPTION_CONVERSION_WORD 记录异常的转换字
logging.file.name LOG_FILE 指定日志文件名
logging.file.path LOG_PATH 指定日志输出路径,使用spring.log作为文件名
logging.pattern.console CONSOLE_LOG_PATTERN 控制台日志的格式模板
logging.pattern.dateformat LOG_DATEFORMAT_PATTERN 日期格式模板
loggging.charset.console CONSOLE_LOG_CHARSET 输出控制台日志的字符集
logging.pattern.file FILE_LOG_PATTERN 文件日志的格式模板,仅当日志输出到文件时才有效
logging.charset.file FILE_LOG_CHARSET 文件日志所用的字符集,仅当日志输出到文件时才有效
logging.pattern.level LOG_LEVEL_PATTERN 指定输出日志级别时使用的格式(默认为%5p)
PID PID 当前进程ID

SpringBoot为logback提供了一些通用的配置文件,开发者只要导入这些配置文件,即可使用预定义的配置。这些文件位于org/springframework/boot/logging/logback/路径下,其中常用的有:

  1. defaults.xml:提供了转换规则及各种通用配置
  2. console-appender.xml:定义一个ConsoleAppender,将日志输出到控制台
  3. file-appender.xml:定义一个RollingFileAppender,将日志输出到文件

代码示例:logback-spring.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <!-- 导入Logback通用的日志配置 -->
   <include resource="org/springframework/boot/logging/logback/defaults.xml" />
   <!-- 定义日志文件 -->
   <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
   <!-- 导入输入到文件的日志配置 -->
   <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
   <!-- 指定将日志输出到文件 -->
   <root level="INFO">
      <appender-ref ref="FILE" />
   </root>
</configuration>

在上面的配置文件中可指定如下占位符:

  1. ${PID}:当前进程ID
  2. ${LOG_FILE}:代表是否通过外部配置设置了logging.file.name属性
  3. ${LOG_PATH}:代表是否通过外部配置设置了logging.file.path属性
  4. ${LOG_EXCEPTION_CONVERSION_WORD}:代表是否通过外部配置设置了logging.exception-conversion-word属性

上面配置将日志输出到文件,因此必须在application-addition.yml指定logging.file.name或logging.file.path属性。

代码示例:application-addition.yml

yaml 复制代码
logging:
  level:
    # 将org.crazyit.app包及其子包下所有日志级别设为TRACE
    org.crazyit.app: trace
  file:
    # 指定日志文件的输出目录,默认文件名为spring.log
#    path: logs/
    # 指定日志文件
    name: my.log

此例子不会在控制台输出日志,仅在当前目录的my.log文件中输出日志。

改用Log4j2日志实现

去除Logback依赖库,添加Log4j2依赖库

xml 复制代码
<!-- Spring Web依赖 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
      <!-- 去除spring-boot-starter-logging日志 -->
      <exclusion>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-logging</artifactId>
      </exclusion>
   </exclusions>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

通过上面配置,项目底层日志框架改成了Log4j2,得益于SpringBoot的日志的抽象机制,上层程序使用的日志没有任何改变。

如果要对Log4j2自定义配置,可通过log4j2.yml或log4j2.json配置。

Logback扩展

可通过logback-spring.xml对Logback配置扩展功能。注意:不能使用logback.xml,因为该文件的加载时机太早,SpringBoot的其他基础功能还没来得及加载。

springProfile标签的name属性可指定相关Profile的日志配置

代码示例:logback-spring.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <!-- 以下配置仅当活动Profile为default、dev和test时有效 -->
   <springProfile name="default | dev | test">
      <include resource="org/springframework/boot/logging/logback/defaults.xml" />
      <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
      <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
      <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
      <root level="DEBUG">
         <appender-ref ref="CONSOLE" />
         <appender-ref ref="FILE" />
      </root>
      <!-- 指定org.crazyit.app日志的级别是DEBUG -->
      <logger name="org.crazyit.app" level="DEBUG"/>
   </springProfile>
   <!-- 以下配置仅当活动Profile为prod时有效 -->
   <springProfile name="prod">
      <include resource="org/springframework/boot/logging/logback/defaults.xml" />
      <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
      <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
      <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
      <root level="INFO">
         <appender-ref ref="CONSOLE" />
         <appender-ref ref="FILE" />
      </root>
      <!-- 指定org.crazyit.app日志的级别是INFO -->
      <logger name="org.crazyit.app" level="INFO"/>
   </springProfile>
</configuration>

此外,logback的配置文件还可读取SpringBoot的配置属性,通过<springProperty.../> 元素来获取,该元素支持以下属性:

  1. name:为读取到的属性值指定名字
  2. source:指定读取哪个配置属性。推荐使用"烤串"写法(比如application-dev)
  3. scope:指定存储该配置属性的作用域
  4. defaultValue:当配置属性不存在时,指定默认值

比如:

xml 复制代码
<springProperty scope="context" name="fluentHost" source="myapp.fluentd.host" defaultValue="localhost" />
   <appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender">
<!--      使用前面定义的fluentHost属性-->
      <remoteHost>${fluentHost}</remoteHost>
   </appender>
相关推荐
lemon_sjdk32 分钟前
Java飞机大战小游戏(升级版)
java·前端·python
LUCIAZZZ40 分钟前
高性能网络模式-Reactor和Preactor
java·服务器·开发语言·网络·操作系统·计算机系统
Dcs1 小时前
Java 开发者必读:近期框架更新汇总(Spring gRPC、Micronaut、Quarkus 等)
java
Pi_Qiu_2 小时前
Python初学者笔记第十三期 -- (常用内置函数)
java·笔记·python
hsx6662 小时前
Android 基础筑基(一)
java
hy.z_7772 小时前
【数据结构】反射、枚举 和 lambda表达式
android·java·数据结构
從南走到北2 小时前
JAVA青企码协会模式系统源码支持微信公众号+微信小程序+H5+APP
java·微信·微信小程序·小程序·uni-app·微信公众平台
草履虫建模2 小时前
Ajax原理、用法与经典代码实例
java·前端·javascript·ajax·intellij-idea
强哥叨逼叨3 小时前
别被假象迷惑!揭秘 Java 线程池中“线程空着但任务卡着”的真相
java
_extraordinary_3 小时前
Java 栈和队列
java·开发语言