知識庫 / Spring / Spring Boot RSS 訂閱

集中記錄所有請求、響應和異常

Logging,Spring Boot
HongKong
5
10:51 AM · Dec 06 ,2025

1. 簡介

日誌記錄在構建 Web 應用程序中起着至關重要的作用。它能夠實現高效的調試、性能監控和錯誤追蹤。然而,以一種乾淨、有條理的方式實施日誌記錄,尤其是在集中捕獲每個請求、響應和異常時,是一個常見的挑戰。

在本教程中,我們將在一 個 Spring Boot 應用程序中實現集中式日誌記錄。 我們將提供一份詳細的、分步的指南,涵蓋所有必要的配置,並使用實際的代碼示例演示整個過程。

2. Maven 依賴

首先,請確保我們在 <em pom.xml</em> 中包含了必要的依賴項。我們需要 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 響應的集中式攔截器。通過實現 <em >Filter</em> 接口,我們可以記錄通過應用程序的每個請求和響應的詳細信息,從而提高調試和監控的效率:

@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 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    private final PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream));

    public ResponseWrapper(HttpServletResponse response) {
        super(response);
    }

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

            @Override
            public void setWriteListener(WriteListener writeListener) {
            }

            @Override
            public void write(int b) {
                outputStream.write(b);
            }
        };
    }

    @Override
    public PrintWriter getWriter() {
        return writer;
    }

    @Override
    public void flushBuffer() throws IOException {
        super.flushBuffer();
        writer.flush();
    }

    public String getBodyAsString() {
        writer.flush();
        return outputStream.toString();
    }
}

4.3. 全局處理異常

Spring Boot 提供了一種便捷的方式通過 <em @ControllerAdvice</em> 註解來管理異常,該註解定義了一個全局異常處理器。該處理器將捕獲請求處理過程中發生的任何異常,並記錄有關該異常的有用信息。

@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: Hello, World!

訪問 /api/error 將會觸發異常,並在過程中進行日誌記錄:

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 或使用 FilterControllerAdvice 創建自定義日誌邏輯,我們確保了應用程序保持清潔和可維護。

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

發佈 評論

Some HTML is okay.