Skip to content

Logback 日志框架完整技术文档 ​

目录 ​

  1. Logback 简介
  2. 核心组件
  3. Appender 详解
  4. Layout 和 Encoder
  5. Filter 过滤器
  6. Logger 配置
  7. MDC 上下文
  8. 高级特性
  9. 性能优化
  10. 最佳实践
  11. 故障排查

一、Logback 简介 ​

1.1 什么是 Logback? ​

Logback 是由 Log4j 创始人 Ceki Gülcü 设计的另一个开源日志组件,是 Log4j 的继任者。

核心优势:

  • ⚡ 性能更优(比 Log4j 快约 10 倍)
  • 🔄 自动重载配置文件
  • 📦 更小的内存占用
  • 🎯 原生支持 SLF4J API
  • 🛡️ 谨慎的模式(遇到错误会降级而非崩溃)
  • 📝 完善的日志归档和压缩

1.2 与其他日志框架的关系 ​

┌─────────────────────────────────────┐
│        应用程序代码                   │
└─────────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────┐
│        SLF4J API(日志门面)          │
└─────────────────────────────────────┘
                  │
         ┌────────┴────────┐
         ▼                 ▼
    ┌────────┐       ┌──────────┐
    │ Logback│       │  Log4j2  │
    └────────┘       └──────────┘

1.3 Maven 依赖 ​

xml
<dependencies>
    <!-- SLF4J API -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.9</version>
    </dependency>
    
    <!-- Logback Classic(包含 Core) -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.14</version>
    </dependency>
    
    <!-- 可选:Logstash Encoder(JSON 格式日志) -->
    <dependency>
        <groupId>net.logstash.logback</groupId>
        <artifactId>logstash-logback-encoder</artifactId>
        <version>7.4</version>
    </dependency>
</dependencies>

1.4 配置文件加载顺序 ​

Logback 按以下顺序查找配置文件:

  1. logback-test.xml(Classpath)
  2. logback.groovy(Classpath)
  3. logback.xml(Classpath)
  4. 如果都没找到,使用 BasicConfigurator(控制台输出)

指定配置文件:

bash
java -Dlogback.configurationFile=/path/to/config.xml -jar app.jar

二、核心组件 ​

Logback 由三个核心模块组成:

2.1 Logger(日志记录器) ​

负责捕获日志信息。

java
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) {
        logger.debug("用户 {} 开始登录", username);
        logger.info("用户 {} 登录成功", username);
        logger.warn("用户 {} 密码即将过期", username);
        logger.error("用户 {} 登录失败", username);
    }
}

2.2 Appender(输出目的地) ​

决定日志输出到哪里(控制台、文件、数据库等)。

2.3 Layout/Encoder(格式化器) ​

决定日志的输出格式。

架构图 ​

Logger (决定是否记录)
   │
   ├─ Filter (过滤器)
   │
   ├─ Appender (输出到哪里)
   │    │
   │    ├─ Encoder/Layout (格式化)
   │    │
   │    └─ Filter (Appender 级别过滤)
   │
   └─ 继承父 Logger 的 Appender (可配置)

三、Appender 详解 ​

Appender 是日志输出的目的地,Logback 提供了多种 Appender。

3.1 ConsoleAppender(控制台输出) ​

类名: ch.qos.logback.core.ConsoleAppender

功能: 将日志输出到控制台(System.out 或 System.err)

配置示例:

xml
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 输出目标:System.out(默认)或 System.err -->
        <target>System.out</target>
        
        <!-- 编码器:定义输出格式 -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        
        <!-- 过滤器:只输出 INFO 及以上级别 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
    </appender>
    
    <root level="DEBUG">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

适用场景:

  • ✅ 开发环境调试
  • ✅ Docker 容器(日志收集通过标准输出)
  • ✅ Kubernetes Pod(kubectl logs)
  • ❌ 生产环境(除非使用容器)

3.2 FileAppender(文件输出) ​

类名: ch.qos.logback.core.FileAppender

功能: 将日志写入指定文件

配置示例:

xml
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <!-- 日志文件路径 -->
    <file>logs/application.log</file>
    
    <!-- 是否追加到文件末尾(false 会覆盖) -->
    <append>true</append>
    
    <!-- 立即刷新(true 安全但慢,false 快但可能丢日志) -->
    <immediateFlush>true</immediateFlush>
    
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        <charset>UTF-8</charset>
    </encoder>
</appender>

缺点:

  • ❌ 文件会无限增长
  • ❌ 没有自动归档和清理
  • ❌ 生产环境不推荐

3.3 RollingFileAppender(滚动文件输出)⭐ ​

类名: ch.qos.logback.core.rolling.RollingFileAppender

功能: 将日志写入文件,并支持自动滚动(按时间、大小等)

核心配置:

xml
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 当前活动日志文件 -->
    <file>logs/application.log</file>
    
    <!-- 滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <!-- 归档文件命名模式 -->
        <fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
        
        <!-- 单个文件最大大小 -->
        <maxFileSize>100MB</maxFileSize>
        
        <!-- 保留的归档文件数量(天数) -->
        <maxHistory>30</maxHistory>
        
        <!-- 所有归档文件总大小上限 -->
        <totalSizeCap>10GB</totalSizeCap>
        
        <!-- 是否在启动时清理过期文件 -->
        <cleanHistoryOnStart>true</cleanHistoryOnStart>
    </rollingPolicy>
    
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        <charset>UTF-8</charset>
    </encoder>
</appender>

3.3.1 滚动策略详解 ​

A. TimeBasedRollingPolicy(按时间滚动)

xml
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- 按天滚动 -->
    <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
    
    <!-- 按小时滚动 -->
    <!-- <fileNamePattern>logs/app.%d{yyyy-MM-dd-HH}.log</fileNamePattern> -->
    
    <!-- 按月滚动 -->
    <!-- <fileNamePattern>logs/app.%d{yyyy-MM}.log</fileNamePattern> -->
    
    <maxHistory>30</maxHistory>
</rollingPolicy>

B. SizeBasedRollingPolicy(按大小滚动)

xml
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
    <fileNamePattern>logs/app.%i.log</fileNamePattern>
    <minIndex>1</minIndex>
    <maxIndex>10</maxIndex>
</rollingPolicy>

<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
    <maxFileSize>10MB</maxFileSize>
</triggeringPolicy>

C. SizeAndTimeBasedRollingPolicy(按时间+大小)⭐ 推荐

xml
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
    <!-- %d 按天,%i 是同一天内的序号 -->
    <fileNamePattern>logs/app.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
    <maxFileSize>100MB</maxFileSize>
    <maxHistory>30</maxHistory>
    <totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>

生成的文件示例:

logs/
├── application.log              # 当前活动文件
├── application.2025-12-07.0.log.gz
├── application.2025-12-07.1.log.gz
├── application.2025-12-07.2.log.gz
├── application.2025-12-06.0.log.gz
└── application.2025-12-05.0.log.gz

3.4 AsyncAppender(异步输出) ​

类名: ch.qos.logback.classic.AsyncAppender

功能: 异步写入日志,提升性能(日志事件先放入队列,后台线程处理)

配置示例:

xml
<configuration>
    <!-- 实际的输出 Appender -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d %p %c - %m%n</pattern>
        </encoder>
    </appender>
    
    <!-- 异步包装器 -->
    <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 引用实际的 Appender -->
        <appender-ref ref="FILE"/>
        
        <!-- 队列大小(默认 256) -->
        <queueSize>512</queueSize>
        
        <!-- 丢弃阈值:队列剩余容量小于此值时丢弃 TRACE/DEBUG/INFO -->
        <!-- 设为 0 表示永不丢弃 -->
        <discardingThreshold>0</discardingThreshold>
        
        <!-- 队列满时是否阻塞(false 会丢弃日志) -->
        <neverBlock>false</neverBlock>
        
        <!-- 是否提取调用者信息(会影响性能) -->
        <includeCallerData>false</includeCallerData>
        
        <!-- 最大刷新时间(毫秒) -->
        <maxFlushTime>1000</maxFlushTime>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="ASYNC_FILE"/>
    </root>
</configuration>

性能对比:

配置QPS延迟
同步文件 Appender~5000高
异步文件 Appender~50000低

注意事项:

  • ⚠️ 应用关闭时可能丢失队列中的日志(设置合理的 maxFlushTime)
  • ⚠️ includeCallerData=true 会显著影响性能
  • ✅ 生产环境强烈推荐

3.5 其他常用 Appender ​

SMTPAppender(邮件通知) ​

xml
<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>smtp.gmail.com</smtpHost>
    <smtpPort>587</smtpPort>
    <STARTTLS>true</STARTTLS>
    <username>your-email@gmail.com</username>
    <password>your-password</password>
    
    <to>admin@company.com</to>
    <from>app@company.com</from>
    <subject>应用错误: %logger{20} - %m</subject>
    
    <layout class="ch.qos.logback.classic.html.HTMLLayout"/>
    
    <!-- 只发送 ERROR 级别 -->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>ERROR</level>
    </filter>
</appender>

SyslogAppender(发送到 Syslog 服务器) ​

xml
<appender name="SYSLOG" class="ch.qos.logback.classic.net.SyslogAppender">
    <syslogHost>localhost</syslogHost>
    <facility>USER</facility>
    <suffixPattern>[%thread] %logger %msg</suffixPattern>
</appender>

DBAppender(数据库) ​

xml
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
    <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
        <driverClass>com.mysql.cj.jdbc.Driver</driverClass>
        <url>jdbc:mysql://localhost:3306/logs</url>
        <user>root</user>
        <password>password</password>
    </connectionSource>
</appender>

四、Layout 和 Encoder ​

4.1 Encoder vs Layout ​

特性EncoderLayout
输出格式字节流(byte[])字符串(String)
功能更强大(可压缩、加密)只格式化
推荐程度✅ 推荐⚠️ 旧版

4.2 PatternLayoutEncoder(最常用) ​

xml
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    <charset>UTF-8</charset>
    <immediateFlush>true</immediateFlush>
</encoder>

4.3 LogstashEncoder(JSON 格式) ​

xml
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
    <!-- 添加自定义字段 -->
    <customFields>{"app":"my-app","env":"prod"}</customFields>
    
    <!-- 包含 MDC -->
    <includeMdc>true</includeMdc>
    
    <!-- 包含上下文信息 -->
    <includeContext>true</includeContext>
    
    <!-- 包含调用者信息 -->
    <includeCallerData>false</includeCallerData>
    
    <!-- 堆栈哈希 -->
    <stackTracePattern>
        <omitCommonFrames>true</omitCommonFrames>
    </stackTracePattern>
</encoder>

输出示例:

json
{
  "@timestamp": "2025-12-08T14:30:25.123+08:00",
  "message": "用户登录成功",
  "logger_name": "com.Glowxq.UserService",
  "thread_name": "http-nio-8080-exec-1",
  "level": "INFO",
  "app": "my-app",
  "env": "prod",
  "mdc": {
    "traceId": "abc123",
    "userId": "user_12345"
  }
}

五、Filter 过滤器 ​

Filter 用于精确控制哪些日志事件被记录。

5.1 Filter 的位置 ​

xml
<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>logs/app.log</file>
        
        <!-- Appender 级别的 Filter -->
        <filter class="...">
            ...
        </filter>
        
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- Logger 级别的 Filter -->
    <turboFilter class="...">
        ...
    </turboFilter>
</configuration>

5.2 Filter 决策 ​

Filter 有三种返回值:

返回值含义
ACCEPT接受日志,不再询问后续 Filter
DENY拒绝日志,不再询问后续 Filter
NEUTRAL中立,继续询问下一个 Filter

5.3 LevelFilter(精确级别过滤) ​

配置示例:

xml
<filter class="ch.qos.logback.classic.filter.LevelFilter">
    <!-- 匹配的级别 -->
    <level>ERROR</level>
    
    <!-- 匹配时的决策 -->
    <onMatch>ACCEPT</onMatch>
    
    <!-- 不匹配时的决策 -->
    <onMismatch>DENY</onMismatch>
</filter>

使用场景: 单独收集 ERROR 日志

xml
<!-- ERROR 专用文件 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logs/error.log</file>
    
    <!-- 只记录 ERROR 级别 -->
    <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>logs/error.%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>90</maxHistory>
    </rollingPolicy>
    
    <encoder>
        <pattern>%d %p %c - %m%n%ex</pattern>
    </encoder>
</appender>

5.4 ThresholdFilter(阈值过滤) ​

配置示例:

xml
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
    <!-- 只记录 WARN 及以上级别 -->
    <level>WARN</level>
</filter>

行为:

  • 高于或等于阈值:返回 NEUTRAL
  • 低于阈值:返回 DENY

5.5 EvaluatorFilter(条件过滤) ​

基于表达式的强大过滤器。

xml
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
    <evaluator>
        <!-- 只记录包含 "重要" 关键字的日志 -->
        <expression>message.contains("重要")</expression>
    </evaluator>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
</filter>

更复杂的示例(过滤敏感信息):

xml
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
    <evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
        <expression>
            return message.contains("password") || 
                   message.contains("token") || 
                   message.contains("secret");
        </expression>
    </evaluator>
    <onMatch>DENY</onMatch>
    <onMismatch>NEUTRAL</onMismatch>
</filter>

5.6 自定义 Filter ​

java
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;

public class BusinessFilter extends Filter<ILoggingEvent> {
    
    private String keyword;
    
    @Override
    public FilterReply decide(ILoggingEvent event) {
        if (event.getMessage().contains(keyword)) {
            return FilterReply.ACCEPT;
        }
        return FilterReply.DENY;
    }
    
    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }
}

XML 配置:

xml
<filter class="com.example.BusinessFilter">
    <keyword>订单</keyword>
</filter>

六、Logger 配置 ​

6.1 Logger 层级 ​

Logger 有层级关系,类似 Java 包结构:

root
 ├─ com
 │   └─ Glowxq
 │       └─ base
 │           ├─ service
 │           │   └─ UserService
 │           └─ controller
 │               └─ UserController
 └─ org
     └─ springframework

6.2 Logger 继承 ​

子 Logger 会继承父 Logger 的配置(Level 和 Appender)。

xml
<configuration>
    <!-- 根 Logger -->
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
    
    <!-- com.Glowxq 包:DEBUG 级别 -->
    <logger name="com.Glowxq" level="DEBUG"/>
    
    <!-- com.Glowxq.base.service 包:TRACE 级别,不继承父 Appender -->
    <logger name="com.Glowxq.base.service" level="TRACE" additivity="false">
        <appender-ref ref="FILE"/>
    </logger>
</configuration>

继承示例:

Logger 名称有效级别Appender
rootINFOCONSOLE
com.GlowxqDEBUGCONSOLE(继承)
com.Glowxq.base.serviceTRACEFILE(不继承)
com.Glowxq.base.controllerDEBUGCONSOLE(继承自 com.Glowxq)

6.3 additivity 属性 ​

作用: 控制日志事件是否向上传播到父 Logger。

xml
<!-- additivity="true"(默认):日志会传播到父 Logger -->
<logger name="com.Glowxq.service" level="DEBUG" additivity="true">
    <appender-ref ref="SERVICE_FILE"/>
</logger>

<!-- additivity="false":日志不会传播到父 Logger -->
<logger name="com.Glowxq.controller" level="DEBUG" additivity="false">
    <appender-ref ref="CONTROLLER_FILE"/>
</logger>

效果对比:

java
Logger logger = LoggerFactory.getLogger("com.Glowxq.service.UserService");
logger.info("test");

additivity="true" 时:

  • 输出到 SERVICE_FILE
  • 也输出到父 Logger 的 Appender(如 CONSOLE)

additivity="false" 时:

  • 只输出到 SERVICE_FILE
  • 不输出到父 Logger

6.4 关闭第三方库的日志 ​

readme.md

xml
<!-- 完全关闭 -->
<logger name="org.apache" level="OFF"/>
<logger name="org.springframework" level="OFF"/>

<!-- 只输出错误 -->
<logger name="com.zaxxer.hikari" level="ERROR"/>
<logger name="io.netty" level="ERROR"/>

<!-- 调试 SQL -->
<logger name="org.hibernate.SQL" level="DEBUG"/>
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>

七、MDC 上下文 ​

7.1 什么是 MDC? ​

MDC(Mapped Diagnostic Context) 是线程级别的键值对存储,用于在日志中添加上下文信息。

7.2 基本使用 ​

java
import org.slf4j.MDC;

public class OrderService {
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
    
    public void createOrder(String orderId, String userId) {
        // 设置 MDC
        MDC.put("orderId", orderId);
        MDC.put("userId", userId);
        
        try {
            logger.info("开始创建订单");
            // ... 业务逻辑 ...
            logger.info("订单创建成功");
        } finally {
            // 清理 MDC(避免内存泄漏)
            MDC.clear();
        }
    }
}

配置:

xml
<pattern>%d [%X{orderId}] [%X{userId}] %p - %m%n</pattern>

输出:

2025-12-08 14:30:25.123 [order_123] [user_456] INFO - 开始创建订单
2025-12-08 14:30:25.456 [order_123] [user_456] INFO - 订单创建成功

7.3 Web 应用中的 MDC ​

Filter 实现:

java
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;

public class MDCFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        try {
            // 生成 TraceId
            String traceId = UUID.randomUUID().toString().replace("-", "");
            MDC.put("traceId", traceId);
            
            // 从请求头获取 UserId
            String userId = httpRequest.getHeader("X-User-Id");
            if (userId != null) {
                MDC.put("userId", userId);
            }
            
            // 记录请求信息
            MDC.put("requestUri", httpRequest.getRequestURI());
            MDC.put("method", httpRequest.getMethod());
            
            chain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

Spring Boot 配置:

java
@Configuration
public class FilterConfig {
    
    @Bean
    public FilterRegistrationBean<MDCFilter> mdcFilter() {
        FilterRegistrationBean<MDCFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new MDCFilter());
        registration.addUrlPatterns("/*");
        registration.setOrder(1);
        return registration;
    }
}

7.4 多线程环境中的 MDC ​

问题: MDC 是 ThreadLocal,子线程不会自动继承父线程的 MDC。

解决方案:

java
import java.util.Map;
import java.util.concurrent.Callable;

public class MDCCallable<T> implements Callable<T> {
    private final Callable<T> callable;
    private final Map<String, String> contextMap;
    
    public MDCCallable(Callable<T> callable) {
        this.callable = callable;
        // 保存当前线程的 MDC
        this.contextMap = MDC.getCopyOfContextMap();
    }
    
    @Override
    public T call() throws Exception {
        // 恢复 MDC
        if (contextMap != null) {
            MDC.setContextMap(contextMap);
        }
        try {
            return callable.call();
        } finally {
            MDC.clear();
        }
    }
}

线程池配置:

java
@Configuration
public class AsyncConfig {
    
    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        
        // 设置 MDC 装饰器
        executor.setTaskDecorator(runnable -> {
            Map<String, String> contextMap = MDC.getCopyOfContextMap();
            return () -> {
                try {
                    if (contextMap != null) {
                        MDC.setContextMap(contextMap);
                    }
                    runnable.run();
                } finally {
                    MDC.clear();
                }
            };
        });
        
        executor.initialize();
        return executor;
    }
}

八、高级特性 ​

8.1 条件配置 ​

根据不同环境使用不同配置:

xml
<configuration>
    <!-- 从 Spring Boot 读取环境变量 -->
    <springProperty scope="context" name="app.env" source="spring.profiles.active"/>
    
    <!-- 开发环境:控制台 + DEBUG -->
    <if condition='property("app.env").equals("dev")'>
        <then>
            <root level="DEBUG">
                <appender-ref ref="CONSOLE"/>
            </root>
        </then>
    </if>
    
    <!-- 生产环境:文件 + INFO -->
    <if condition='property("app.env").equals("prod")'>
        <then>
            <root level="INFO">
                <appender-ref ref="ROLLING_FILE"/>
            </root>
        </then>
    </if>
</configuration>

8.2 变量定义 ​

xml
<configuration>
    <!-- 定义变量 -->
    <property name="LOG_HOME" value="/var/logs/myapp"/>
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
    
    <!-- 使用变量 -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>${LOG_HOME}/application.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>
    
    <!-- 从系统属性读取 -->
    <property name="LOG_LEVEL" value="${log.level:-INFO}"/>
    
    <root level="${LOG_LEVEL}">
        <appender-ref ref="FILE"/>
    </root>
</configuration>

启动参数:

bash
java -Dlog.level=DEBUG -jar app.jar

8.3 包含外部配置 ​

xml
<configuration>
    <!-- 包含其他配置文件 -->
    <include resource="logback-appenders.xml"/>
    <include resource="logback-loggers.xml"/>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

8.4 自动扫描配置变更 ​

xml
<configuration scan="true" scanPeriod="30 seconds">
    <!-- 配置文件变更后 30 秒自动重载 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        ...
    </appender>
</configuration>

8.5 JMX 管理 ​

xml
<configuration>
    <!-- 启用 JMX -->
    <jmxConfigurator/>
    
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        ...
    </appender>
</configuration>

使用 JConsole 连接后可以动态修改:

  • Logger 级别
  • Appender 状态
  • 查看日志统计

九、性能优化 ​

9.1 使用异步 Appender ​

xml
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="FILE"/>
    <queueSize>512</queueSize>
    <discardingThreshold>0</discardingThreshold>
    <neverBlock>false</neverBlock>
</appender>

9.2 避免使用位置信息 ​

❌ 避免:

xml
<pattern>%C.%M:%L - %m%n</pattern>

✅ 推荐:

xml
<pattern>%logger{36} - %m%n</pattern>

9.3 使用参数化日志 ​

❌ 避免字符串拼接:

java
logger.debug("User " + userId + " login at " + new Date());

✅ 推荐参数化:

java
logger.debug("User {} login at {}", userId, new Date());

9.4 条件日志 ​

java
// 避免不必要的对象创建
if (logger.isDebugEnabled()) {
    logger.debug("Complex message: {}", createComplexObject());
}

9.5 合理的日志级别 ​

xml
<!-- 生产环境 -->
<root level="INFO"/>

<!-- 关键业务 DEBUG -->
<logger name="com.Glowxq.core" level="DEBUG"/>

<!-- 第三方库只记录错误 -->
<logger name="org.apache" level="ERROR"/>
<logger name="org.springframework" level="WARN"/>

9.6 限制日志大小 ​

xml
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
    <fileNamePattern>logs/app.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
    <maxFileSize>100MB</maxFileSize>
    <maxHistory>7</maxHistory>
    <totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>

十、最佳实践 ​

10.1 生产环境完整配置模板 ​

xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
    
    <!-- ========== 变量定义 ========== -->
    <property name="APP_NAME" value="Glowxq-base"/>
    <property name="LOG_HOME" value="${LOG_PATH:-./logs}"/>
    <property name="LOG_PATTERN" 
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId:-}] %5p [%t] %-40.40logger{39} : %m%n"/>
    
    <!-- ========== 控制台输出 ========== -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%cyan(%d{HH:mm:ss.SSS}) %highlight(%-5level) %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    
    <!-- ========== INFO 日志文件 ========== -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${APP_NAME}-info.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/${APP_NAME}-info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    
    <!-- ========== ERROR 日志文件 ========== -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${APP_NAME}-error.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}%ex{full}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/${APP_NAME}-error.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>
    
    <!-- ========== 异步包装 ========== -->
    <appender name="ASYNC_INFO" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="INFO_FILE"/>
        <queueSize>512</queueSize>
        <discardingThreshold>0</discardingThreshold>
    </appender>
    
    <appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="ERROR_FILE"/>
        <queueSize>256</queueSize>
        <discardingThreshold>0</discardingThreshold>
    </appender>
    
    <!-- ========== Logger 配置 ========== -->
    <logger name="com.Glowxq" level="DEBUG"/>
    
    <!-- 关闭第三方库的日志 -->
    <logger name="org.apache" level="WARN"/>
    <logger name="org.springframework" level="WARN"/>
    <logger name="com.zaxxer.hikari" level="WARN"/>
    <logger name="io.netty" level="WARN"/>
    
    <!-- ========== Root Logger ========== -->
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="ASYNC_INFO"/>
        <appender-ref ref="ASYNC_ERROR"/>
    </root>
    
</configuration>

10.2 微服务分布式追踪 ​

xml
<configuration>
    <property name="LOG_PATTERN" 
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId:-N/A}] [%X{spanId:-N/A}] [%X{userId:-}] %5p [%t] %logger{36} : %m%n"/>
    
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

Filter 实现:

java
@Component
public class TraceFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        try {
            // 从请求头获取或生成 TraceId
            String traceId = httpRequest.getHeader("X-Trace-Id");
            if (traceId == null) {
                traceId = UUID.randomUUID().toString().replace("-", "");
            }
            MDC.put("traceId", traceId);
            
            // SpanId
            String spanId = UUID.randomUUID().toString().substring(0, 8);
            MDC.put("spanId", spanId);
            
            // 传递 TraceId 到下游服务
            ((HttpServletResponse) response).setHeader("X-Trace-Id", traceId);
            
            chain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

10.3 日志脱敏 ​

xml
<conversionRule conversionWord="mask" 
                converterClass="com.Glowxq.log.MaskingConverter"/>

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <encoder>
        <pattern>%d %p %c - %mask(%m)%n</pattern>
    </encoder>
</appender>

Converter 实现:

java
public class MaskingConverter extends ClassicConverter {
    
    private static final Pattern PHONE_PATTERN = Pattern.compile("(\\d{3})\\d{4}(\\d{4})");
    private static final Pattern ID_CARD_PATTERN = Pattern.compile("(\\d{6})\\d{8}(\\d{4})");
    
    @Override
    public String convert(ILoggingEvent event) {
        String message = event.getFormattedMessage();
        
        // 手机号脱敏
        message = PHONE_PATTERN.matcher(message).replaceAll("$1****$2");
        
        // 身份证脱敏
        message = ID_CARD_PATTERN.matcher(message).replaceAll("$1********$2");
        
        return message;
    }
}

十一、故障排查 ​

11.1 开启 Logback 调试 ​

xml
<configuration debug="true">
    <!-- 会打印配置解析过程 -->
</configuration>

或者启动参数:

bash
java -Dlogback.debug=true -jar app.jar

11.2 常见问题 ​

问题1:日志不输出 ​

原因:

  • Logger 级别设置过高
  • Filter 拒绝了日志
  • Appender 配置错误

排查:

xml
<configuration debug="true">
    <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
    ...
</configuration>

问题2:日志重复输出 ​

原因: additivity="true" 导致日志传播到父 Logger

解决:

xml
<logger name="com.Glowxq" level="DEBUG" additivity="false">
    <appender-ref ref="FILE"/>
</logger>

问题3:日志文件不滚动 ​

检查:

  • fileNamePattern 是否包含时间/索引占位符
  • 文件路径是否有写权限
  • 查看 Logback 内部状态
java
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);

问题4:中文乱码 ​

解决:

xml
<encoder>
    <pattern>%msg%n</pattern>
    <charset>UTF-8</charset>
</encoder>

问题5:性能问题 ​

检查:

  • 是否使用了 %C、%F、%L、%M
  • 是否使用异步 Appender
  • 日志级别是否合理

11.3 监控日志系统 ​

java
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.Appender;
import org.slf4j.LoggerFactory;

@Component
public class LogbackMonitor {
    
    @Scheduled(fixedRate = 60000)
    public void checkLogback() {
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
        
        // 检查 Appender 状态
        context.getLoggerList().forEach(logger -> {
            Iterator<Appender> it = logger.iteratorForAppenders();
            while (it.hasNext()) {
                Appender appender = it.next();
                if (!appender.isStarted()) {
                    // 告警:Appender 未启动
                    System.err.println("Appender " + appender.getName() + " is not started!");
                }
            }
        });
    }
}

附录 ​

A. 配置文件 Schema ​

xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://ch.qos.logback/xml/ns/logback"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback
                                   https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd">
    ...
</configuration>

B. Logback vs Log4j2 ​

特性LogbackLog4j2
性能高极高
异步AsyncAppender原生异步
无锁否是
Lambda不支持支持
配置XMLXML/JSON/YAML
社区成熟活跃

C. 参考资源 ​


文档版本: 1.0
适用版本: Logback 1.2.x - 1.5.x
最后更新: 2025-12-08
作者: Glowxq 技术团队

基于 VitePress 构建