SpringBoot日志管理详解

Lou.Chen2023年1月10日
大约 13 分钟

Java日志概述

总体概述

image-20230110102359685

在Java中,Java中的日志框架分为两大类,日志门面日志实现

日志门面

日志门面定义了一组日志的接口规范,它并不提供底层具体的实现逻辑。

Apache Commons LoggingSlf4j 就属于这一类。

  • Apache Commons Logging

JCL(Commons-Logging),Apache开发的日志门面,在2014年就已经停止更新了。

  • Slf4j

是 Log4j和Logback 的作者 Ceki Gülcü。目前最广泛时候的Slf4j作为日志门面

日志实现

日志实现则是日志具体的实现,包括日志级别控制、日志打印格式、日志输出形式(输出到数据库、输出到文件、输出到控制台等)。Log4jLog4j2Logback 以及 Java Util Logging 则属于这一类。

将日志门面和日志实现分离其实是一种典型的门面模式,这种方式可以让具体业务在不同的日志实现框架之间自由切换,而不需要改动任何代码,开发者只需要掌握日志门面的 API 即可。

日志门面是不能单独使用的,它必须和一种具体的日志实现框架相结合使用。

日志级别

使用日志级别的好处在于,调整级别,就可以屏蔽掉很多调试相关的日志输出。不同的日志实现定义的日志级别不太一样,不过也都大同小异。

从高到低依次如下:

  • OFF 是最高等级的,用于关闭所有日志记录。

  • FATAL 致命的错误,指出每个严重的错误事件将会导致应用程序的退出。

  • ERROR 指出虽然发生错误事件,但仍然不影响系统的继续运行。

  • WARN 警告,表明会出现潜在错误的情形。

  • INFO 信息表明消息在粗粒度级别上突出强调应用程序的运行过程。

  • DEBUG 调试指出细粒度信息事件对调试应用程序是非常有帮助的。

  • TRACE 与DEBUG 相比更细致化的记录事件消息。

  • ALL 用于打开所有日志记录。

Java Util Logging

Java Util Logging 定义了 7 个日志级别,从严重到普通依次是:

  • SEVERE
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST

因为默认级别是 INFO,因此 INFO 级别以下的日志,不会被打印出来。

Log4j

Log4j 定义了 8 个日志级别(除去 OFF 和 ALL,可以说分为 6 个级别),从严重到普通依次是:

  • OFF:最高等级的,用于关闭所有日志记录。
  • FATAL:重大错误,这种级别可以直接停止程序了。
  • ERROR:打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。
  • WARN:警告提示。
  • INFO:用于生产环境中输出程序运行的一些重要信息,不能滥用。
  • DEBUG:用于开发过程中打印一些运行信息。
  • TRACE
  • ALL 最低等级的,用于打开所有日志记录。

Logback

Logback 日志级别比较简单,从严重到普通依次是:

  • ERROR
  • WARN
  • INFO
  • DEBUG
  • TRACE

日志实现综合对比

  • Java Util Logging

Java Util Logging 系统在 JVM 启动时读取配置文件并完成初始化,一旦应用程序开始运行,就无法修改配置。另外,这种日志实现配置也不太方便,只能在 JVM 启动时传递参数,像下面这

样:-Djava.util.logging.config.file=<config-file-name> 由于这些局限性,导致 Java Util Logging 并未广泛使用。

  • Log4j

虽然配置繁琐,但是一旦配置完成,使用起来就非常方便,只需要将相关的配置文件放到 classpath 下即可。在很多情况下,Log4j 的配置文件我们可以在不同的项目中反复使用。

Log4j 可以和 Apache Commons Logging 搭配使用,Apache Commons Logging 会自动搜索并使用 Log4j,如果没有找到 Log4j,再使用 Java Util Logging

作者是Slf4j的作者Ceki Gülcü,但是目前Log4j已经停止更新

  • Logback

LogbackSlf4j 的原生实现框架,它也出自 Log4j 作者(Ceki Gülcü)之手,但是相比 Log4j,它拥有更多的优点、特性以及更强的性能。

  • log4j2

Apache开发的产品,性能强悍,log4j2是重新架构的一款日志组件,他抛弃了之前log4j的不足,以及吸取了优秀的logback的设计重新推出的一款新组件。log4j2的社区活跃很频繁而且更新的也很快。

也是目前使用最广泛的日志实现框架

最佳实践方案

  • 如果不想添加任何依赖,使用 Java Util Logging 或框架容器已经提供的日志接口。
  • 如果比较在意性能,推荐:Slf4j + Logback
  • 如果项目中已经使用了 Log4j 且没有发现性能问题,推荐组合为:Slf4j + Log4j2

SpringBoot日志实现

默认日志实现

Spring Boot 使用 Apache Commons Logging 作为内部的日志框架门面,它只是一个日志接口,在实际应用中需要为该接口来指定相应的日志实现。

Spring Boot 默认的日志实现是 Logback

image-20230110112203329

在 Spring Boot 项目中,只要添加了如下 web 依赖,日志依赖就自动添加进来了:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

SpringBoot日志配置

Spring Boot 的日志系统会自动根据 classpath 下的内容选择合适的日志配置,在这个过程中首选 Logback。

修改日志级别

默认的日志级别为INFO,若想要修改指定包下的所有的日志输出级别为DEBUG

  • logging.level.包名=日志级别
logging.level.org.lc.elk=debug
  • 示例:下面使用的是Slf4j作为日志门面,默认logback作为日志实现
  • 配置日志级别为DEBUG,那么就会打印DEBUG及以上的所有日志级别信息,否则默认只能打印INFO级别及以上的日志级别信息
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ElkApplication {
    private static final Logger logger = LoggerFactory.getLogger(ElkApplication.class);
    public static void main(String[] args) {
        SpringApplication.run(ElkApplication.class, args);
        logger.warn("test warn");
        logger.info("test info...");
        logger.debug("test debug...");
    }
}

指定日志文件名/路径

  • 指定日志文件的名称louchen.log ,则默认在项目的根目录下生成louchen.log的文件
logging.file.name=louchen.log
  • 指定日志文件生成路径以及文件名(常用)
logging.file.name=/Users/louchen/workStudy/noteInterview/SpringBoot_Cloud/ElasticSearch/springboot_elk/log/louchen.log
  • 指定日志文件目录,默认在该目录下生成spring.log的日志文件,不能同时指定logging.file.name,否则以logging.file.name为准,logging.file.path将会失效
logging.file.path=/Users/louchen/workStudy/noteInterview/SpringBoot_Cloud/ElasticSearch/springboot_elk/log

其它日志配置

如果想对输出到文件中的日志进行精细化管理,还有如下一些属性可以配置:

  • 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:日志文件保存的天数。

例如:

logging.file.name=/Users/louchen/workStudy/noteInterview/SpringBoot_Cloud/ElasticSearch/springboot_elk/log/louchen.log
logging.logback.rollingpolicy.max-file-size=1MB

创建日志:

@SpringBootApplication
public class ElkApplication {
    private static final Logger logger = LoggerFactory.getLogger(ElkApplication.class);
    public static void main(String[] args) {
        SpringApplication.run(ElkApplication.class, args);
        logger.debug("test debug...");
        logger.info("test info...");
        for (int i = 0; i < 100000; i++) {
            logger.info("{}",i);
        }
    }
}

日志生成结果:

image-20230110140044285

日志分组

application.properties 中还可以配置日志分组。

日志分组能够把相关的 logger 放到一个组统一管理。

例如我们可以定义一个 tomcat 组:

logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat

然后统一管理 tomcat 组中的所有 logger:

logging.level.tomcat=TRACE

Spring Boot 中还预定义了两个日志分组 web 和 sql,如下:

image-20230110140512312

不过在 application.properties 中只能实现对日志一些非常简单的配置,如果想实现更加细粒度的日志配置,那就需要使用日志实现的原生配置,例如:

Logbackclasspath:logback.xml

Log4jclasspath:log4j.xml

Log4j2classpath:log4j2.xml

如果这些日志配置文件存在于 classpath 下,那么默认情况下,Spring Boot 就会自动加载这些配置文件。

Logback配置

基本配置

默认的 Logback 配置文件名有两种:两个文件使用的内置语法一致

  • 加载顺序不一致:logback.xml -> application.properties -> logback-spring.xml

  • logback.xml

    • 这种配置文件会直接被日志框架加载。
    • 所有Spring中的一些高级功能,在这个配置文件中可能无法使用。
    • logback.xml存放的位置是在你启动的那个类所在的项目的resources目录
  • logback-spring.xml

    • 这种配置文件不会被日志框架直接加载,而是由 Spring Boot 去解析日志配置,可以使用 Spring Boot 的高级 Profile 功能。

    • 只有在Spring应用程序运行的时候才生效,即带有@SpringBootApplication注解的类启动的时候才会生效。

    • 如果不是Spring应用程序,而是一个main方法或者一个JUnit的测试方法,要用 logback.xml 来配置。

    • logback-spring.xml存放的位置是在SpringApplication主类所在的项目的resources目录

Spring Boot 中为 Logback 提供了四个默认的配置文件,位置在 org/springframework/boot/logging/logback/,分别是:

  • defaults.xml:提供了公共的日志配置,日志输出规则等。
  • console-appender.xml:使用 CONSOLE_LOG_PATTERN 添加一个ConsoleAppender。
  • file-appender.xml:添加一个 RollingFileAppender。
  • base.xml:为了兼容旧版 Spring Boot 而提供的。

例如:自定义logback.xml

  • 如果需要自定义 logback.xml 文件,可以通过 include 引入 Spring Boot 已经提供的配置文件,也可以不使用,可以自定义。

  • 如果application.proerpties中也配置了相同的规则,那么以application.proerpties配置文件为准

  • 一个典型的 logback.xml 文件如下(resources/logback.xml)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
    <logger name="org.springframework.web" level="DEBUG"/>
    <!--指定 org.lc.elk 包下的日志级别为 DEBUG -->
    <logger name="org.lc.elk" level="DEBUG"/>
</configuration>

例如:自定义 logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
    <!--应用名称-->
    <property name="APP_NAME" value="logstash"/>
    <!--日志文件保存路径-->
    <property name="LOG_FILE_PATH" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/logs}"/>
    <contextName>${APP_NAME}</contextName>
    <!--每天记录日志到文件appender-->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

Log4j2配置

如果 classpath 下存在 Log4j2 的依赖,Spring Boot 会自动进行配置。

默认情况下 classpath 下当然不存在 Log4j2 的依赖,如果想使用 Log4j2,可以排除已有的 Logback,然后再引入 Log4j2,如下:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <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 的配置就比较容易了,在 reources 目录下新建 log4j2.xml 文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="warn">
    <properties>
        <Property name="app_name">logging</Property>
        <Property name="log_path">logs/${app_name}</Property>
    </properties>
    <appenders>
        <console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="[%d][%t][%p][%l] %m%n" />
        </console>
        <RollingFile name="RollingFileInfo" fileName="${log_path}/info.log"
                     filePattern="${log_path}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="INFO" />
                <ThresholdFilter level="WARN" onMatch="DENY"
                                 onMismatch="NEUTRAL" />
            </Filters>
            <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
                <SizeBasedTriggeringPolicy size="2 MB" />
            </Policies>
            <DefaultRolloverStrategy compressionLevel="0" max="10"/>
        </RollingFile>
        <RollingFile name="RollingFileWarn" fileName="${log_path}/warn.log"
                     filePattern="${log_path}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="WARN" />
                <ThresholdFilter level="ERROR" onMatch="DENY"
                                 onMismatch="NEUTRAL" />
            </Filters>
            <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
                <SizeBasedTriggeringPolicy size="2 MB" />
            </Policies>
            <DefaultRolloverStrategy compressionLevel="0" max="10"/>
        </RollingFile>

        <RollingFile name="RollingFileError" fileName="${log_path}/error.log"
                     filePattern="${log_path}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
            <ThresholdFilter level="ERROR" />
            <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
                <SizeBasedTriggeringPolicy size="2 MB" />
            </Policies>
            <DefaultRolloverStrategy compressionLevel="0" max="10"/>
        </RollingFile>
    </appenders>
    <loggers>
        <root level="info">
            <appender-ref ref="Console" />
            <appender-ref ref="RollingFileInfo" />
            <appender-ref ref="RollingFileWarn" />
            <appender-ref ref="RollingFileError" />
        </root>
    </loggers>
</configuration>

首先在 properties 节点中指定了应用名称以及日志文件位置。

然后通过几个不同的 RollingFile 对不同级别的日志分别处理,不同级别的日志将输出到不同的文件,并按照各自的命名方式进行压缩。

配置解释

<?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="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" />
        <!-- 定义日志存储的路径,不要配置相对路径 -->
        <property name="FILE_PATH" value="E:\logs\log4j2" />
        <property name="FILE_NAME" value="springbootlog4j2" />
    </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>