RestTemplate 로깅 해보기

RestTemplate?

Spring에서는 HTTP 요청이 가능한 HttpClient를 지원한다.

가장 널리 쓰였던 동기식 HTTP 클라이언트인 RestTemplate와 최근 Webflux가 등장하며 함께 등장한 비동기 HTTP 클라이언트인 WebClient가 존재한다.

스프링에선 WebClient를 밀어주고 있는 추세이고 RestTemplateDepreacte될 예정이라고 밝힌 바 있다.

현재 개발 중인 계정 체계 서비스의 경우 외부 API 요청이 빈번하지 않아서 RestTemplate을 사용하고 있다.

그런데 외부 API 요청에 대한 로깅을 남기고 있지 않아서 추적이 어려운 점이 있었다.

계정 체계 서비스는 MSA로 이루어져있는데 이미 다른 서비스에서 개발한 RestTemplate 로깅이 있어서 가져와서 적용하였다.

그러나 그저 copy&paste를 하는 것은 용납할 수 없기에 이해하고자 한다.

RestTemplate으로 로깅해보기

본격적인 로깅을 위해 RestTemplate을 로깅을 어떻게할 수 있는지를 알아보자.

RestTemplate에는 Interceptor를 추가할 수 있다.

이 Interceptor는 ClientHttpRequestInterceptor 인터페이스를 구현하여 만들 수 있다.

public class HttpClientInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] requestBody, ClientHttpRequestExecution execution) throws IOException {

        ClientHttpResponse response = execution.execute(request, requestBody);

        RequestLoggingUtils.setRequest(request, requestBody);
        RequestLoggingUtils.setResponse(response);
        RequestLoggingUtils.flush(null);

        return response;
    }
}

HttpClientInterceptor에서 눈여겨 볼 부분은 byte array 타입의 requestBody이다.

requestBodyStream이므로 소비가 되면 사라지는 (byte array가 비워진다) 특징이 있다.

이렇게 되면 인터셉터에서 이 requestBody를 읽어버리면 Presentation Layer에서 요청의 requestBody를 읽을 수 없게 되기 때문에 문제가 된다.

이를 위해 RestTemplate 설정에서 BufferingClientHttpRequestFactory를 사용해야한다.

BufferingClientHttpRequestFactoryClientHttpRequests를 아래와 같이 래핑해서 사용할 수 있다.

    private RestTemplate newRestTemplate(int connectionTimeout, int readTimeout, Proxy proxy) {
        // 리퀘스트에 대한 설정
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(connectionTimeout);
        requestFactory.setReadTimeout(readTimeout);
        if (proxy != null) {
            requestFactory.setProxy(proxy);
        }

        RestTemplate restTemplate = new RestTemplate();
        // SimpleClientHttpRequestFactory를 래핑해서 body stream이 소진되는 것을 막아준다.
        BufferingClientHttpRequestFactory bufferingClientHttpRequestFactory = new BufferingClientHttpRequestFactory(requestFactory);
        restTemplate.setRequestFactory(bufferingClientHttpRequestFactory);
        restTemplate.setInterceptors(Collections.singletonList(new HttpClientInterceptor()));
        return restTemplate;
    }

BufferingClientHttpRequestFactory의 자세한 역할은 해당 클래스의 주석을 읽어보면 알 수 있다.

/**
 * Wrapper for a {@link ClientHttpRequestFactory} that buffers
 * all outgoing and incoming streams in memory.
 *
 * <p>Using this wrapper allows for multiple reads of the
 * {@linkplain ClientHttpResponse#getBody() response body}.
 *
 * @author Arjen Poutsma
 * @since 3.1
 */
public class BufferingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {

이 래퍼 클래스를 사용하면 ClientHttpResponse의 바디를 여러번 읽을 수 있다는 주석을 확인할 수 있다.

또한 아래는 스택 오버플로우에서 발췌한 글인데 BufferingClientHttpRequestFactorySimpleClientHttpRequestFactory를 아래와 같이 설명하고 있다.

BufferingClientHttpRequestFactory is a decorator around ClientHttpRequestFactory, which the RestTemplate uses to create ClientHttpRequests which faciliate HTTP communication. This decorator in particular provides buffering of outgoing/incoming streams.

SimpleClientHttpRequestFactory is an implementation of ClientHttpRequestFactory, which uses JDK facilities (classes from java.net package) and therefore does not depend on third party libraries, such as Apache HttpComponents HTTP client, which is required by another implementation HttpComponentsClientHttpRequestFactory.

한글로 번역해보자면 아래와 같다. (영어를 잘 못해서 틀릴 수 있음 주의)

BufferingClientHttpRequestFactory는 ClientHttpRequestFactory의 데코레이터로써 레스트 템플릿이 HTTP 요청을 용이하게 하기 위한 ClientHttpRequests를 만드는게 사용한다.
이 데코레이터는 outgoing/incoming stream의 버퍼링을 제공한다.

SimpleClientHttpRequestFactory는 ClientHttpRequestFactory를 구현한 클래스이며 JDK 기능을 원할하게 사용하기 위한 용도로 사용된다.
그러므로 서드파티 라이브러리에 (예를 들어 아파치 HttpComponents HTTP client같은) 종속적이지 않게 된다.

출처

여담

개인적으로 백엔드 서비스를 개발하면서 반드시 필요한 세 가지 기능을 나열해보자면 다음과 같다.

로깅, 모니터링, 알람

큰 서비스에서 로깅과 모니터링 그리고 알람 기능이 없으면 서버에 이상이 생기거나 문제가 발생했을 때 추적하는게 어렵다.

그래서 백엔드에서는 위의 세 가지 기능이 잘 구축되어있어야 이슈에 개발자들이 기민하게 대응할 수 있다.



© 2022. by minkuk

Powered by minkuk