Spring WebClient로 요청 본문 로깅하기

스프링 웹클라이언트에 대하여



Spring은 Reactive로 가고 있습니다. Spring One 2020 컨퍼런스에서 소개된 강연을 한 번만 보면 Java 및 Spring의 Reactive Web 및 함수형 프로그래밍 패러다임이 최전선에 있다는 것을 알 수 있습니다.

비차단 방식으로 작업을 수행할 때의 흥미로운 문제 중 하나는 로깅과 같은 간단한 작업이 때때로 조금 더 복잡해질 수 있다는 것입니다. 데이터를 언제 사용할 수 있는지 정확히 모르기 때문에 Spring의 RestTemplate과 같은 방식으로 데이터를 로그에 던질 수는 없습니다. (RestTemplate 이라고 하면 deprecation 대상인 것 같습니다! https://docs.spring.io/spring-framework/docs/current/javadoc-api/index.html?org/springframework/web/client/RestTemplate.html )

문서에 따르면, 특히 차단 및 비차단 방법을 활용할 수 있는 기능을 제공하기 때문에 오늘날 아웃바운드 API 호출에 org.springframework.web.reactive.client.WebClient를 사용해야 합니다.

이제 Spring WebClient를 사용해 본 사람이라면 요청 내용이나 응답 내용을 검색하는 것이 때때로 약간 어려울 수 있다는 사실을 증명할 수 있습니다. 특히 특정 형식을 찾고 있는 경우에 그렇습니다.

답변이 없는 수십 개의 스택 오버플로 게시물이 있으며 동일한 응답을 가지고 있습니다. 주제에 대한 Baeldung의 기사를 확인하세요. https://www.baeldung.com/spring-log-webclient-calls .

이제 Baeldung은 내가 셀 수 있는 것보다 더 많은 시간을 사이드 프로젝트에서 그리고 전문적으로 저장했습니다. 그러나 이 기사에서는 기본적인 구현 예 이상을 보여주지 않습니다. 누락된 것은 예제 출력과 기사에서 언급되지 않은 주의 사항을 공유하는 것입니다.

그래서 더 이상 고민하지 않고 여기에 예제, 주석 및 출력과 함께 Spring Webclient에서 요청 및 응답 로깅(HTTP 본문 포함)을 수행하는 가장 좋은 방법(제 생각에는)을 살펴보겠습니다.

Netty 로깅은 Baeldung의 게시물에 포함되어 있지만 Jetty HTTP 클라이언트만큼 세분화되지는 않습니다. 첫 번째 단계는 기본 HTTP 클라이언트에 대한 액세스를 제공하는 필수 종속성을 추가하는 것입니다.

코드



# Gradle    
implementation group: 'org.eclipse.jetty', name: 'jetty-reactive-httpclient', version: '1.1.4'

# Maven
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-reactive-httpclient</artifactId>
    <version>1.1.4</version>
</dependency>

필요한 클래스에 액세스할 수 있게 되면 빌드해야 하는 두 가지 구성 요소가 있습니다.

첫 번째는 강화 방법입니다. 이 메서드는 요청을 받고 다시 요청을 제공하여 우리가 관심 있는 모든 부분을 가로채고 기록할 수 있도록 합니다.

다음은 향상 방법의 예이며 결과는 다음과 같습니다.

// org.eclipse.jetty.client.api.Request
private Request enhance(Request inboundRequest) {
    StringBuilder log = new StringBuilder();
    // Request Logging
    inboundRequest.onRequestBegin(request ->
            log.append("Request: \n")
            .append("URI: ")
            .append(request.getURI())
            .append("\n")
            .append("Method: ")
            .append(request.getMethod()));
    inboundRequest.onRequestHeaders(request -> {
        log.append("\nHeaders:\n");
        for (HttpField header : request.getHeaders()) {
            log.append("\t\t" + header.getName() + " : " + header.getValue() + "\n");
        }
    });
    inboundRequest.onRequestContent((request, content) ->
            log.append("Body: \n\t")
            .append(content.toString()));
    log.append("\n");

    // Response Logging
    inboundRequest.onResponseBegin(response ->
            log.append("Response:\n")
            .append("Status: ")
            .append(response.getStatus())
            .append("\n"));
    inboundRequest.onResponseHeaders(response -> {
       log.append("Headers:\n");
       for (HttpField header : response.getHeaders()) {
           log.append("\t\t" + header.getName() + " : " + header.getValue() + "\n");
       }
    });
    inboundRequest.onResponseContent(((response, content) -> {
        var bufferAsString = StandardCharsets.UTF_8.decode(content).toString();
        log.append("Response Body:\n" + bufferAsString);
    }));

    // Add actual log invocation
    logger.info("HTTP ->\n");
    inboundRequest.onRequestSuccess(request -> logger.info(log.toString()));
    inboundRequest.onResponseSuccess(response -> logger.info(log.toString()));

    // Return original request
    return inboundRequest;
}

요청 개체는 기록하려는 데이터에 도달하고 잡을 수 있는 많은 후크를 제공합니다. 인터페이스 문서는 여기 -> https://www.eclipse.org/jetty/javadoc/9.4.8.v20171121/org/eclipse/jetty/client/api/Request.html

WebClient를 호출하는 동안 강화 메서드를 실행하기 위해 자체 HttpClient를 만들고 기본 JettyClientHttpConnector 대신 사용할 것입니다. 다음은 WebClient를 제공하는 예제 빈입니다.

@Bean
public WebClient jettyHttpClient() {
    SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
    HttpClient httpClient = new HttpClient(sslContextFactory) {
        @Override
        public Request newRequest(URI uri) {
            Request request = super.newRequest(uri);
            return enhance(request);
        }
    };
    return WebClient.builder().clientConnector(new JettyClientHttpConnector(httpClient)).build();
}

출력



이제 기본 HttpClient로 시드한 WebClient를 사용하여 다음 출력을 얻습니다.
2020-10-08 15:00:00.000  INFO 2100 --- [   @cafebabe-37] 
c.s.l.examples.JettyWebClient            : 
Request: 
URI: http://httpbin.org/get
Method: GET
Headers:
        Accept-Encoding : gzip
        User-Agent : Jetty/9.4.31.v20200723
        Accept : */*
        Host : httpbin.org
Response:
Status: 200
Headers:
        Date : Thu, 08 Oct 2020 20:24:17 GMT
        Content-Type : application/json
        Content-Length : 297
        Connection : keep-alive
        Server : gunicorn/19.9.0
        Access-Control-Allow-Origin : *
        Access-Control-Allow-Credentials : true
Response Body:
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip", 
    "Host": "httpbin.org", 
    "User-Agent": "Jetty/9.4.31.v20200723", 
    "X-Amzn-Trace-Id": "Root=1-5f7f7571-    157328ac70a3bd900bc1c8bc"
  }, 
  "origin": "12.345.678.91", 
  "url": "http://httpbin.org/get"
}

Netty 접근 방식보다 이 접근 방식을 사용하는 이점 중 하나는 이것이 표시되기 위해 로그를 변경할 필요가 없다는 것입니다. 또한 필요한 경우 각 요청의 데이터를 저장할 수 있습니다.

이 게시물의 목표는 요청 및 응답 데이터를 가장 세부적으로 제어할 수 있는 구현을 이해하는 것이었습니다.

위의 코드는 다음 저장소에 있는 전체 작동 애플리케이션에서 사용할 수 있습니다. https://github.com/StevenPG/logging-with-webclient

좋은 웹페이지 즐겨찾기