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 构建