在Logback中配置`requestId`进行日志追踪的实践与应用

在Logback中配置requestId进行日志追踪的实践与应用

1. 引言

在分布式系统和微服务架构中,日志是调试、监控和性能分析的关键工具。然而,由于多个请求会在系统中并行处理,日志记录很容易变得杂乱无章,难以区分属于同一请求的日志。为了解决这一问题,我们可以在日志中引入requestId,确保每个请求的生命周期内生成的所有日志都包含一个唯一标识符。

本文将深入探讨如何在Java Spring Boot应用中,通过配置logback-spring.xml文件和使用MDC(Mapped Diagnostic Context)技术,确保在不同场景下(包括普通Controller请求、定时任务、线程池场景等)日志中都能记录并追踪唯一的requestId

2. 基础知识

2.1 什么是Logback?

Logback 是一个高效、灵活的Java日志框架,广泛用于Spring Boot项目中。它支持多种日志输出格式和策略,允许用户灵活配置日志级别、输出格式、目标位置等。

2.2 什么是requestId

requestId是一个唯一标识符,用于标识每个请求的日志条目。通过为每个请求生成一个唯一的requestId,我们可以在分布式系统中轻松追踪请求的流向和执行情况。

2.3 什么是MDC(Mapped Diagnostic Context)?

MDC(映射诊断上下文)是slf4j提供的一种机制,允许在多线程环境中存储和共享上下文信息(例如requestId)。MDC数据与当前线程相关联,因此能够有效区分和跟踪多线程环境中的日志。


3. 普通Controller请求场景

3.1 场景描述

在Web应用中,每个HTTP请求会触发一个Controller方法的执行。为了在日志中记录每个请求的唯一标识符,我们需要在每个请求开始时生成一个requestId,并将其贯穿于请求处理的整个流程中。

3.2 配置Logback

首先,我们需要在logback-spring.xml文件中配置日志格式,确保requestId能被正确输出:

  1. 修改logback-spring.xml文件

    xml 复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
    
        <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{requestId}] %-5level %logger{36} - %msg%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>

    在这里,我们使用%X{requestId}来引用MDC中的requestId

  2. 创建过滤器以生成和设置requestId

    创建一个Servlet过滤器,在每个请求到达时生成一个requestId并将其放入MDC:

    java 复制代码
    import org.slf4j.MDC;
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.util.UUID;
    
    public class RequestIdFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            // 初始化方法,可根据需要进行设置
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            try {
                String requestId = UUID.randomUUID().toString();
                MDC.put("requestId", requestId);
                chain.doFilter(request, response);
            } finally {
                MDC.remove("requestId");
            }
        }
    
        @Override
        public void destroy() {
            // 销毁方法,可根据需要进行设置
        }
    }
  3. 在Spring Boot应用中注册过滤器

    java 复制代码
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class FilterConfig {
    
        @Bean
        public FilterRegistrationBean<RequestIdFilter> requestIdFilter() {
            FilterRegistrationBean<RequestIdFilter> registrationBean = new FilterRegistrationBean<>();
            registrationBean.setFilter(new RequestIdFilter());
            registrationBean.addUrlPatterns("/*"); // 应用到所有URL
            return registrationBean;
        }
    }
3.3 实现效果

通过这种方式,每个HTTP请求都会被分配一个唯一的requestId,并且在请求处理的整个过程中都能记录在日志中,方便调试和分析。


4. 定时任务场景

4.1 场景描述

在应用中,定时任务(Scheduled Tasks)通常用于执行一些周期性的后台任务。我们希望每次定时任务执行时,都生成一个新的requestId以跟踪任务的执行情况。

4.2 配置Logback和AOP
  1. 配置Logback

    确保logback-spring.xml中的日志格式包含%X{requestId}

  2. 创建AOP切面以生成requestId

    如果你使用了@Scheduled注解定义定时任务,可以使用Spring AOP在每次任务执行前生成requestId

    java 复制代码
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Pointcut;
    import org.slf4j.MDC;
    import org.springframework.stereotype.Component;
    import java.util.UUID;
    
    @Aspect
    @Component
    public class ScheduledTaskAspect {
    
        @Pointcut("@annotation(org.springframework.scheduling.annotation.Scheduled)")
        public void scheduledMethod() {}
    
        @Before("scheduledMethod()")
        public void beforeScheduledMethod() {
            String requestId = UUID.randomUUID().toString();
            MDC.put("requestId", requestId);
        }
    
        @After("scheduledMethod()")
        public void afterScheduledMethod() {
            MDC.remove("requestId");
        }
    }
  3. @Scheduled的定时任务

    对于不是使用@Scheduled注解的定时任务(例如自己实现的定时任务调度器),你可以在任务执行方法的开头和结尾手动设置和清除requestId

    java 复制代码
    public void executeTask() {
        try {
            String requestId = UUID.randomUUID().toString();
            MDC.put("requestId", requestId);
            // 执行任务逻辑
        } finally {
            MDC.remove("requestId");
        }
    }
4.3 实现效果

通过使用AOP或手动设置requestId,我们能够确保每次定时任务执行时都生成一个新的requestId,使日志更加清晰易读。


5. 线程池场景

5.1 场景描述

在多线程环境中(如使用线程池执行异步任务),子线程不会自动继承父线程的MDC上下文。因此,我们需要确保子线程能够正确继承父线程的requestId,并在其基础上拼接一个4位随机码,形成唯一标识符。

5.2 配置线程池和MDC
  1. 创建自定义的Runnable包装类

    使用自定义MdcRunnable类来确保MDC上下文在子线程中得以传递:

    java 复制代码
    import org.slf4j.MDC;
    import java.util.Map;
    import java.util.UUID;
    
    public class MdcRunnable implements Runnable {
        private final Runnable task;
        private final Map<String, String> contextMap;
    
        public MdcRunnable(Runnable task) {
            this.task = task;
            this.contextMap = MDC.getCopyOfContextMap();
        }
    
        @Override
        public void run() {
            if (contextMap != null) {
                MDC.setContextMap(contextMap);
            }
            try {
                // 拼接4位随机码
                String requestId = MDC.get("requestId");
                if (requestId != null) {
                    String newRequestId = requestId + "-" + UUID.randomUUID().toString().substring(0, 4);
                    MDC.put("requestId", newRequestId);
                }
                task.run();
            } finally {
                MDC.clear();
            }
        }
    }
  2. 修改线程池配置

    在现有的线程池配置中,使用MdcRunnable来包装每个任务:

    java 复制代码
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    import java.util.concurrent.Executor;
    
    @Configuration
    public

class ThreadPoolConfig {

复制代码
   @Bean(name = "mdcThreadPoolExecutor")
   public Executor threadPoolTaskExecutor() {
       ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       executor.setCorePoolSize(10);
       executor.setMaxPoolSize(20);
       executor.setQueueCapacity(100);
       executor.setThreadNamePrefix("mdc-executor-");
       executor.setTaskDecorator(runnable -> new MdcRunnable(runnable));
       executor.initialize();
       return executor;
   }

}

复制代码
3. **使用自定义线程池**:

在需要异步处理的地方使用自定义的线程池:

```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.Executor;

@Service
public class AsyncService {

    @Autowired
    private Executor mdcThreadPoolExecutor;

    @Async("mdcThreadPoolExecutor")
    public void executeAsyncTask() {
        // 子线程的逻辑
        // 此处日志的requestId将继承自父线程并拼接随机码
        System.out.println("Executing task in async service with requestId: " + MDC.get("requestId"));
    }
}
5.3 实现效果

通过自定义的线程池配置,确保子线程能够继承并修改requestId,使得在复杂的异步任务场景下仍能保持日志的一致性和可追溯性。


6. 总结

通过在Spring Boot应用中正确配置Logback、MDC和线程池,我们可以确保每个请求的日志都能包含一个唯一的requestId,从而提高系统日志的可读性和调试效率。无论是在普通的HTTP请求、定时任务,还是多线程场景下,我们都可以通过合适的配置和编码实践,确保日志的准确性和可追溯性。这种日志追踪机制在大型分布式系统中尤其重要,有助于快速定位问题并提高系统的稳定性。

相关推荐
EumenidesJ15 天前
Java常用日志框架介绍
java·log4j·logback·slf4j
躲在没风的地方24 天前
logback日志控制服务器日志输出
java·服务器·logback
ta叫我小白1 个月前
Spring Boot 设置滚动日志logback
java·spring boot·spring·logback
代码的余温1 个月前
Spring Boot集成Logback日志全攻略
xml·spring boot·logback
代码的余温1 个月前
Logback.xml配置详解与实战指南
xml·logback
清风92001 个月前
Logback——日志技术(基础)
java·前端·logback
代码的余温1 个月前
MyBatis集成Logback日志全攻略
java·tomcat·mybatis·logback
秋千码途1 个月前
小架构step系列08:logback.xml的配置
xml·java·logback
枣伊吕波1 个月前
第十五节:第六部分:日志技术:logback的核心配置文件详解、日志级别
logback
再见晴天*_*1 个月前
logback 日志不打印
java·服务器·logback