簡體   English   中英

在 RestController ExceptionHandler 中記錄請求正文字符串

[英]log request body string in RestController ExceptionHandler

  • 最終目標:

在 RestController 的 @ExceptionHandler 中記錄請求正文字符串

  • 解釋

默認情況下,當請求無效 json 時,springboot 會拋出一個HttpMessageNotReadableException ,但消息很籠統,不包括具體的請求正文。 這使得調查變得困難。 另一方面,我可以使用過濾器記錄每個請求字符串,但是這樣日志將被太多成功的日志淹沒。 我只想在請求無效時記錄它。 我真正想要的是在@ExceptionHandler中我會得到那個字符串(以前在某個地方)並記錄為錯誤。

為了說明問題,我在 github 中創建了一個演示項目

  • controller:
@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.IllegalStateException: 在 getReader() 已經為當前請求調用后無法調用 getInputStream()'。

經過一番搜索1 2 ,我決定將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:缺少所需的請求正文:公共 java.lang.String com.example.demo.GreetController.example.demo.com。

我還嘗試了getContentAsByteArraygetInputStreamgetReader方法,因為一些教程說框架會檢查特定的方法調用。

CommonsRequestLoggingFilter的建議嘗試了 CommonsRequestLoggingFilter。 迪南。

但一切都是徒勞的。

現在我有點困惑。 當請求有效和無效時,誰能解釋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";
  }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM