知識庫 / Spring / Spring MVC RSS 訂閱

Spring – 記錄傳入請求

Logging,Spring MVC
HongKong
6
02:25 PM · Dec 06 ,2025

1. 簡介

有時我們需要進行額外的處理,例如在 HTTP 請求報文中進行日誌記錄記錄傳入的 HTTP 請求對於調試應用程序非常有幫助

在本快速教程中,我們將學習使用 Spring Boot 的日誌過濾器記錄傳入請求的基礎知識。

2. Maven 依賴

讓我們首先將 <a href="https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web"><em>spring-boot-starter-web</em></a > 依賴添加到我們的pom.xml 文件中:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.2</version>
</dependency>

此依賴項提供了所有核心要求,用於在 Spring Boot 應用程序中記錄傳入的請求。

3. 基本 Web 控制器

首先,我們將定義一個用於我們示例中的控制器:

@RestController
public class TaxiFareController {

    @GetMapping("/taxifare/get/")
    public RateCard getTaxiFare() {
        return new RateCard();
    }
    
    @PostMapping("/taxifare/calculate/")
    public String calculateTaxiFare(
      @RequestBody @Valid TaxiRide taxiRide) {
 
        // return the calculated fare
    }
}

在下一部分,我們將看到如何將傳入的請求記錄到 Spring Boot 應用程序中。

4. 使用自定義請求日誌記錄

我們希望使用自定義 Filter 來捕獲請求報文負載,在控制器接收請求之前

4.1. 封裝 HTTP 請求

我們需要封裝 HTTP 請求並記錄其 payload。 HttpServletRequestWrapper 類提供封裝和修改傳入的 HttpServletRequest 對象的功能。

首先,讓我們創建一個繼承 HttpServletRequestWrapper 類的 CachedHttpServletRequest 類:

public class CachedHttpServletRequest extends HttpServletRequestWrapper {

    private byte[] cachedPayload;

    public CachedHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        InputStream requestInputStream = request.getInputStream();
        this.cachedPayload = StreamUtils.copyToByteArray(requestInputStream);
    }
}

在上述代碼中,首先,我們在包裝器構造函數內部讀取實際的請求體,並將其存儲在 cachedPayload字節數組中

然後,我們覆蓋了 getInputStream()getReader()方法:

@Override
public ServletInputStream getInputStream() {
    return new CachedServletInputStream(this.cachedPayload);
}

@Override
public BufferedReader getReader() {
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedPayload);
    return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}

<em>getInputStream()</em> 方法中,我們返回新的擴展 <em>ServletInputStream</em> 對象,其中包含 <em>cachedPayload</em>。 此外,我們還重寫了 <em>getReader()</em> 方法。 此方法返回一個 <em>BufferedReader</em> 對象,可用於以字符數據讀取請求體。

4.2. 擴展 ServletInputStream

正如我們所提到的,我們需要擴展 ServletInputStream 類以創建我們的包裝器。 讓我們創建一個 CachedServletInputStream 類:

public class CachedServletInputStream extends ServletInputStream {

    private final static Logger LOGGER = LoggerFactory.getLogger(CachedServletInputStream.class);
    private InputStream cachedInputStream;

    public CachedServletInputStream(byte[] cachedBody) {
        this.cachedInputStream = new ByteArrayInputStream(cachedBody);
    }
}

在本類中,首先我們創建一個新的構造函數,其中包含 cachedBody,並返回一個從該構造函數創建的新的 ByteArrayInputStream 對象。 我們使用此 InputStream 對象在其他重寫的方法中。

然後,我們重寫 isFinished()isReady()setReadListener()read() 方法:

@Override
public boolean isFinished() {
    try {
        return cachedInputStream.available() == 0;
    } catch (IOException exp) {
        LOGGER.error(exp.getMessage());
    }
    return false;
}

@Override
public boolean isReady() {
    return true;
}

@Override
public void setReadListener(ReadListener readListener) {
    throw new UnsupportedOperationException();
}

@Override
public int read() throws IOException {
    return cachedInputStream.read();
}

isReady() 方法用於檢查數據是否可以不阻塞地讀取,然後讀取數據。read() 方法從 cachedInputStream 對象中讀取數據。isFinished() 方法在流中已讀取所有數據時返回 true,否則返回 false

4.3. 自定義過濾器

隨後,我們將創建一個擴展 OncePerRequestFilter 類並重寫 doFilterInternal() 方法的過濾器類。 在 doFilterInternal() 方法中,我們使用自定義請求包裝器獲取請求負載並進行日誌記錄。

讓我們創建一個 RequestCachingFilter 類:

@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
@WebFilter(filterName = "RequestCachingFilter", urlPatterns = "/*")
public class RequestCachingFilter extends OncePerRequestFilter {

    private final static Logger LOGGER = LoggerFactory.getLogger(RequestCachingFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
      FilterChain filterChain) throws ServletException, IOException {
        CachedHttpServletRequest cachedHttpServletRequest = new CachedHttpServletRequest(request);
        LOGGER.info("REQUEST DATA: " + IOUtils.toString(cachedHttpServletRequest.getInputStream(), StandardCharsets.UTF_8));
        filterChain.doFilter(cachedHttpServletRequest, response);
    }
}

此外,過濾器映射到應用程序的所有請求,URL映射為 /*

在下一部分,我們將使用 Spring Boot 內置的請求日誌記錄解決方案。

5. 使用 Spring Boot 內置請求日誌記錄

Spring Boot 提供了一種內置的解決方案來記錄請求負載。通過配置,我們可以使用現成的過濾器,將其集成到 Spring Boot 應用程序中。

AbstractRequestLoggingFilter 是一個提供基本日誌記錄功能的過濾器。子類應覆蓋 beforeRequest()afterRequest() 方法,以在請求周圍執行實際的日誌記錄。

Spring Boot 框架提供了三個具體的實現類,我們可以使用它們來記錄傳入的請求:

  • CommonsRequestLoggingFilter
  • Log4jNestedDiagnosticContextFilter (已棄用)
  • ServletContextRequestLoggingFilter

現在,讓我們轉向 CommonsRequestLoggingFilter,並配置它以捕獲傳入的請求以進行日誌記錄。

5.1. 配置 Spring Boot 應用

我們可以通過嚮應用添加 Bean 定義來啓用請求日誌記錄來配置 Spring Boot 應用:

@Configuration
public class RequestLoggingFilterConfig {

    @Bean
    public CommonsRequestLoggingFilter logFilter() {
        CommonsRequestLoggingFilter filter
          = new CommonsRequestLoggingFilter();
        filter.setIncludeQueryString(true);
        filter.setIncludePayload(true);
        filter.setMaxPayloadLength(10000);
        filter.setIncludeHeaders(false);
        filter.setAfterMessagePrefix("REQUEST DATA: ");
        return filter;
    }
}

5.2. 配置日誌級別

此日誌過濾器也要求我們設置日誌級別為 DEBUG。可以通過在 logback.xml 中添加以下元素來啓用 DEBUG 模式:

<logger name="org.springframework.web.filter.CommonsRequestLoggingFilter">
    <level value="DEBUG" />
</logger>

另一種啓用 DEBUG 級別的日誌記錄方式是在 application.properties 中添加以下內容:

logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG

現在,該應用程序已準備好進行測試。

6. 示例演示

最後,讓我們編寫一個測試,以查看如何記錄傳入的請求:

@Test
public void givenRequest_whenFetchTaxiFareRateCard_thanOK() {
    TestRestTemplate testRestTemplate = new TestRestTemplate();
    TaxiRide taxiRide = new TaxiRide(true, 10l);
    String fare = testRestTemplate.postForObject(
      URL + "calculate/", 
      taxiRide, String.class);
 
    assertThat(fare, equalTo("200"));
}

當我們執行測試用例時,會在控制枱中看到以下輸出:

18:24:04.318 [] INFO  c.b.web.log.app.RequestCachingFilter - REQUEST DATA: {"isNightSurcharge":true,"distanceInMile":10}
18:24:04.321 [] DEBUG o.s.w.f.CommonsRequestLoggingFilter - Before request [POST /spring-rest/taxifare/calculate/]
18:24:04.429 [] DEBUG o.s.w.f.CommonsRequestLoggingFilter - REQUEST DATA: POST /spring-rest/taxifare/calculate/, payload={"isNightSurcharge":true,"distanceInMile":10}]

我們能看到,由於調用了 RequestCachingFilterCommonsRequestLoggingFilter 類,請求報負載在控制枱打印出來。

7. 結論

在本文中,我們學習瞭如何使用自定義過濾器在 Spring Boot 應用程序中實現基本 Web 請求日誌記錄。 此外,我們還討論了 Spring Boot 內置的過濾器類,它提供了易於使用且簡單的日誌記錄機制。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.