一、核心概念

1.1 为什么需要日志?

日志是应用程序运行时的“黑匣子”,用于:

  • 调试:定位问题、追踪执行流程。
  • 监控:分析系统行为、性能瓶颈。
  • 审计:记录关键操作(如登录、支付)。
  • 告警:发现异常并通知运维。

1.2 Spring Boot 默认日志框架:Logback

  • Spring Boot 默认集成 SLF4J + Logback
  • SLF4J(Simple Logging Facade for Java):日志门面,提供统一 API。
  • Logback:SLF4J 的原生实现,性能高、功能强大。

1.3 可选日志框架:Log4j2

  • Log4j2:Apache 开发,性能优于 Logback(尤其在异步日志场景)。
  • 支持更丰富的插件和功能(如自动重新加载配置)。
  • 需要排除默认的 Logback 依赖。

1.4 日志级别(Level)

日志按严重程度分级,级别从高到低

级别 说明 使用场景
OFF 关闭所有日志 通常不用
ERROR 错误,程序无法继续运行 异常捕获、系统故障
WARN 警告,潜在问题 业务逻辑异常、降级处理
INFO 信息,关键业务流程 启动信息、重要操作
DEBUG 调试,详细执行信息 开发调试、问题排查
TRACE 追踪,比 DEBUG 更详细 深度调试

规则:如果当前日志级别设置为 INFO,则 DEBUGTRACE 日志不会输出。


二、详细操作步骤(适合快速实践)

步骤 1:项目准备

创建标准 Spring Boot 项目,确保包含 spring-boot-starter-web(它依赖 spring-boot-starter-logging)。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 其他依赖 -->
</dependencies>

步骤 2:使用默认 Logback(无需额外依赖)

Spring Boot 已内置 Logback,可直接使用。

2.1 在代码中使用日志

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

@RestController
public class UserController {

    // 获取日志记录器(通常以类名为参数)
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @GetMapping("/user/{id}")
    public String getUser(@PathVariable Long id) {
        logger.debug("Fetching user with id: {}", id); // 使用占位符 {}
        
        if (id <= 0) {
            logger.warn("Invalid user id: {}", id);
            return "Invalid ID";
        }

        try {
            // 模拟业务逻辑
            String user = findUserById(id);
            logger.info("User fetched successfully: {}", user);
            return user;
        } catch (Exception e) {
            logger.error("Failed to fetch user with id: {}", id, e); // 记录异常堆栈
            return "Error";
        }
    }

    private String findUserById(Long id) {
        if (id == 1L) return "Alice";
        throw new RuntimeException("User not found");
    }
}

2.2 配置日志级别(通过 application.yml

# application.yml
logging:
  level:
    root: INFO                    # 根日志级别
    com.example: DEBUG            # 指定包的日志级别
    org.springframework: WARN     # 降低 Spring 框架日志级别
    org.hibernate: ERROR          # 仅记录 Hibernate 错误

  # 文件输出配置
  file:
    name: logs/app.log            # 日志文件路径(相对或绝对)
    # max-size: 10MB              # 单个文件最大大小(需 logback-spring.xml 配置)
    # max-history: 7               # 保留天数

  # 控制台输出格式
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} - %5p %c{1}:%L - %m%n"
    # %d: 日期, %5p: 级别(5字符宽), %c{1}: 类名(简写), %L: 行号, %m: 消息, %n: 换行

2.3 使用 logback-spring.xml 进行高级配置

src/main/resources/ 下创建 logback-spring.xml(推荐使用 -spring 后缀以支持 Spring 扩展)。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 定义变量 -->
    <property name="LOG_PATH" value="logs"/>
    <property name="APP_NAME" value="myapp"/>

    <!-- 彩色控制台输出(Spring Boot 提供) -->
    <springProfile name="dev,test">
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>
                    %clr(%d{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}
                </pattern>
            </encoder>
        </appender>
    </springProfile>

    <!-- 生产环境控制台输出(无颜色) -->
    <springProfile name="prod">
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    </springProfile>

    <!-- 按天滚动的文件输出 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${APP_NAME}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天生成一个文件,保留30天 -->
            <fileNamePattern>${LOG_PATH}/archive/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%d{ISO8601} [%thread] %-5level %logger{50} - %msg%n%ex{10}</pattern>
        </encoder>
    </appender>

    <!-- 异常日志单独输出(可选) -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${APP_NAME}-error.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/archive/${APP_NAME}-error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{ISO8601} [%thread] %-5level %logger{50} - %msg%n%ex</pattern>
        </encoder>
    </appender>

    <!-- 根日志器 -->
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>

    <!-- 特定包的日志器 -->
    <logger name="com.example.service" level="DEBUG" additivity="false">
        <appender-ref ref="FILE"/>
        <!-- 不继承根日志器的 appender -->
    </logger>
</configuration>

步骤 3:切换到 Log4j2

3.1 排除默认日志依赖并引入 Log4j2

<dependencies>
    <!-- 排除 spring-boot-starter-web 中的 logging starter -->
    <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>

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

3.2 创建 log4j2-spring.xml 配置文件

src/main/resources/ 下创建 log4j2-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Properties>
        <Property name="LOG_PATH">logs</Property>
        <Property name="APP_NAME">myapp</Property>
    </Properties>

    <Appenders>
        <!-- 控制台输出 -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>

        <!-- 按天滚动的文件输出 -->
        <RollingFile name="FileAppender" fileName="${LOG_PATH}/${APP_NAME}.log"
                     filePattern="${LOG_PATH}/archive/${APP_NAME}-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout>
                <Pattern>%d{ISO8601} [%thread] %-5level %logger{50} - %msg%n%ex{10}</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100MB"/>
            </Policies>
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>

        <!-- 错误日志单独输出 -->
        <RollingFile name="ErrorFileAppender" fileName="${LOG_PATH}/${APP_NAME}-error.log"
                     filePattern="${LOG_PATH}/archive/${APP_NAME}-error-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout>
                <Pattern>%d{ISO8601} [%thread] %-5level %logger{50} - %msg%n%ex</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100MB"/>
            </Policies>
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <!-- 根日志器 -->
        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="FileAppender"/>
            <AppenderRef ref="ErrorFileAppender"/>
        </Root>

        <!-- 特定包 -->
        <Logger name="com.example.service" level="debug" additivity="false">
            <AppenderRef ref="FileAppender"/>
        </Logger>
    </Loggers>
</Configuration>

3.3 在 application.yml 中配置(可选)

# application.yml - Log4j2 配置(部分可通过 YAML 设置)
logging:
  config: classpath:log4j2-spring.xml  # 显式指定配置文件
  level:
    root: INFO
    com.example: DEBUG

三、常见错误与解决方案

错误现象 原因分析 解决方案
No appenders could be found for logger 配置文件未找到或命名错误 检查 logback-spring.xmllog4j2-spring.xml 是否在 resources 目录
日志级别不生效 配置未加载或级别设置错误 检查 application.yml 或 XML 配置中的 level 设置
文件未生成 路径权限问题或路径错误 确认日志目录有写权限;使用绝对路径测试
异步日志不生效 未正确配置异步 Appender Logback:使用 AsyncAppender;Log4j2:确保 AsyncLoggerAsyncAppender 配置正确
ClassNotFoundException: ch.qos.logback.classic.Logger 依赖冲突 确保已排除 spring-boot-starter-logging 当使用 Log4j2 时

四、注意事项

  1. 配置文件命名
    • Logback:logback-spring.xml(推荐)或 logback.xml
    • Log4j2:log4j2-spring.xml(推荐)或 log4j2.xml
    • 使用 -spring 后缀可支持 <springProfile> 等 Spring 扩展。
  2. 日志级别设置
    • 生产环境避免 DEBUG/TRACE,防止日志爆炸。
    • 关键业务包可单独设置级别。
  3. 性能影响
    • 字符串拼接:使用 logger.debug("Value: " + expensiveMethod()); 会始终执行 expensiveMethod()
    • 正确方式:logger.debug("Value: {}", expensiveMethod());(仅在需要时计算)。
  4. 异常日志
    • 记录异常时,传入 Throwable 对象以输出完整堆栈:logger.error("Message", exception);
  5. 多环境配置
    • 使用 <springProfile name="dev"> 在 XML 中区分环境。

五、使用技巧

5.1 动态修改日志级别(生产环境神器)

使用 Spring Boot Actuator

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: loggers  # 暴露 loggers 端点
  • 查看日志级别

    GET http://localhost:8080/actuator/loggers
    GET http://localhost:8080/actuator/loggers/com.example.service
    
  • 修改日志级别

    POST http://localhost:8080/actuator/loggers/com.example.service
    Content-Type: application/json
    {
      "configuredLevel": "DEBUG"
    }
    

5.2 使用 MDC(Mapped Diagnostic Context)

在日志中添加上下文信息(如请求 ID、用户 ID)。

// 在拦截器或 Filter 中
import org.slf4j.MDC;

MDC.put("userId", "123");
MDC.put("requestId", UUID.randomUUID().toString());

// 在日志 pattern 中使用
// Logback pattern: %X{userId} %X{requestId}
// 输出: [userId=123] [requestId=abc-123] ...

// 清理
MDC.clear();

5.3 异步日志(提升性能)

Logback 异步配置

<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="FILE"/>
    <queueSize>1000</queueSize>
    <discardingThreshold>0</discardingThreshold> <!-- 0 表示不丢弃 ERROR 日志 -->
</appender>

<root level="INFO">
    <appender-ref ref="ASYNC_FILE"/>
</root>

Log4j2 异步配置(推荐)

  • 全局异步:在 log4j2.component.properties 中设置 Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
  • 混合模式:部分 Logger 异步。

5.4 日志归档与清理

  • 配置 maxHistorytotalSizeCap(Logback)或 DefaultRolloverStrategy(Log4j2)自动清理旧日志。
  • 使用外部脚本(如 Linux logrotate)进行更复杂的归档策略。

六、最佳实践

  1. 统一日志格式
    • 团队内约定日志格式,便于解析和分析。
  2. 关键信息结构化
    • 使用 JSON 格式输出日志,便于 ELK 等系统解析。
    <PatternLayout pattern='{"time":"%d{ISO8601}","level":"%p","logger":"%c","message":"%m","exception":"%ex"}%n'/>
    
  3. 避免日志泄露敏感信息
    • 不要记录密码、密钥、完整身份证号等。
  4. 合理使用日志级别
    • INFO 记录关键业务;DEBUG 用于开发;ERROR 必须包含可行动信息。
  5. 监控日志文件大小
    • 防止磁盘被占满。
  6. 使用集中式日志系统
    • 将日志发送到 ELK(Elasticsearch, Logstash, Kibana)或 Splunk。

七、性能优化

优化点 Logback Log4j2
同步日志 性能良好 性能优秀
异步日志 AsyncAppender,性能提升明显 AsyncLogger,性能极佳(LMAX Disruptor)
字符串拼接 使用 {} 占位符避免不必要的字符串创建 同左
I/O 性能 合理设置 maxFileSize 和滚动策略 同左,Log4j2 在高并发下 I/O 更稳定
总体建议 对于一般应用足够 对于高吞吐、低延迟场景优先选择

结论:对于大多数应用,Logback 性能已足够。若追求极致性能或需要 Log4j2 特有功能,可切换。


总结

Spring Boot 日志管理是应用可观测性的基石。

  • 默认选择:Logback + logback-spring.xml,简单高效。
  • 高性能选择:Log4j2,在高并发场景下表现更优。
  • 关键技巧:使用占位符、MDC、异步日志、Actuator 动态调级。
  • 最佳实践:结构化日志、避免敏感信息、集中管理。

快速上手建议

  1. 使用默认 Logback。
  2. 配置 logback-spring.xml 实现文件滚动。
  3. 通过 application.yml 设置日志级别。
  4. 引入 Actuator 实现生产环境动态调级。

本文基于 Spring Boot 3.x 编写。