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
相关产品推荐
相关产品推荐

