如何在一個地方記錄所有請求、回應和異常
1. 簡介
日誌記錄在建立 Web 應用程式中起著至關重要的作用。它可以實現高效的調試、效能監控和錯誤追蹤。然而,以乾淨、有組織的方式實現日誌記錄,特別是以集中的方式捕獲每個請求、回應和異常時,是一個常見的挑戰。
在本教程中,我們將在 Spring Boot 應用程式中實作集中式日誌記錄。我們將提供詳細的逐步指南,涵蓋所有必要的配置,並透過實際的程式碼範例來示範流程。
2. Maven 依賴項
首先,確保我們的pom.xml
中有必要的依賴項。我們需要Spring Web和可選的Spring Boot Actuator來實現更好的監控:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>3.4.1</version>
</dependency>
一旦設定了依賴關係,我們就可以實現日誌記錄邏輯了。
3. 使用 Spring Boot Actuator 進行請求日誌記錄
在建立自訂邏輯之前,請考慮使用 Spring Boot Actuator,它可以開箱即用地記錄 HTTP 請求。 Actuator 模組包含一個端點/actuator/httpexchanges
(適用於 Spring Boot 2.0+),它顯示對應用程式發出的最後 100 個 HTTP 請求。除了新增spring-boot-starter-actuator
依賴項之外,我們還將配置應用程式屬性以公開httpexchanges
端點:
management:
endpoints:
web:
exposure:
include: httpexchanges
我們還將添加一個記憶體存儲庫來儲存追蹤資料。這使得我們能夠臨時儲存追蹤資料而不影響主要應用程式邏輯:
@Configuration
public class HttpTraceActuatorConfiguration {
@Bean
public InMemoryHttpExchangeRepository createTraceRepository() {
return new InMemoryHttpExchangeRepository();
}
}
現在我們可以運行我們的應用程式並訪問/actuator/httpexchanges
來查看記錄:
**4.**建立自訂日誌過濾器
建立自訂日誌過濾器使我們能夠根據需要自訂流程。雖然 Spring Boot Actuator 提供了一種方便的方式來記錄 HTTP 請求和回應,但它可能無法涵蓋所有詳細或自訂日誌記錄要求的用例。自訂過濾器可讓我們記錄其他詳細資訊、以特定方式格式化日誌或將日誌與其他監控工具整合。此外,記錄 Actuator 等工具預設未捕獲的敏感資料也很有用。例如,我們可以以任何格式記錄請求標頭、正文內容和回應詳細資訊。
**4.1.**實作自訂過濾器
此過濾器將成為所有傳入 HTTP 請求和傳出 HTTP 回應的集中攔截器。透過實現Filter
接口,我們可以記錄通過應用程式的每個請求和回應的詳細信息,使調試和監控更加高效:
@Override
public void doFilter(jakarta.servlet.ServletRequest request, jakarta.servlet.ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
logRequest(httpRequest);
ResponseWrapper responseWrapper = new ResponseWrapper(httpResponse);
chain.doFilter(request, responseWrapper);
logResponse(httpRequest, responseWrapper);
} else {
chain.doFilter(request, response);
}
}
在我們的自訂篩選器中,我們使用兩種附加方法來記錄請求和回應:
private void logRequest(HttpServletRequest request) {
logger.info("Incoming Request: [{}] {}", request.getMethod(), request.getRequestURI());
request.getHeaderNames().asIterator().forEachRemaining(header ->
logger.info("Header: {} = {}", header, request.getHeader(header))
);
}
private void logResponse(HttpServletRequest request, ResponseWrapper responseWrapper) throws IOException {
logger.info("Outgoing Response for [{}] {}: Status = {}",
request.getMethod(), request.getRequestURI(), responseWrapper.getStatus());
logger.info("Response Body: {}", responseWrapper.getBodyAsString());
}
**4.2.**自訂響應包裝器
我們將實作一個自訂的ResponseWrapper
,它允許我們在基於 servlet 的 Web 應用程式中捕獲和操作 HTTP 回應的回應主體。這個包裝器很方便,因為預設的HttpServletResponse
在寫入回應主體後不提供直接存取。透過攔截並儲存回應內容,我們可以在將其發送給客戶端之前記錄或修改它:
public class ResponseWrapper extends HttpServletResponseWrapper {
private final CharArrayWriter charArrayWriter = new CharArrayWriter();
private final PrintWriter writer = new PrintWriter(charArrayWriter);
public ResponseWrapper(HttpServletResponse response) {
super(response);
}
@Override
public PrintWriter getWriter() {
return writer;
}
public String getBodyAsString() {
return charArrayWriter.toString();
}
}
**4.3.**全域處理異常
Spring Boot 透過@ControllerAdvice
註解提供了一種管理異常的便捷方法,該註解定義了一個全域異常處理程序。此處理程序將捕獲請求處理期間發生的任何異常並記錄有關它的有用資訊:
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
logger.error("Exception caught: {}", ex.getMessage(), ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred");
}
}
我們也使用了ExceptionHandler
註釋。此批註指定方法將處理特定類型的異常,在本例中為Exception.class
。這意味著該處理程序將捕獲所有異常(除非它們在應用程式的其他地方處理)。我們捕獲所有異常,記錄它們,並向客戶端傳回通用錯誤回應。透過記錄堆疊追蹤,我們可以確保不會遺漏任何細節。
**5.**測試實施
為了測試日誌設置,我們可以建立一個簡單的 REST 控制器:
@RestController
@RequestMapping("/api")
public class TestController {
@GetMapping("/hello")
public String hello() {
return "Hello, World!";
}
@GetMapping("/error")
public String error() {
throw new RuntimeException("This is a test exception");
}
}
存取/api/hello
將記錄請求和回應:
INFO 19561 --- [log-all-requests] [nio-8080-exec-3] c.baeldung.logallrequests.LoggingFilter : Incoming Request: [GET] /api/hello
NFO 19561 --- [log-all-requests] [nio-8080-exec-3] c.baeldung.logallrequests.LoggingFilter : Header: host = localhost:8080
INFO 19561 --- [log-all-requests] [nio-8080-exec-3] c.baeldung.logallrequests.LoggingFilter : Header: connection = keep-alive
…
INFO 19561 --- [log-all-requests] [nio-8080-exec-3] c.baeldung.logallrequests.LoggingFilter : Outgoing Response for [GET] /api/hello: Status = 200
INFO 19561 --- [log-all-requests] [nio-8080-exec-3] c.baeldung.logallrequests.LoggingFilter : Response Body:
存取/api/error will
觸發異常,並將其記錄在流程中:
INFO 19561 --- [log-all-requests] [nio-8080-exec-7] c.baeldung.logallrequests.LoggingFilter : Outgoing Response for [GET] /api/error: Status = 500
6. 結論
在本文中,我們成功實現了請求、回應和異常的集中日誌記錄機制。透過利用 Spring Boot Actuator 或使用Filter
和ControllerAdvice
建立自訂日誌邏輯,我們確保我們的應用程式保持乾淨且可維護。
這有助於我們監控我們的應用程序,並使我們能夠在問題出現時快速解決問題。與往常一樣,完整的源代碼可在 GitHub 上取得。