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

Spring CommonsRequestLoggingFilter无法记录响应,求响应日志实现方案

解决请求+响应(含错误)日志记录的简洁方案

CommonsRequestLoggingFilter确实只负责请求日志,要同时记录响应内容和错误信息,最简洁的方式是基于Spring自带工具类自定义过滤器,再配合全局异常处理器覆盖错误场景。下面给你具体实现:

方案一:自定义请求响应过滤器(核心实现)

我们可以继承Spring的OncePerRequestFilter,用ContentCachingResponseWrapper包装响应来捕获响应内容,同时处理请求日志和异常记录:

1. 编写自定义过滤器类

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingResponseWrapper;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class RequestResponseLoggingFilter extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger(RequestResponseLoggingFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 先记录请求基本信息
        logRequestDetails(request);

        // 用ContentCachingResponseWrapper包装响应,方便后续读取响应体
        ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);

        try {
            // 继续执行过滤器链
            filterChain.doFilter(request, wrappedResponse);
            // 记录正常响应内容
            logResponseDetails(wrappedResponse);
        } catch (Exception e) {
            // 捕获并记录请求处理过程中的异常
            logger.error("请求处理失败,URI: {}", request.getRequestURI(), e);
            throw e; // 不要吞异常,继续向上抛让Spring处理
        } finally {
            // 必须调用这个方法,把缓存的响应内容写回原始响应,否则客户端收不到结果
            wrappedResponse.copyBodyToResponse();
        }
    }

    private void logRequestDetails(HttpServletRequest request) {
        StringBuilder logMsg = new StringBuilder();
        logMsg.append("收到请求: ").append(request.getMethod()).append(" ").append(request.getRequestURI());
        if (request.getQueryString() != null) {
            logMsg.append("?").append(request.getQueryString());
        }
        // 如果需要记录请求体,可以用ContentCachingRequestWrapper包装request,类似响应的处理方式
        logger.info(logMsg.toString());
    }

    private void logResponseDetails(ContentCachingResponseWrapper response) {
        StringBuilder logMsg = new StringBuilder();
        logMsg.append("返回响应: 状态码=").append(response.getStatus());
        byte[] responseBody = response.getContentAsByteArray();
        if (responseBody.length > 0) {
            String bodyStr = new String(responseBody, StandardCharsets.UTF_8);
            logMsg.append(" | 响应体: ").append(bodyStr);
        }
        logger.info(logMsg.toString());
    }
}

2. 注册过滤器Bean

在配置类里把这个过滤器注册成Spring Bean:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LoggingFilterConfig {

    @Bean
    public RequestResponseLoggingFilter requestResponseLoggingFilter() {
        return new RequestResponseLoggingFilter();
    }
}

方案二:补充全局异常日志(可选)

如果想更细致地记录控制器层的异常,可以配合@ControllerAdvice实现全局异常处理,确保所有未捕获的异常都被记录:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleAllExceptions(Exception ex) {
        logger.error("发生未处理的异常", ex);
        return ResponseEntity.internalServerError().body("服务器内部错误");
    }
}

关键说明

  • ContentCachingResponseWrapper是Spring提供的工具类,会缓存响应体内容,让我们能在过滤器中读取,必须调用copyBodyToResponse(),否则客户端无法收到响应。
  • 如果需要记录请求体,可以用ContentCachingRequestWrapper包装请求,在filterChain.doFilter()之后读取getContentAsByteArray()即可。
  • 这个方案完全基于Spring原生类,不需要引入额外依赖,非常简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:22:15