SpringBoot日志处理

1、简介

SpringBoot作为日常开发利器,开箱即用,大量的star等已经成为节省开发的重要框架之一,但是各个框架的star中引入的日志框架却不尽相同,有的是log4j,有的是slf4j,这导致我们在引入多个框架的star的时候,往往会引入多个日志框架,每一个日志框架彼此效率不尽相同,那么我们能不能做到在项目中仅引入一个统一的日志框架呢?本篇我们就来探索SpringBoot如何实现统一日志操作。

为什么需要日志

首先我们需要明白,日志的作用是什么--即用来在程序运行过程中,将我们需要的信息打印出来,便于我们在调试中查找和观察。在JAVA中存在很多常见的日志框架,如JUL、JCL、Jboss-logging、log4j、logback、slf4j等,这么多日志框架,我们该如何选择?
日志文件的命名格式

推荐的日志文件命名方式为appName_logType_logName.log 其中 logType为日志类型,推荐分类有 stats monitor visit等, logName 为日志描述。这种命名的好处是通过文件名就可以知道曰志文件属于什么应用,什么类型 ,什么目的,也有利于归类查找。例如, mppserver 应用中单独监控时区转换异常的日志文件名定义为mppserver__monitor_timeZoneConvert.log

日志门面与日志实现

在日志框架选型之前,我们先了解一个概念,什么是日志门面日志门面,不是具体的日志解决方案,它只服务于各种各样的日志系统,允许最终用户在部署其应用时使用其所希望的日志实现来使用日志功能。而日志实现则是基于对应的日志门面的规范来实现的具体日志功能的框架,常见的日志门面与日志实现关系如下:


每一种日志框架输出信息的效率也不尽相同,而我们日常开发使用的框架中往往都会引入一个日志框架来辅助输出框架信息,然而框架之间由于历史迭代原因及框架性能等问题,选择的日志框架也不一样,常见的框架与默认选择的日志系统关系如下:


由于历史迭代原因,JCL和jboss-logging日志框架,基本已经很久没有更新了,不太适合作为现在框架的主流选择,那么剩下的选择中log4j、slf4j是使用最多的,然而由于log4j的输出性能问题,log4j的作者选择重新编写了一个日志门面--Slf4j,并且编写了基于Slf4j的日志实现--logback,其输出信息的效率远超log4j,解决了log4j遗留下的性能问题,所以在SpringBoot框架中,默认也选择了Slf4j来作为默认日志框架。


SpringBoot 默认选择的是 SLF4J + Logback 的组合,如果不需要更改为其他日志系统(如 Log4j2 等),则无需多余的配置,LogBack 默认会将日志打印到控制台上。

    由于新建的 Spring Boot 项目一般都会引用 spring-boot-starter 或者 spring-boot-starter-web,而这两个起步依赖中都已经包含了对于 spring-boot-starter-logging 的依赖,所以,我们无需额外添加依赖。

2、基本用法

(1)这里我们打印出 5 种不同级别的日志,可以看到控制台会输出相关日志信息:

@RestController
public class HelloController {
    Logger logger = LoggerFactory.getLogger(getClass());
    @RequestMapping("/test")
    public void test(){
        logger.trace("Trace 日志...");
        logger.debug("Debug 日志...");
        logger.info("Info 日志...");
        logger.warn("Warn 日志...");
        logger.error("Error 日志...");
    }
}

得到的结果如下:


日志级别从小到大为 trace < debug < info < warn < error < fatal,由于默认日志级别设置为 INFO,因此上面样例 trace debug 级别的日志都看不到。
(2)如果项目有使用 Lombok 的话,直接使用 @Slf4j 注解可以省去从日志工厂生成日志对象这一步,直接进行日志记录。下面代码的效果同上面是一样的:

@Controller
@Slf4j
public class LoggerController {
    @RequestMapping("/testLog")
    @ResponseBody
    public String testLogger(){
        log.trace("Trace 日志...");
        log.debug("Debug 日志...");
        log.info("Info 日志...");
        log.warn("Warn 日志...");
        log.error("Error 日志...");
        return "log";
    }
}


可以看到,这里也是只输出了 Info Warn和error级别的日志信息。

3、日志级别

(1)日志级别从小到大为 trace < debug < info < warn < error < fatal,由于默认日志级别设置为 INFO,因此上面样例 trace debug 级别的日志都看不到。

(2)我们可以在 applicaition.properties 文件中修改日志级别。比如下面将全局日志级别都改成 trace,因此系统所有的日志都能看到:

logging.level.root=trace


(3)我们也可以只设置某个包的日志级别,这样能够更方便准确地定位问题。比如下面配置,只对所有 com.example 包下面产生的日志级别改成 trace

4、生成日志文件

 如果无需复杂的日志配置,执行简单设置日志打印级别,打印方式可直接再 application.yml 中配置。  默认情况下 Spring Boot 将 info 级别的日志输出到控制台中,不会写到日志文件,且不能进行复杂配置

#日志操作
#设置全局(root)的日志输出级别为trace,这样所有级别的日志均可以看到,也可以指定对应的包的日志输出级别
#logging.level.root=trace
#设置名称这里可以包含路径,如果不写路径则默认在当前项目下
logging.file=/Users/mac/Documents/out.log
#指定日志输出的路径,如果path和file同时配置的话,则path会失效
#这里会在对应的路径下输出一个spring.log的日志文件
#logging.path=/Users/mac/Documents

这里是演示只定义logging.file得到的日志文件如下:


注意:file 和 path 是不能同时配置的,如果同时配置path不会生效。其原因是,没有logback-spring.xml配置文件,系统只认识logging.file,不认识logging.path。所以要配置logback-spring.xml,spring boot会默认加载此文件,为什么不配置logback.xml,因为logback.xml会先application.properties加载,而logback-spring.xml会后于application.properties加载,这样我们在application.properties文中设置日志文件名称和文件路径才能生效。

以下配置是设置控制台和日志文件的日志形式。

#设置控制台输出日志的形式
logging.pattern.console=%clr(%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n)
#设置日志文件的格式
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} >>> [%thread] >>> %-5level >>> %logger{50} >>> %msg%n

设置cn.henu包下的日志输出级别为debug

logging.level.cn.henu=debug

5、默认配置文件

总结:SpringBoot中默认日志启动器为 spring-boot-starter-logging ,默认采用的是 logback 日志框架
spring-boot-2.3.1.RELEASE.jar! \org\springframework\boot\logging\logback\base.xml 做了日志的默认配置


<included> 
	<!--日志格式默认规定--> 
	<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" /> 		<!--日志级别默认为: info -->
    <root level="INFO"> 
        <appender-ref ref="CONSOLE" /> 
        <appender-ref ref="FILE" /> 
    </root> 
</included>

日志文件采用的是滚动日志追加


在下面类中会读取上面xml中配置的信息


如果spring boot的日志功能无法满足我们的需求(比如异步日志记录等),我们可以自已定义的日志配置文件

6、自定义配置文件

接下来说明如何通过独立的 xml 配置文件来配置日志打印。虽然 springboot 是要消灭 xml 的,但是有些复杂功能还是得编写 xml。使用 xml 后要将 application.yml 中的配置去掉,避免冲突.

根据不同的日志系统,按照指定的规则组织配置文件名,并放在 resources 目录下,就能自动被 spring boot 加载:

  • Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
  • Log4j:      log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
  • Log4j2:    log4j2-spring.xml, log4j2.xml
  • JDK (Java Util Logging): logging.properties

想要自定义文件名的可配置:logging.config指定配置文件名:


logging.config=classpath:logging-config.xml

Spring Boot 官方推荐优先使用带有 -spring 的文件名作为你的日志配置(如使用 logback-spring.xml ,而不是 logback.xml ),命名为 logback-spring.xml 的日志配置文件, spring boot 可以为它添加一些 spring boot 特有的配置项(下面会提到)。

配置文件的组成

  根节点<configuration>有 5 个子节点,下面来进行一一介绍。

1: <root>节点

  root 节点是必选节点,用来指定最基础的日志输出级别,只有一个 level 属性,用于设置打印级别,可选如下:TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF

  root 节点可以包含 0 个或多个元素,将appender添加进来。如下:

<root level="debug">
 <appender-ref ref="console" />
 <appender-ref ref="file" />
</root>

appender 也是子节点之一,将会在后面说明。

2: <contextName>节点

  设置上下文名称,默认为default,可通过%contextName来打印上下文名称,一般不使用此属性。

3: <property>节点

  用于定义变量,方便使用。有两个属性:name,value。定义变量后,可以使用${}来使用变量。如下:

<property name="path" value="./log"/>
<property name="appname" value="app"/>
4: <appender>节点

  appender 用来格式化日志输出的节点,这个最重要。有两个属性:

  • name:该本 appender 命名
  • class:指定输出策略,通常有两种:控制台输出,文件输出

下面通过例子来说明这个怎么用:

  1. 输出到控制台/按时间输出日志
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--设置存储路径变量-->
    <property name="LOG_HOME" value="./log"/>
    <!--控制台输出appender-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--设置输出格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <!--设置编码-->
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!--文件输出,时间窗口滚动-->
    <appender name="timeFileOutput" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日志名,指定最新的文件名,其他文件名使用FileNamePattern -->
        <File>${LOG_HOME}/timeFile/out.log</File>
        <!--文件滚动模式-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名,可设置文件类型为gz,开启文件压缩-->
            <FileNamePattern>${LOG_HOME}/timeFile/info.%d{yyyy-MM-dd}.%i.log.gz</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
            <!--按大小分割同一天的-->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!--输出格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <!--设置编码-->
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!--指定基础的日志输出级别-->
    <root level="INFO">
        <!--appender将会添加到这个loger-->
        <appender-ref ref="console"/>
        <appender-ref ref="timeFileOutput"/>
    </root>
</configuration>
  1. 设置只输出单个级别

  在 appender 中设置,filter 子节点,在默认级别上再此过滤,配置 onMatch,onMismatch 可实现只输出单个级别

<appender ...>
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>INFO</level>
        <!--接受匹配-->
        <onMatch>ACCEPT</onMatch>
        <!--拒绝不匹配的-->
        <onMismatch>DENY</onMismatch>
    </filter>
</appender>
5: <logger>节点
构成

  此节点用来设置一个包或具体的某一个类的日志打印级别、以及指定<appender>,有以下三个属性:

  • name: 必须。用来指定受此 loger 约束的某个包或者某个具体的类
  • level:可选。设置打印级别。默认为 root 的级别。
  • addtivity: 可选。是否向上级 loger(也就是 root 节点)传递打印信息。默认为 true。

使用示例如下:

  1. 不指定级别,不指定 appender
<!-- 控制com.example.service下类的打印,使用root的level和appender -->
<logger name="com.example.service"/>

2.指定级别,不指定 appender

<!-- 控制com.example.service下类的打印,使用root的appender打印warn级别日志 -->
<logger name="com.example.service" level="WARN"/>

3.指定级别,指定 appender

<!-- 控制com.example.service下类的打印,使用console打印warn级别日志 -->
<!-- 设置addtivity是因为这里已经指定了appender,如果再向上传递就会被root下的appender再次打印 -->
<logger name="com.example.service" level="WARN" addtivity="false">
    <appender-ref ref="console">
</logger>

通过指定 appender 就能将指定的包下的日志打印到指定的文件中。

多环境日志输出

  通过设置文件名为-spring 结尾,可分环境配置 logger,示例如下:

<configuration>
    <!-- 测试环境+开发环境. 多个使用逗号隔开. -->
    <springProfile name="test,dev">
        <logger name="com.example.demo.controller" level="DEBUG" additivity="false">
            <appender-ref ref="console"/>
        </logger>
    </springProfile>
    <!-- 生产环境. -->
    <springProfile name="prod">
        <logger name="com.example.demo" level="INFO" additivity="false">
            <appender-ref ref="timeFileOutput"/>
        </logger>
    </springProfile>
</configuration>

通过配置spring.profiles.active也能做到切换上面的 logger 打印设置

7、使用其他日志

例如:这里使用的是log4j2

首先需要在web中去掉默认的日志配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions><!-- 去掉springboot默认配置 -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency> <!-- 引入log4j2依赖 -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

日志配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
    <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
    <!--变量配置-->
    <Properties>
        <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
        <!-- %logger{36} 表示 Logger 名字最长36个字符 -->
        <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
        <!-- 定义日志存储的路径 -->
        <property name="FILE_PATH" value="./logs/log4j2" />
        <property name="FILE_NAME" value="lzy-log4j2-demo" />
    </Properties>
    <appenders>
        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
        <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </File>
        <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>
        <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>
        <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>
    </appenders>
    <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
    <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>
        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
        <logger name="org.mybatis" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </logger>
        <!--监控系统信息-->
        <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
        <Logger name="org.springframework" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="Filelog"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
        </root>
    </loggers>
</configuration>

以上就是全部内容,如有改变还会继续更新。

全部评论