强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

25 - 日志:SLF4J、Logback、Log4j2、结构化日志

25 - 日志:SLF4J、Logback、Log4j2、结构化日志

日志框架体系

┌─────────────────────────────────────┐
│         应用代码                      │
│     使用 SLF4J / Log4j2 API          │
├──────────────┬──────────────────────┤
│  SLF4J 门面   │  Log4j2 API          │
├──────────────┴──────────────────────┤
│           日志实现                    │
│  ┌─────────┬──────────┬──────────┐ │
│  │ Logback │ Log4j2   │ JUL      │ │
│  └─────────┴──────────┴──────────┘ │
└─────────────────────────────────────┘
组件角色说明
SLF4J门面(Facade)统一 API,不实现日志
Logback实现Spring Boot 默认,性能好
Log4j2实现Apache 出品,功能丰富
JUL实现JDK 内置,不推荐

SLF4J 使用

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);

    // 或使用 Lombok
    // @Slf4j
    // public class OrderService {

    public Order createOrder(String userId, List<Item> items) {
        log.info("创建订单: userId={}, 商品数={}", userId, items.size());

        try {
            Order order = doCreateOrder(userId, items);
            log.info("订单创建成功: orderId={}, 金额={}", order.getId(), order.getTotal());
            return order;
        } catch (InsufficientStockException e) {
            log.warn("库存不足: {}", e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error("创建订单失败: userId={}", userId, e);  // e 作为最后参数会打印堆栈
            throw e;
        }
    }
}

日志级别

级别用途示例
TRACE最细粒度追踪方法进入退出
DEBUG调试信息SQL 语句、参数
INFO关键业务事件订单创建、用户登录
WARN警告库存不足、配置缺失
ERROR错误异常、服务不可用

💡 使用占位符 {} 而非字符串拼接:log.info("name={}", name) 优于 log.info("name=" + name)

Logback 配置

<!-- src/main/resources/logback-spring.xml -->
<configuration>
    <property name="LOG_PATH" value="logs"/>
    <property name="APP_NAME" value="myapp"/>

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 文件输出(滚动) -->
    <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.gz</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- JSON 格式(结构化日志) -->
    <appender name="JSON" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${APP_NAME}-json.log</file>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <customFields>{"app":"${APP_NAME}","env":"${ENV:-dev}"}</customFields>
        </encoder>
    </appender>

    <!-- 环境差异化配置 -->
    <springProfile name="dev">
        <root level="DEBUG">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="FILE"/>
            <appender-ref ref="JSON"/>
        </root>
    </springProfile>

    <!-- 包级别控制 -->
    <logger name="org.hibernate.SQL" level="DEBUG"/>
    <logger name="com.zaxxer.hikari" level="WARN"/>
    <logger name="org.springframework.web" level="INFO"/>
</configuration>

Log4j2 配置

<!-- 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{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>

        <RollingFile name="File"
                     fileName="${LOG_PATH}/${APP_NAME}.log"
                     filePattern="${LOG_PATH}/${APP_NAME}-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="%d %-5level [%t] %logger{50} - %msg%n"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="100MB"/>
                <TimeBasedTriggeringPolicy interval="1"/>
            </Policies>
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>

        <Async name="AsyncFile" bufferSize="1024">
            <AppenderRef ref="File"/>
        </Async>
    </Appenders>

    <Loggers>
        <Logger name="org.hibernate.SQL" level="DEBUG"/>
        <Root level="INFO">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="AsyncFile"/>
        </Root>
    </Loggers>
</Configuration>

Logback vs Log4j2

维度LogbackLog4j2
Spring Boot 默认✅ 是需切换
性能更好(异步)
配置格式XMLXML/YAML/JSON/Properties
异步需配置内置 AsyncLogger
社区成熟活跃

结构化日志

// 使用 MDC(Mapped Diagnostic Context)
import org.slf4j.MDC;

public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        try {
            MDC.put("traceId", UUID.randomUUID().toString());
            MDC.put("userId", getUserId(request));
            MDC.put("requestUri", ((HttpServletRequest) request).getRequestURI());
            chain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

⚠️ 注意事项

  1. 不要使用 System.out.println — 没有级别、时间戳、线程信息。
  2. 生产环境不要开 DEBUG — 日志量大,影响性能。
  3. 异常日志传入异常对象log.error("msg", e) 而非 log.error(e.getMessage())
  4. 避免字符串拼接 — 使用 {} 占位符,性能更好。

💡 技巧

  1. Lombok @Slf4j — 自动生成 private static final Logger log
  2. 异步日志 — Log4j2 的 AsyncLogger 可大幅减少日志对业务线程的影响。
  3. 日志采样 — 高并发场景可配置只记录部分 DEBUG 日志。

🏢 业务场景

  • 线上排错: ERROR 日志 + 堆栈信息定位问题根因。
  • 审计追踪: INFO 日志记录关键业务操作。
  • 性能分析: TRACE/DEBUG 日志分析方法执行时间。
  • ELK 集构: JSON 格式日志 → Filebeat → Elasticsearch → Kibana。

📖 扩展阅读