RestController ExceptionHandler中的日志请求正文字符串


  • 最终目标:

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

  • 解释

默认情况下,当请求是无效的json时,springboot会抛出一个HttpMessageNotReadableException,但消息是非常通用的,不包括特定的请求体。这使得调查变得困难。另一方面,我可以使用过滤器记录每个请求字符串,但这样日志中会充斥着太多成功的字符串。我只想在请求无效时记录它。我真正想要的是在@ExceptionHandler中,我会得到那个字符串(以前在某个地方得到的(并记录为ERROR。

为了说明这个问题,我在github中创建了一个演示项目。

  • 控制器:
@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";
}
}
  • 客户端
    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.IollegalStateException:在之后无法调用getInputStream((已经为当前请求"调用了getReader((。

经过一番搜索12,我决定将FilterContentCachingRequestWrapper一起使用。

@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);
}

除了日志位于RestController之后之外,此代码运行良好。如果我更改订单:

String requestBody = IOUtils.toString(cachedRequest.getReader());
log.info(requestBody);
chain.doFilter(cachedRequest, response);

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

com.example.demo.GreetController:所需的请求主体为缺少:public java.lang.Stringcom.example.demo.GreetController.greet(com.example.deom.WellomeMessage(

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

按照@M的建议尝试CommonsRequestLoggingFilter。Deinum。

但这一切都是徒劳的。

现在我有点困惑。当请求有效和无效时,有人能解释RestControllerFilter的执行顺序吗?有没有更简单的方法(更少的代码(来实现我的最终目标?谢谢

我使用的是springboot 2.6.3,jdk11。

  1. 创建一个过滤器,将您的请求封装在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));
}

注意:我也封装了响应,以防您也想要。

现在,在您的异常处理方法中,使用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";
}

可能是多个过滤器向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();
}

然后,您的异常处理程序将类似于以下

@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";
}

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

根据@M.Deinum的评论,我解决了问题,并希望对其他人有用:

  1. 添加筛选器
public class MyFilter implements Filter {
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);
}
}
  1. ExceptionHandler中注入ContentCachingRequestWrapper
@ExceptionHandler({ HttpMessageNotReadableException.class })
public String addStudent(HttpMessageNotReadableException e, ContentCachingRequestWrapper cachedRequest) {
log.error(e.getMessage());
try {
String requestBody = IOUtils.toString(cachedRequest.getContentAsByteArray(), cachedRequest.getCharacterEncoding());
log.error(requestBody);
} catch (IOException ex) {
ex.printStackTrace();
}
return "greeting from @ExceptionHandler";
}

最新更新