You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中如何捕获特定请求日志并在debug参数为true时返回

捕获Spring请求特定日志并返回的实现方案

核心思路

利用你已有的唯一请求ID,通过自定义日志Appender拦截该请求ID对应的日志,在请求结束时将收集到的日志附加到响应中。

具体实现步骤

1. 自定义日志Appender(以Logback为例)

创建一个继承AppenderBase<ILoggingEvent>的自定义Appender,用ThreadLocal暂存当前请求的日志(Spring MVC默认单线程处理请求,ThreadLocal效率更高):

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import java.util.ArrayList;
import java.util.List;

public class RequestScopedLogAppender extends AppenderBase<ILoggingEvent> {
    private static final ThreadLocal<List<String>> requestLogs = new ThreadLocal<>();

    @Override
    protected void append(ILoggingEvent event) {
        List<String> logs = requestLogs.get();
        if (logs != null) {
            // 格式化日志,保留请求ID、时间、级别等关键信息
            String requestId = event.getMDCPropertyMap().get("requestId"); // 从MDC取已有的请求ID
            String formattedLog = String.format("[%s] [%s] [%s] %s",
                event.getTimeStamp(), event.getLevel(), requestId, event.getFormattedMessage());
            logs.add(formattedLog);
        }
    }

    // 初始化当前请求的日志容器
    public static void startCapturing() {
        requestLogs.set(new ArrayList<>());
    }

    // 获取日志并清理ThreadLocal,避免内存泄漏
    public static List<String> getAndClearLogs() {
        List<String> logs = requestLogs.get();
        requestLogs.remove();
        return logs != null ? logs : new ArrayList<>();
    }
}

在logback.xml中配置该Appender,确保它能接收所有日志:

<appender name="REQUEST_SCOPED" class="com.yourpackage.RequestScopedLogAppender" />
<root level="INFO">
    <appender-ref ref="REQUEST_SCOPED" />
    <!-- 保留原有控制台/文件Appender,不影响正常日志输出 -->
    <appender-ref ref="CONSOLE" />
</root>

2. 拦截请求,开启日志捕获

用Spring HandlerInterceptor在请求开始时判断是否需要捕获日志:

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;

public class DebugLogInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 检查debug参数是否为true
        if ("true".equalsIgnoreCase(request.getParameter("debug"))) {
            RequestScopedLogAppender.startCapturing();
        }
        return true;
    }
}

注册拦截器到Spring MVC:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DebugLogInterceptor());
    }
}

3. 响应中附加日志

用ResponseBodyAdvice在返回响应前,将捕获的日志包装到响应内容中:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@ControllerAdvice
public class DebugLogResponseAdvice implements ResponseBodyAdvice<Object> {
    private final ObjectMapper objectMapper;

    public DebugLogResponseAdvice(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true; // 对所有接口生效,可按需调整范围
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        HttpServletRequest servletRequest = (HttpServletRequest) request.getServletRequest();
        if (!"true".equalsIgnoreCase(servletRequest.getParameter("debug"))) {
            return body; // 非debug请求直接返回原响应
        }

        List<String> logs = RequestScopedLogAppender.getAndClearLogs();
        if (logs.isEmpty()) {
            return body;
        }

        // 包装原响应和日志
        Map<String, Object> responseWrapper = new HashMap<>();
        responseWrapper.put("data", body);
        responseWrapper.put("debugLogs", logs);

        // 处理String类型响应,避免类型转换异常
        if (body instanceof String) {
            try {
                return objectMapper.writeValueAsString(responseWrapper);
            } catch (JsonProcessingException e) {
                throw new RuntimeException("包装debug日志失败", e);
            }
        }
        return responseWrapper;
    }
}

注意事项

  • 内存泄漏防护:必须在getAndClearLogs中调用requestLogs.remove(),清理ThreadLocal中的数据。
  • 性能控制:仅在debug=true时开启日志捕获,避免影响正常请求的性能。
  • 日志级别过滤:如果需要只捕获特定级别日志,可在Appender的append方法中增加级别判断,比如只收集DEBUG及以上级别。

内容的提问来源于stack exchange,提问作者BBloggsbott

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 02:56:24