在 RestController ExceptionHandler 中记录请求正文字符串

Lei*_*ang 4 java servlets spring-mvc servlet-filters spring-boot

  • 最终目标:

在 RestController 的 @ExceptionHandler 中记录请求正文字符串

  • 解释

默认情况下,当 request 是无效的 json 时,springboot 会抛出一个HttpMessageNotReadableException,但该消息非常通用,不包含具体的请求正文。这使得调查变得困难。另一方面,我可以使用过滤器记录每个请求字符串,但这样日志将被太多成功的字符串淹没。我只想在请求无效时记录该请求。我真正想要的是@ExceptionHandler我将得到该字符串(以前在某个地方得到过)并记录为错误。

为了说明这个问题,我在github上创建了一个demo项目

  • 控制器:
@RestController
public class GreetController {
    protected static final Logger log = LogManager.getLogger();

    @PostMapping("/")
    public String greet(@RequestBody final WelcomeMessage msg) {
        // if controller successfully returned (valid request),
        // then don't want any request body logged
        return "Hello " + msg.from;
    }

    @ExceptionHandler({HttpMessageNotReadableException.class})
    public String addStudent(HttpMessageNotReadableException e) {
        // this is what I really want!
        log.error("{the request body string got somewhere, such as Filters }"); 
        return "greeting from @ExceptionHandler";
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 客户端
    1. 有效请求 curl -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message":"nice to meet you!"}'

    2. 无效请求(无效 json) curl -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message""nice to meet you!"}'

我曾经尝试过HandlerInterceptor,但会收到一些错误,例如

“java.lang.IllegalStateException:在当前请求已调用 getReader() 后无法调用 getInputStream()”。

经过一番搜索1 2后,我决定使用Filterwith ContentCachingRequestWrapper

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper(httpServletRequest);
        chain.doFilter(cachedRequest, response);
        String requestBody = IOUtils.toString(cachedRequest.getContentAsByteArray(), cachedRequest.getCharacterEncoding());
        log.info(requestBody);
    }
Run Code Online (Sandbox Code Playgroud)

该代码运行良好,只是日志位于RestController. 如果我改变顺序:

        String requestBody = IOUtils.toString(cachedRequest.getReader());
        log.info(requestBody);
        chain.doFilter(cachedRequest, response);
Run Code Online (Sandbox Code Playgroud)

适用于无效请求,但当请求有效时,出现以下异常:

com.example.demo.GreetController :缺少必需的请求正文:public java.lang.String com.example.demo.GreetController.greet(com.example.demo.WelcomeMessage)

我还尝试了getContentAsByteArray,getInputStreamgetReader方法,因为一些教程说框架会检查特定方法调用。

CommonsRequestLoggingFilter按照@M的建议尝试过。德努姆。

但一切都是徒劳。

现在我有点困惑。谁能解释一下请求有效和无效时RestController和的执行顺序吗?Filter有没有更简单的方法(更少的代码)来实现我的最终目标?谢谢!

我使用的是springboot 2.6.3,jdk11。

M. *_*num 7

  1. 创建一个过滤器,将您的请求包装在 a 中ContentCachingRequestWrapper(仅此而已)。

  2. 在异常处理方法中使用HttpServletRequest作为参数作为参数

  3. 检查实例是否ContentCachingRequestWrapper

  4. 使用getContentAsByteArray来获取内容。

像这样的东西。

public class CachingFilter extends OncePerRequestFilter {

protected abstract void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

  filterChain.doFilter(new ContentCachingRequestWrapper(request), new ContentCachingResponseWrapper(response));
}
Run Code Online (Sandbox Code Playgroud)

注意:我也包装了回复,以防万一您也想要这样。

现在,在您的异常处理方法中,使用HttpServletRequest作为参数,并利用它来发挥您的优势。

@ExceptionHandler({HttpMessageNotReadableException.class})
public String addStudent(HttpMessageNotReadableException e, HttpServletRequest req) {
  if (req instanceof ContentCachingRequestWrapper) {
    ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) req;
    log.error(new String(wrapper.getContentAsByteArray()));
  }
  return "greeting from @ExceptionHandler";
}
Run Code Online (Sandbox Code Playgroud)

可能是多个过滤器添加了一个包装器,HttpServletRequest因此您可能需要迭代这些包装器,您也可以使用这个

private Optional<ContentCachingRequestWrapper> findWrapper(ServletRequest req) {
    ServletRequest reqToUse = req;
    while (reqToUse instanceof ServletRequestWrapper) {
        if (reqToUse instanceof ContentCachingRequestWrapper) {
            return Optional.of((ContentCachingRequestWrapper) reqToUse);
        }
        reqToUse = ((ServletRequestWrapper) reqToUse).getRequest();
    }
    return Optional.empty();
}
Run Code Online (Sandbox Code Playgroud)

然后你的异常处理程序看起来像这样

@ExceptionHandler({HttpMessageNotReadableException.class})
public String addStudent(HttpMessageNotReadableException e, HttpServletRequest req) {
  Optional<ContentCachingRequestWrapper) wrapper = findWrapper(req);
  wrapper.ifPresent(it -> log.error(new String(it.getContentAsByteArray())));
 
  return "greeting from @ExceptionHandler";
}
Run Code Online (Sandbox Code Playgroud)

但这可能取决于您的过滤器顺序以及是否有多个过滤器添加包装器。


归档时间:

查看次数:

3897 次

最近记录:

3 年,9 月 前