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}]
我們能看到,由於調用了 RequestCachingFilter 和 CommonsRequestLoggingFilter 類,請求報負載在控制枱打印出來。
7. 結論
在本文中,我們學習瞭如何使用自定義過濾器在 Spring Boot 應用程序中實現基本 Web 請求日誌記錄。 此外,我們還討論了 Spring Boot 內置的過濾器類,它提供了易於使用且簡單的日誌記錄機制。