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

Spring Boot自定义DispatcherServlet异常时重复日志问题求助

问题分析

当控制器抛出ResponseStatusException这类异常时,Spring Boot的默认错误处理逻辑会将请求转发到/error端点生成标准错误响应,这直接导致自定义DispatcherServlet被执行两次:

  • 第一次是原始的/api/test/error请求,此时控制器抛出异常还未生成响应体,日志中无响应体
  • 第二次是转发后的/error请求,此时请求体已被读取无法重复获取,日志中无请求体
解决方案

方案1:过滤/error端点日志

在日志方法中判断请求URI,直接跳过/error请求的日志记录:

private void log(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
    String requestUri = request.getRequestURI();
    if ("/error".equals(requestUri)) {
        return;
    }
    // 原有日志逻辑
    System.out.println("Logging...");
}

这种方式简单直接,只保留业务请求的日志记录。

方案2:改用OncePerRequestFilter实现(推荐)

自定义DispatcherServlet并非记录请求响应日志的最佳实践,Spring官方推荐使用OncePerRequestFilter,它能保证一次请求仅执行一次过滤逻辑,天然避免重复执行问题:

实现日志Filter

@Component
public class RequestResponseLoggingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);

        try {
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        } finally {
            log(wrappedRequest, wrappedResponse);
            wrappedResponse.copyBodyToResponse();
        }
    }

    private void log(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
        // 原有日志逻辑
        System.out.println("Logging...");
    }
}

清理原有配置

删除之前自定义的dispatcherServlet() Bean注册,无需替换默认的DispatcherServlet。

该方案优势:

  • 符合Spring生态最佳实践,Filter是专门处理请求拦截、日志的组件
  • OncePerRequestFilter自动处理请求转发、包含等场景,确保逻辑只执行一次
  • 无需额外判断/error端点,逻辑更简洁

方案3:关联原始请求与错误响应(进阶)

如果需要关联原始请求和错误响应的日志,可以通过请求属性获取原始请求信息:

private void log(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
    String originalUri = (String) request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
    if (originalUri != null) {
        // 记录错误响应对应的原始请求URI
        System.out.println("Logging error response for original request: " + originalUri);
    } else {
        // 记录正常业务请求日志
        System.out.println("Logging...");
    }
}

这种方式可以把两次请求的日志关联起来,便于问题排查,但需要额外处理日志关联逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 12:56:17