Logback 是 Java 生态系统中使用最广泛的日志框架之一,作为 Log4j 的继任者和 SLF4J 的原生实现,它以其卓越的性能、灵活的配置和丰富的功能成为现代 Java 应用的首选日志解决方案。

1. Logback 简介

1.1 什么是 Logback

Logback 由 Log4j 创始人 Ceki Gülcü 开发,设计上分为三个核心模块:

模块作用
logback-core基础框架,为其他两个模块提供支持
logback-classicSLF4J 的原生实现,包含核心日志功能
logback-access与 Servlet 容器集成,提供 HTTP 访问日志功能

1.2 为什么选 Logback

性能优势:
- 初始化速度比 Log4j 快约 10 倍
- 内存占用更少
- 异步 Appender 吞吐量更高
- 自动重载配置无需重启应用

功能优势:
- 原生支持 SLF4J
- 条件化配置(if/then/else)
- 自动压缩归档日志
- 更强大的过滤器

2. 核心架构

2.1 三大核心组件

// Logger - 日志记录器,负责捕获日志信息
Logger logger = LoggerFactory.getLogger(MyClass.class);
logger.info("用户 {} 登录成功", username);

// Appender - 日志输出目标,决定日志写到哪里
// ConsoleAppender -> 控制台
// FileAppender -> 文件
// RollingFileAppender -> 滚动文件

// Layout/Encoder - 日志格式化,定义输出样式
// PatternLayoutEncoder 是最常用的

2.2 日志级别层级

ERROR > WARN > INFO > DEBUG > TRACE > ALL
  ^                                       |
  |                                       |
  +---------------------------------------+
              (继承关系)

级别设置规则:只输出级别 >= 设定级别的日志

// 如果 root logger 设置为 INFO
logger.debug("这行不会输出");  // DEBUG < INFO,忽略
logger.info("这行会输出");     // INFO >= INFO,输出
logger.error("这行会输出");    // ERROR > INFO,输出

3. 配置详解

3.1 XML 配置(最常用)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 定义变量 -->
    <property name="LOG_PATTERN" 
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
    <property name="LOG_DIR" value="logs"/>

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 文件输出 -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>${LOG_DIR}/application.log</file>
        <append>true</append>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 滚动文件输出(生产环境推荐) -->
    <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/app.log</file>
        
        <!-- 滚动策略:按时间和文件大小 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天生成一个新文件 -->
            <fileNamePattern>${LOG_DIR}/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 保留 30 天 -->
            <maxHistory>30</maxHistory>
            <!-- 总大小限制 3GB -->
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        
        <!-- 触发策略:文件超过 100MB 滚动 -->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <maxFileSize>100MB</maxFileSize>
        </triggeringPolicy>
        
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 异步 Appender(高性能场景) -->
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 队列大小,默认 256 -->
        <queueSize>512</queueSize>
        <!-- 丢弃策略:队列满时丢弃 INFO 及以下级别 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 不丢失日志事件 -->
        <neverBlock>false</neverBlock>
        <appender-ref ref="ROLLING_FILE"/>
    </appender>

    <!-- 包级别的日志级别配置 -->
    <logger name="com.example.dao" level="DEBUG"/>
    <logger name="org.springframework" level="WARN"/>
    <logger name="org.hibernate.SQL" level="DEBUG"/>

    <!-- Root Logger -->
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="ASYNC"/>
    </root>
</configuration>

3.2 配置自动重载

<!-- 每 60 秒扫描配置变化 -->
<configuration scan="true" scanPeriod="60 seconds">
    ...
</configuration>

3.3 条件化配置(多环境支持)

<configuration>
    <!-- 根据环境变量选择配置 -->
    <if condition='property("env").contains("dev")'>
        <then>
            <property name="LOG_LEVEL" value="DEBUG"/>
        </then>
        <else>
            <property name="LOG_LEVEL" value="INFO"/>
        </else>
    </if>
    
    <root level="${LOG_LEVEL}">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

4. 日志格式详解

4.1 常用 Pattern 符号

符号含义示例输出
%d{pattern}日期时间%d{yyyy-MM-dd HH:mm:ss} → 2024-01-15 14:30:25
%level / %le / %p日志级别INFO, DEBUG, ERROR
%logger{len}Logger 名(可缩写)%logger{36} → c.e.m.MyService
%msg / %m日志消息实际日志内容
%thread / %t线程名main, pool-1-thread-2
%class调用类名(慢,不推荐生产用)com.example.MyClass
%method / %M调用方法名(慢)doSomething
%line / %L行号(慢)42
%file / %F文件名(慢)MyClass.java
%ex / %exception异常堆栈完整的 stack trace
%n换行符平台相关
%X{key}MDC 变量见下文 MDC 章节
%markerMarker 标记特殊标记,用于过滤

4.2 颜色输出(IDE/终端友好)

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n</pattern>
    </encoder>
</appender>

颜色说明:

  • %highlight() - 根据级别自动着色(ERROR=红,WARN=黄,INFO=绿,DEBUG=蓝)
  • %cyan() / %magenta() / %yellow() 等 - 指定颜色

5. 代码中使用

5.1 基础用法

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

public class UserService {
    // 推荐:用类名作为 logger 名
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
    
    public void login(String username, String password) {
        // 简单日志
        logger.info("用户登录请求");
        
        // 带参数的日志(推荐,避免字符串拼接开销)
        logger.info("用户 {} 尝试登录", username);
        
        // 多参数
        logger.debug("用户 {} 从 IP {} 登录,时间 {}", 
                     username, clientIp, LocalDateTime.now());
        
        // 判断级别(避免参数计算开销)
        if (logger.isDebugEnabled()) {
            // 复杂参数只在 DEBUG 级别计算
            logger.debug("详细对象信息: {}", expensiveToString());
        }
    }
}

5.2 异常日志

try {
    riskyOperation();
} catch (BusinessException e) {
    // 正确:记录异常,最后一个参数放异常对象
    logger.error("业务处理失败,订单号: {}", orderNo, e);
} catch (Exception e) {
    // 错误示范:不要这样!会丢失堆栈
    // logger.error("发生错误: " + e.getMessage());
    
    // 正确
    logger.error("系统异常", e);
}

5.3 使用 Markers 进行高级过滤

import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class AuditService {
    // 创建标记
    private static final Marker AUDIT_MARKER = MarkerFactory.getMarker("AUDIT");
    private static final Marker SECURITY_MARKER = MarkerFactory.getMarker("SECURITY");
    
    static {
        SECURITY_MARKER.add(AUDIT_MARKER);  // 标记继承
    }
    
    public void deleteUser(Long userId) {
        // 带标记的日志
        logger.info(SECURITY_MARKER, "删除用户 {}, 操作人 {}", userId, currentUser);
    }
}

配合配置过滤:

<appender name="AUDIT_FILE" class="ch.qos.logback.core.FileAppender">
    <file>logs/audit.log</file>
    <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
        <evaluator class="ch.qos.logback.classic.boo.JaninoEventEvaluator">
            <expression>
                marker != null && marker.contains("AUDIT")
            </expression>
        </evaluator>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
    </encoder>
</appender>

6. MDC(Mapped Diagnostic Context)

MDC 是 Logback 的杀手锏功能,用于在日志中添加上下文信息(如用户ID、请求ID、TraceID 等)。

6.1 基本使用

import org.slf4j.MDC;

public class OrderController {
    private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
    
    public void createOrder(CreateOrderRequest request) {
        // 1. 放入上下文
        MDC.put("userId", request.getUserId());
        MDC.put("requestId", UUID.randomUUID().toString());
        MDC.put("traceId", request.getTraceId());
        
        try {
            logger.info("开始创建订单");
            // 业务逻辑...
            orderService.create(request);
            logger.info("订单创建成功,订单号: {}", orderNo);
        } finally {
            // 2. 必须清理!否则内存泄漏
            MDC.clear();
        }
    }
}

6.2 配合 Pattern

<pattern>%d{HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{userId}] %-5level %logger - %msg%n</pattern>

输出示例:

14:30:25.123 [http-nio-8080-exec-1] [abc-123-xyz] [user_9527] INFO c.e.s.OrderService - 开始创建订单
14:30:25.145 [http-nio-8080-exec-1] [abc-123-xyz] [user_9527] DEBUG c.e.s.OrderService - 参数验证通过
14:30:25.200 [http-nio-8080-exec-1] [abc-123-xyz] [user_9527] INFO c.e.s.OrderService - 订单创建成功,订单号: ORDER_20240115_001

6.3 Spring Boot 集成(自动填充 MDC)

@Component
public class MdcInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                            HttpServletResponse response, 
                            Object handler) {
        String traceId = request.getHeader("X-Trace-Id");
        if (traceId == null) {
            traceId = UUID.randomUUID().toString().replace("-", "");
        }
        MDC.put("traceId", traceId);
        MDC.put("clientIp", getClientIp(request));
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request,
                               HttpServletResponse response,
                               Object handler, Exception ex) {
        MDC.clear();
    }
}

7. 高级特性

7.1 日志分离(不同业务模块写不同文件)

<!-- 订单模块日志 -->
<appender name="ORDER_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logs/order/order.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logs/order/order.%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
    </encoder>
</appender>

<!-- 支付模块日志 -->
<appender name="PAY_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logs/pay/pay.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logs/pay/pay.%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>90</maxHistory>  <!-- 支付日志保留更久 -->
    </rollingPolicy>
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
    </encoder>
</appender>

<logger name="com.example.order" level="INFO" additivity="false">
    <appender-ref ref="ORDER_LOG"/>
    <appender-ref ref="CONSOLE"/>
</logger>

<logger name="com.example.pay" level="INFO" additivity="false">
    <appender-ref ref="PAY_LOG"/>
    <appender-ref ref="CONSOLE"/>
</logger>

注意 additivity="false" 防止日志重复输出到 root。

7.2 SMTP 告警(错误邮件通知)

<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>smtp.company.com</smtpHost>
    <smtpPort>587</smtpPort>
    <username>alert@company.com</username>
    <password>password</password>
    <to>ops@company.com</to>
    <from>alert@company.com</from>
    <subject>%logger{20} - %m</subject>
    
    <!-- 只发送 ERROR 级别 -->
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>ERROR</level>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
    
    <layout class="ch.qos.logback.classic.PatternLayout">
        <pattern>%d{yyyy-MM-dd HH:mm:ss} %level %logger - %msg%n%ex</pattern>
    </layout>
</appender>

7.3 日志脱敏(自定义 Encoder)

public class MaskingPatternEncoder extends PatternLayoutEncoder {
    
    private Pattern pattern = Pattern.compile("(phone|idCard|password)=([^&]*)");
    private String replacement = "$1=***";
    
    @Override
    public byte[] encode(ILoggingEvent event) {
        String formatted = layout.doLayout(event);
        // 脱敏处理
        String masked = pattern.matcher(formatted).replaceAll(replacement);
        return masked.getBytes(StandardCharsets.UTF_8);
    }
}

8. Spring Boot 集成

8.1 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 包含 spring-boot-starter-logging,自动引入 logback -->

8.2 application.yml 配置

logging:
  level:
    root: INFO
    com.example: DEBUG
    org.springframework.web: WARN
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/application.log
  logback:
    rollingpolicy:
      max-file-size: 100MB
      max-history: 30

8.3 多环境配置

# application-dev.yml
logging:
  level:
    root: DEBUG
    com.example: TRACE

# application-prod.yml  
logging:
  level:
    root: WARN
    com.example: INFO
  pattern:
    console: "%d{HH:mm:ss} [%X{traceId}] %-5level - %msg%n"

9. 性能优化建议

9.1 DO(应该做)

// 1. 使用参数化日志,避免字符串拼接
logger.debug("User {} logged in from {}", user, ip);
// 而不是:logger.debug("User " + user + " logged in from " + ip);

// 2. 异步日志应对高并发
// 使用 <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">

// 3. 关闭不必要的行号、方法名输出(%L, %M 很耗性能)
// 生产环境用:%d %level %logger{36} - %msg%n

// 4. 批量写入(增大缓冲区)
// <immediateFlush>false</immediateFlush>

// 5. 使用 isXxxEnabled 避免昂贵参数计算
if (logger.isDebugEnabled()) {
    logger.debug("Result: {}", heavyComputation());
}

9.2 DON’T(不要做)

// 1. 不要创建多个 Logger 实例
// 错误:每次 new LoggerFactory.getLogger(...)
// 正确:static final Logger

// 2. 不要记录过多 DEBUG 信息在生产环境
// 使用条件配置或动态调整级别

// 3. 不要在循环中记录大量日志
for (Item item : items) {
    logger.debug("Processing item: {}", item);  // 如果 items 很多,危险!
}
// 改为:logger.debug("Processed {} items", items.size());

// 4. 不要记录敏感信息
logger.info("User password: {}", password);  // 绝对不要!

10. 常见问题排查

10.1 日志不输出

检查清单:

  1. logback.xml 是否在 src/main/resources 目录
  2. 文件名是否正确(区分大小写)
  3. 日志级别是否设置正确
  4. 包路径是否匹配 <logger name="...">

10.2 日志重复

原因:Logger 的 additivity 默认为 true,日志会向上传播给父 Logger。

<!-- 解决:设置 additivity="false" -->
<logger name="com.example.dao" level="DEBUG" additivity="false">
    <appender-ref ref="DAO_LOG"/>
</logger>

10.3 中文乱码

<encoder>
    <pattern>...</pattern>
    <charset>UTF-8</charset>  <!-- 显式指定编码 -->
</encoder>

10.4 动态调整日志级别(运行时)

// 通过 JMX 或代码动态调整
LoggerContext ctx = (LoggerContext) LoggerFactory.getILoggerFactory();
ctx.getLogger("com.example").setLevel(Level.DEBUG);

11. 总结

场景推荐方案
本地开发ConsoleAppender + 彩色输出
测试环境文件 + Console,DEBUG 级别
生产环境RollingFileAppender + AsyncAppender,INFO/WARN 级别
分布式追踪MDC 传递 traceId/requestId
审计日志Marker + 单独 Appender
告警通知SMTPAppender 过滤 ERROR

Logback 的优势在于其简洁的设计、高性能和灵活性。掌握它的核心概念(Logger、Appender、Layout/Encoder)和 MDC 特性,你就能构建出既易用又强大的日志系统。


参考资源: