在 RestController ExceptionHandler 中记录请求正文字符串
log request body string in RestController ExceptionHandler
- 最终目标:
在 RestController 的 @ExceptionHandler.
中记录请求正文字符串
- 解释
默认情况下,当请求无效时json,springboot 会抛出一个HttpMessageNotReadableException
,但是消息非常笼统,不包括具体的请求体。这使得调查变得困难。另一方面,我可以使用过滤器记录每个请求字符串,但这样日志会被太多成功的字符串淹没。我只想在无效时记录请求。我真正想要的是 @ExceptionHandler
我会得到那个字符串(以前在某处得到)并记录为错误。
为了说明问题,我在github中创建了一个demo project。
- 控制器:
@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";
}
}
- 客户端
有效请求
curl -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message":"nice to meet you!"}'
请求无效(无效json)
curl -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message""nice to meet you!"}'
我曾经尝试过 HandlerInterceptor
但会出现一些错误,例如
'java.lang.IllegalStateException: Cannot call getInputStream() after
getReader() has already been called for the current request'.
经过一番搜索 ,我决定将 Filter
与 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);
}
除了日志在 RestController
之后,此代码运行良好。如果我改变顺序:
String requestBody = IOUtils.toString(cachedRequest.getReader());
log.info(requestBody);
chain.doFilter(cachedRequest, response);
适用于无效请求,但当请求有效时,出现以下异常:
com.example.demo.GreetController : Required request body is
missing: public java.lang.String
com.example.demo.GreetController.greet(com.example.demo.WelcomeMessage)
我也尝试了 getContentAsByteArray
、getInputStream
和 getReader
方法,因为一些 tutorials 说框架检查特定方法调用。
按照@M 的建议尝试了CommonsRequestLoggingFilter
。 Deinum.
但一切都是徒劳。
现在我有点困惑。谁能解释一下 RestController
和 Filter
在请求有效和无效时的执行顺序?
有没有更简单的方法(更少的代码)来实现我的最终目标?谢谢!
我用的是springboot 2.6.3,jdk11.
根据@M.Deinum的评论,我解决了,希望对其他人有用:
- 添加过滤器
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);
}
}
- 在
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";
}
创建一个过滤器,将您的请求包装在 ContentCachingRequestWrapper
中(仅此而已)。
在你的异常处理方法中使用HttpServletRequest
作为参数作为参数
检查 ContentCachingRequestWrapper
的实例
使用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";
}
但这可能取决于您的过滤器顺序以及是否有多个过滤器添加包装器。
- 最终目标:
在 RestController 的 @ExceptionHandler.
中记录请求正文字符串- 解释
默认情况下,当请求无效时json,springboot 会抛出一个HttpMessageNotReadableException
,但是消息非常笼统,不包括具体的请求体。这使得调查变得困难。另一方面,我可以使用过滤器记录每个请求字符串,但这样日志会被太多成功的字符串淹没。我只想在无效时记录请求。我真正想要的是 @ExceptionHandler
我会得到那个字符串(以前在某处得到)并记录为错误。
为了说明问题,我在github中创建了一个demo project。
- 控制器:
@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";
}
}
- 客户端
有效请求
curl -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message":"nice to meet you!"}'
请求无效(无效json)
curl -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message""nice to meet you!"}'
我曾经尝试过 HandlerInterceptor
但会出现一些错误,例如
'java.lang.IllegalStateException: Cannot call getInputStream() after getReader() has already been called for the current request'.
经过一番搜索 Filter
与 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);
}
除了日志在 RestController
之后,此代码运行良好。如果我改变顺序:
String requestBody = IOUtils.toString(cachedRequest.getReader());
log.info(requestBody);
chain.doFilter(cachedRequest, response);
适用于无效请求,但当请求有效时,出现以下异常:
com.example.demo.GreetController : Required request body is missing: public java.lang.String com.example.demo.GreetController.greet(com.example.demo.WelcomeMessage)
我也尝试了 getContentAsByteArray
、getInputStream
和 getReader
方法,因为一些 tutorials 说框架检查特定方法调用。
按照@M 的建议尝试了CommonsRequestLoggingFilter
。 Deinum.
但一切都是徒劳。
现在我有点困惑。谁能解释一下 RestController
和 Filter
在请求有效和无效时的执行顺序?
有没有更简单的方法(更少的代码)来实现我的最终目标?谢谢!
我用的是springboot 2.6.3,jdk11.
根据@M.Deinum的评论,我解决了,希望对其他人有用:
- 添加过滤器
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);
}
}
- 在
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";
}
创建一个过滤器,将您的请求包装在
ContentCachingRequestWrapper
中(仅此而已)。在你的异常处理方法中使用
HttpServletRequest
作为参数作为参数检查
的实例ContentCachingRequestWrapper
使用
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";
}
但这可能取决于您的过滤器顺序以及是否有多个过滤器添加包装器。