跳转至

Spring Boot 日志

前置知识:本文假设你已经了解「SLF4J」日志门面的基本用法和「Logback」日志框架的核心概念(Logger / Appender / Layout)。Spring Boot 的日志体系正是基于这套组合构建的。

本文你会学到

  • Spring Boot 默认的日志架构——为什么引入 spring-boot-starter 就自动拥有日志能力
  • application.yml 中各项日志配置的含义——级别、格式、文件输出、归档策略、日志组
  • logback-spring.xml 扩展配置——按环境区分日志行为、读取 Spring 配置属性
  • 如何将默认的 Logback 切换为 Log4j2——排除依赖、引入新依赖、添加配置文件
  • 实际项目中最容易踩的日志陷阱——依赖冲突、桥接器循环、配置文件加载时机

🏗️ Spring Boot 的默认日志

当你第一次在 Spring Boot 项目中写下 LoggerFactory.getLogger() 并调用 logger.info() 时,控制台就已经有格式化日志输出了——你甚至没有添加任何日志依赖。这是因为 spring-boot-starter 自动帮你搞定了整个日志体系。

默认架构

Spring Boot 内部预装了一套完整的日志方案:

graph LR
    App["你的业务代码"] -->|"调用"| SLF4J["SLF4J API<br/>(日志门面)"]
    SLF4J -->|"原生实现"| Logback["Logback<br/>(日志实现)"]

    Bridge1["JUL → SLF4J<br/>(jul-to-slf4j)"]
    Bridge2["JCL → SLF4J<br/>(jcl-over-slf4j)"]
    Bridge3["Log4j → SLF4J<br/>(log4j-over-slf4j)"]

    Bridge1 --> SLF4J
    Bridge2 --> SLF4J
    Bridge3 --> SLF4J

    classDef app fill:transparent,stroke:#539bf5,color:#adbac7,stroke-width:2px
    classDef facade fill:transparent,stroke:#f57c00,color:#adbac7,stroke-width:2px
    classDef impl fill:transparent,stroke:#388e3c,color:#adbac7,stroke-width:2px
    classDef bridge fill:transparent,stroke:#7b1fa2,color:#adbac7,stroke-width:1px
    class App app
    class SLF4J facade
    class Logback impl
    class Bridge1,Bridge2,Bridge3 bridge

这套架构包含三个层次:

层次 组件 说明
门面 SLF4J APIslf4j-api 业务代码只依赖这个接口
实现 Logbacklogback-classic 原生实现 SLF4J,无需适配器
桥接器 jul-to-slf4j / jcl-over-slf4j / log4j-over-slf4j 将旧 API 的日志调用重定向到 SLF4J

💡 桥接器的意义:即使你的项目依赖了使用 JUL 或 Log4j 1.x API 的第三方库,它们的日志也会被自动路由到 SLF4J → Logback 管道,实现统一输出。这一切由 spring-boot-starter 自动引入,你无需手动配置。

默认日志格式

不做任何配置时,Spring Boot 的控制台日志长这样:

默认日志输出格式
2026-04-12T14:30:00.123+08:00  INFO 12345 --- [main] c.l.s.logging.SpringBootLoggingTest : 这是一条 INFO 日志

格式拆解:

部分 含义 示例
2026-04-12T14:30:00.123+08:00 日期时间(带时区) 精确到毫秒
INFO 日志级别 TRACE / DEBUG / INFO / WARN / ERROR
12345 进程 ID(PID) 操作系统分配的进程号
--- 分隔符 固定标记
[main] 线程名 mainhttp-nio-8080-exec-1
c.l.s.logging.SpringBootLoggingTest Logger 名称(缩写) 36 字符内缩写显示
这是一条 INFO 日志 日志消息 你传入的内容

📝 application.yml 配置

Spring Boot 提供了以 logging. 为前缀的配置项,让你在 application.yml 中就能完成大部分日志配置,无需编写 XML。

日志级别

当你需要控制哪些日志被输出时,通过 logging.level 来设置。最常见的场景是:开发时想看到自己代码的 DEBUG 日志,但不想被第三方框架的调试信息淹没:

application.yml — 日志级别配置
1
2
3
4
5
6
logging:
  level:
    root: INFO                          # 全局默认级别
    com.luguosong: DEBUG                # 你的业务包使用 DEBUG 级别
    org.springframework: WARN           # Spring 框架只记录 WARN 及以上
    org.hibernate.SQL: DEBUG            # 只对 Hibernate SQL 开启 DEBUG

级别继承规则:

  • 未显式配置的 Logger 继承最近的已配置祖先,最终回退到 root
  • root 是所有 Logger 的顶级祖先,默认级别为 INFO
  • 可以精确到类名:com.luguosong.service.UserService: TRACE

日志格式

当你觉得默认日志格式不满足需求(比如想加入线程名、缩短时间格式),可以通过 logging.pattern 自定义:

application.yml — 日志格式配置
1
2
3
4
logging:
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{40} - %msg%n"

常用占位符速查:

占位符 说明 示例输出
%d{pattern} 日期时间 2026-04-12 14:30:00.123
%thread 线程名 main
%-5level 日志级别(左对齐,5 字符宽) INFOERROR
%logger{36} Logger 名(最长 36 字符,自动缩写) c.l.s.logging.Test
%msg 日志消息 用户登录成功
%n 换行符
%clr{...}{颜色} Spring Boot 颜色扩展(仅控制台) %clr{%d}{cyan}

颜色仅限控制台

file 格式中不要使用 %clr 颜色标记,文件不支持 ANSI 颜色码。

文件输出

当你需要将日志持久化到文件时,Spring Boot 提供了两种配置方式:

application.yml — 文件输出配置
1
2
3
4
logging:
  file:
    name: logs/spring-app.log    # 指定文件名(含路径)
    # path: logs/                # 或者只指定目录,文件名默认为 spring.log
配置项 说明 优先级
logging.file.name 指定完整文件路径 高(同时设置时以 name 为准)
logging.file.path 只指定目录,文件名默认为 spring.log

两者只设其一即可。如果同时设置,name 生效,path 被忽略。

日志归档

当日志文件不断增长时,归档策略可以防止磁盘被撑满。Spring Boot 内置了基于 Logback 的归档配置:

application.yml — 日志归档配置
1
2
3
4
5
6
7
logging:
  logback:
    rollingpolicy:
      max-file-size: 10MB        # 单个文件最大大小,超过后归档
      max-history: 30             # 保留 30 天的归档文件
      total-size-cap: 1GB         # 所有归档文件总大小上限
      clean-history-on-start: true # 应用启动时清理过期归档

各配置项的含义:

配置项 说明 默认值
max-file-size 单个日志文件的最大大小 10MB
max-history 保留的归档文件天数 7
total-size-cap 所有归档文件总大小上限 0(无限制)
clean-history-on-start 启动时是否清理过期归档 false

📌 归档只在配置了文件输出(logging.file.namelogging.file.path)时才会生效。没有文件输出,归档配置自然无用武之地。

日志组

当你的项目中有多个包需要统一设置级别时,可以用日志组把相关包打包管理:

application.yml — 日志组配置
1
2
3
4
5
6
logging:
  group:
    demo: "com.luguosong"                    # 自定义组名,绑定包路径
    web: "com.luguosong.web,com.luguosong.api"  # 多个包用逗号分隔
  level:
    demo: DEBUG                               # 对整个组设置级别

Spring Boot 预定义了几个常用日志组:

预定义组 包含的包 用途
web org.springframework.core.codecorg.springframework.httporg.springframework.web Spring Web 框架
sql org.springframework.jdbc.coreorg.hibernate.SQL SQL 语句记录
使用预定义日志组
1
2
3
logging:
  level:
    sql: DEBUG    # 开启 SQL 日志,等价于分别设置 org.springframework.jdbc.core 和 org.hibernate.SQL

🎨 Logback 扩展配置

application.yml 中的配置无法满足需求时(比如需要按环境区分 Appender、使用过滤器、配置异步日志),就需要编写 Logback 原生配置文件了。Spring Boot 提供了 logback-spring.xml 扩展,在标准 Logback 配置的基础上增加了 Spring 特有功能。

logback-spring.xml vs logback.xml

对比维度 logback.xml logback-spring.xml
加载者 Logback 框架自身 Spring Boot
加载时机 Spring Boot 应用启动之前 Spring Boot 初始化之后
Spring Profile 支持 不支持(无法使用 <springProfile> 支持
Spring 属性读取 不支持(无法使用 <springProperty> 支持
推荐程度 不推荐在 Spring Boot 项目中使用 推荐

命名不要搞错

如果你在 Spring Boot 项目中使用了 logback.xml(而不是 logback-spring.xml),Spring Boot 的日志配置(如 logging.level.*logging.pattern.*)将不会生效,因为 Logback 框架先于 Spring Boot 加载了 logback.xml

按环境区分配置:springProfile

当你需要开发环境和生产环境使用不同的日志策略时,<springProfile> 标签可以按 Spring Profile 有条件地加载配置:

logback-spring.xml — springProfile 示例
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <springProfile name="dev | default">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

    <springProfile name="prod">
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>logs/spring-app.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>logs/spring-app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                <maxFileSize>10MB</maxFileSize>
                <maxHistory>30</maxHistory>
            </rollingPolicy>
            <encoder>
                <pattern>${FILE_LOG_PATTERN}</pattern>
            </encoder>
        </appender>
        <root level="WARN">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>
</configuration>

name 属性支持多种写法:

写法 含义
name="dev" 精确匹配 dev profile
name="dev | default" 匹配 devdefault| 表示或)
name="!prod" 排除 prod,匹配其他所有 profile
name="dev & cloud" 同时激活 devcloud 两个 profile 时匹配

上面的配置效果:

  • dev 或默认 profile:只输出到控制台,root 级别 INFO
  • prod profile:同时输出到控制台和文件,root 级别 WARN,日志文件自动按日期和大小归档

读取 Spring 配置属性:springProperty

当你需要在 Logback 配置中引用 application.yml 里定义的属性时,<springProperty> 提供了桥梁:

logback-spring.xml — springProperty 示例
<!-- 从 Spring 配置中读取属性,供 Logback 使用 -->
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="my-app"/>
<springProperty scope="context" name="LOG_PATH" source="logging.file.path" defaultValue="logs"/>

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_PATH}/${APP_NAME}.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <fileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
        <maxFileSize>10MB</maxFileSize>
        <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder>
        <pattern>${FILE_LOG_PATTERN}</pattern>
    </encoder>
</appender>
属性 说明
scope 作用域,通常为 context(整个 Logback 上下文可见)
name 在 Logback 中引用时使用的变量名
source application.yml 中的属性路径
defaultValue Spring 配置中未找到时的默认值

🔄 切换为 Log4j2

虽然 Logback 是 Spring Boot 的默认选择,但在某些场景下你可能想切换到 Log4j2(比如需要 Log4j2 的异步日志性能优势、或者团队更熟悉 Log4j2 的配置方式)。切换分三步:排除默认 Logback、引入 Log4j2 starter、添加配置文件。

排除默认 Logback 并引入 Log4j2

spring-boot-starter 内部通过 spring-boot-starter-logging 引入了 Logback。切换的第一步是在依赖中排除它,然后引入 spring-boot-starter-log4j2

pom.xml — 切换到 Log4j2
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <!-- 排除默认的 Logback 日志实现 -->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- 引入 Log4j2 日志实现 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
</dependencies>

排除 spring-boot-starter-logging 会移除以下依赖:

  • logback-classic(Logback 实现)
  • logback-core(Logback 核心)
  • jul-to-slf4j / log4j-over-slf4j / jcl-over-slf4j(桥接器)

spring-boot-starter-log4j2 会重新引入 slf4j-api 和 Log4j2 的 SLF4J 桥接实现,业务代码中的 org.slf4j.Logger 无需任何修改。

添加 Log4j2 配置文件

src/main/resources/ 下添加 log4j2.xmllog4j2-spring.xml

log4j2-spring.xml — 基本配置示例
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Console"/>
        </Root>
        <Logger name="com.luguosong" level="DEBUG"/>
    </Loggers>
</Configuration>

切换完成后,所有 application.yml 中的 logging.* 配置依然有效——Spring Boot 会自动适配 Log4j2。

⚠️ 常见陷阱

在实际使用 Spring Boot 日志时,有几个容易踩的坑:

多次引入 SLF4J 实现导致冲突

当 classpath 中存在多个 SLF4J 实现时(比如同时引入了 logback-classiclog4j-slf4j-impl),启动时会看到这样的警告:

多实现冲突警告
1
2
3
4
SLF4J: Class path contains multiple SLF4J providers.
SLF4J: Found provider [ch.qos.logback.classic.spi.LogbackServiceProvider]
SLF4J: Found provider [org.apache.logging.slf4j.SLF4JServiceProvider]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

根因是某个第三方依赖传递引入了另一个 SLF4J 实现。解决方法是用 mvn dependency:tree 找到冲突来源,然后排除:

排除传递依赖中的 SLF4J 实现
<dependency>
    <groupId>第三方库</groupId>
    <artifactId>xxx</artifactId>
    <exclusions>
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </exclusion>
    </exclusions>
</dependency>

桥接器循环依赖

这是一个更隐蔽的问题:log4j-over-slf4j(把 Log4j API 的调用桥接到 SLF4J)和 slf4j-over-log4j(把 SLF4J 的调用桥接到 Log4j)如果同时存在,就会形成死循环:

graph LR
    SLF4J["SLF4J"] -->|"slf4j-over-log4j"| Log4j["Log4j"]
    Log4j -->|"log4j-over-slf4j"| SLF4J

    classDef node1 fill:transparent,stroke:#d32f2f,color:#adbac7,stroke-width:2px
    classDef node2 fill:transparent,stroke:#f57c00,color:#adbac7,stroke-width:2px
    class SLF4J node1
    class Log4j node2

这两个桥接器的作用完全相反,同时存在会导致 StackOverflowError。Spring Boot 默认引入的是 log4j-over-slf4j(把旧 Log4j API 的日志桥接到 SLF4J),如果你又手动加了 slf4j-over-log4j,就中招了。

务必检查

添加任何日志相关依赖时,检查它是否传递引入了与现有桥接器方向相反的桥接器。用 mvn dependency:tree | grep slf4j 快速排查。

logback.xml vs logback-spring.xml 加载时机差异

这个坑在前面已经提到过,但值得再次强调:

配置文件 加载者 加载时机 application.yml 日志配置是否生效
logback.xml Logback 框架 Spring Boot 启动**之前** 不生效
logback-spring.xml Spring Boot Spring 上下文初始化**之后** 正常生效

实际后果:如果你用了 logback.xml,在 application.yml 中设置 logging.level.com.luguosong=DEBUG 可能完全不生效——因为 Logback 在 Spring Boot 读取配置之前就已经按照 logback.xml 初始化完成了。

结论:在 Spring Boot 项目中,始终使用 logback-spring.xml

📋 完整代码示例

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.0</version>
        <relativePath/>
    </parent>
    <groupId>com.luguosong</groupId>
    <artifactId>springboot-logging-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-logging-demo</name>
    <description>Spring Boot 日志配置演示</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
SpringbootLoggingApplication.java
package com.luguosong.springboot.logging;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootLoggingApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootLoggingApplication.class, args);
    }
}
application.yml
# 日志级别配置
logging:
  level:
    root: INFO
    com.luguosong: DEBUG
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/spring-app.log
  logback:
    rollingpolicy:
      max-file-size: 10MB
      max-history: 30
      total-size-cap: 1GB
  group:
    demo: "com.luguosong"
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <springProfile name="dev | default">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

    <springProfile name="prod">
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>logs/spring-app.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>logs/spring-app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                <maxFileSize>10MB</maxFileSize>
                <maxHistory>30</maxHistory>
            </rollingPolicy>
            <encoder>
                <pattern>${FILE_LOG_PATTERN}</pattern>
            </encoder>
        </appender>
        <root level="WARN">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>
</configuration>
SpringBootLoggingTest.java
package com.luguosong.springboot.logging;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * Spring Boot 日志配置演示测试
 * <p>
 * 验证 Spring Boot 默认日志体系(SLF4J + Logback)
 * 以及 application.yml 中各项日志配置的效果
 */
@SpringBootTest
public class SpringBootLoggingTest {

    // 通过 SLF4J 门面获取 Logger,不依赖具体实现
    private static final Logger logger = LoggerFactory.getLogger(SpringBootLoggingTest.class);

    /**
     * 在 Spring Boot 环境中使用 LoggerFactory.getLogger() 输出各级别日志,
     * 验证 SLF4J + Logback 组合在默认配置下的行为
     */
    @Test
    void testDefaultLogging() {
        // 各级别日志输出
        logger.trace("这是一条 TRACE 级别日志");
        logger.debug("这是一条 DEBUG 级别日志");
        logger.info("这是一条 INFO 级别日志");
        logger.warn("这是一条 WARN 级别日志");
        logger.error("这是一条 ERROR 级别日志");

        // 占位符用法
        String user = "张三";
        logger.info("用户 {} 登录成功", user);

        // 异常日志
        logger.error("操作失败", new RuntimeException("模拟业务异常"));
    }

    /**
     * 验证不同 profile 下日志行为差异
     * <p>
     * 默认 profile(dev):root 级别 INFO,com.luguosong 包级别 DEBUG
     * prod profile:root 级别 WARN,同时输出到文件
     */
    @Test
    void testProfileLogging() {
        // 当前默认 profile 下,com.luguosong 包级别为 DEBUG
        // 因此 DEBUG 及以上级别日志会被输出
        logger.debug("默认 profile 下,DEBUG 级别可见");
        logger.info("默认 profile 下,INFO 级别可见");
        logger.warn("默认 profile 下,WARN 级别可见");

        // 验证 Logger 实现类是 Logback(Spring Boot 默认)
        Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
        System.out.println("Root Logger 实现类: " + rootLogger.getClass().getName());
        System.out.println("当前 Logger 实现类: " + logger.getClass().getName());
    }
}

项目中有完整的可运行示例,路径为 code/java/javase/logging/springboot-logging-demo/