簡體   English   中英

如何在 spring 引導中保存對數據庫的請求和響應

[英]How to save requests and responses to database in spring boot

我想寫一個方面或類似的東西,每當請求到達 controller 時,它都會將請求和響應保存到數據庫。

第一個問題是我應該在我的實體中使用什么類型來請求和響應(字符串、blob 等)

第二個問題,如何獲取請求、響應及其 controller 名稱來創建實體以保存到數據庫?

最后,是否可以計算 controller 的響應時間(在控制器中花費的時間)?

第一個問題是我應該在我的實體中使用什么類型來請求和響應(字符串、blob 等)

它主要取決於數據庫供應商和請求/響應長度。
某些供應商可能會限制字符串,因此需要 blob。 另一方面,在 blob 上的匹配速度較慢。
另一種選擇是使用 nosql 格式,例如 JSON。

第二個問題,如何獲取請求、響應及其 controller 名稱來創建實體以保存到數據庫?

真的有幾種方法。
您可以利用內置 Spring 引導http 跟蹤功能,但它有一個限制:發布/接收的請求/響應不可用。

5.8. HTTP 跟蹤

HTTP 可以通過在應用程序配置中提供 HttpTraceRepository 類型的 bean 來啟用跟蹤。 為方便起見,Spring Boot 默認提供一個 InMemoryHttpTraceRepository 存儲最近 100 次請求-響應交換的跟蹤。 與其他跟蹤解決方案相比,InMemoryHttpTraceRepository 受到限制,我們建議僅將其用於開發環境。 對於生產環境,建議使用生產就緒跟蹤或可觀察性解決方案,例如 Zipkin 或 Spring Cloud Sleuth。 或者,創建您自己的 HttpTraceRepository 以滿足您的需求。

httptrace 端點可用於獲取有關存儲在 HttpTraceRepository 中的請求-響應交換的信息。

5.8.1. 定制 HTTP 跟蹤

要自定義每個跟蹤中包含的項目,請使用 management.trace.http.include 配置屬性。 對於高級自定義,請考慮注冊您自己的 HttpExchangeTracer 實現。

替代方案正在為請求/響應實施過濾器並登錄。
例如:

@Component
public class RequestResponseStoringFilter implements Filter {
    
  @Override
  public void doFilter(ServletRequest req, ServletResponse res,
      FilterChain chain) throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    long start = System.currentTimeMillis();
    try {
        chain.doFilter(req, resp);
    } finally {
       // Measure elapsed time
        long elapsed = System.currentTimeMillis() - start;
       // store request data and store response data in a DB
       .....
    }

  }

  @Override
  public void destroy() {}

  @Override
  public void init(FilterConfig arg0) throws ServletException {}

}

最后,是否可以計算 controller 的響應時間(在控制器中花費的時間)?

實現過濾器的方式可以做到這一點,如上所示。
httptrace 端點方式提供了timeTaken字段。
FIY,這是HttpTrace實例的內容:

HttpTrace.Principal getPrincipal() 
HttpTrace.Request   getRequest() 
HttpTrace.Response  getResponse() 
HttpTrace.Session   getSession() 
Instant getTimestamp() 
Long    getTimeTaken() 

這有點擴展了其他答案,但我認為它需要一個單獨的答案。

如果您引入了 spring-boot-starter-web,那么您已經在引入 spring-aop。 如果你要去 go 沿着切點路線走,我強烈建議只使用spring-boot-starter-actuator附帶的 Micrometer @Timed注釋。 我已經多次編寫了自己的度量切入點,但是如果您只是在計時以及成功和失敗的計數之后, @Timed效果很好。

我還強烈建議研究使用時間序列數據庫(例如influx )來存儲響應時間和其他性能指標等內容。 將原始有效負載和其他可能的審計問題保存在單獨的數據庫中。 通過 influx 並在其上運行GrafanaChronograf ,您可以做一些非常強大的事情。 毫無疑問,我目前的公司所做的最好的事情之一就是多年來采用 Influx/Chronograf。

關於請求/響應捕獲,我的工作流程中有一個奇怪的邊緣情況,曾經 http 跟蹤無法滿足一些硬性要求。 您可以使用ContentCachingRequestWrapper自己直接在鏈過濾器中捕獲內容然后您可以通過以下方式訪問它們:

 @Component
class MyPayloadCapturingFilter extends OncePerRequestFilter {

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

        filterChain.doFilter(requestWrapper, responseWrapper)

        def requestBody = new String(requestWrapper.contentAsByteArray)
        def responseBody = new String(responseWrapper.contentAsByteArray)
        //..do something with them
    }
}

注意OncePerRequestFilter ,我發現我的過濾器多次觸發同一個請求。 這可以防止這種情況。

添加此依賴項:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

然后,為控制器的方法執行創建一個Around方面:

    @Around("within(path.to.your.controller.*)")
    public Object pointcutWithin(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info(" ###### pointcutWithin() before");
        long start = System.nanoTime();

        Object result = joinPoint.proceed();

        logger.info(" ###### pointcutWithin() after");
        long end = System.nanoTime();

        long timeElapsedInMillis = (end - start) / 1000000;
        logger.info(" ###### elapsed time in millis: "+timeElapsedInMillis);

        return result;
    }

至於堅持:首先,像這樣獲取 req 和 resp :

MyRequest req = (MyRequest) joinPoint.getArgs()[0];

MyResponse resp = (MyResponse) result;

那么你真正想要堅持什么取決於你。 對於具有簡單字段的類,我將 go 與varchar一起使用,只需記住覆蓋他們的toString方法。

暫無
暫無

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

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