Programming/Spring Boot

RestTemplate Logging 요청과 응답 로그 남기기

Jan92 2021. 12. 23. 00:03

'RestTemplate 요청과 응답 로그 남기기 (Logging)'

 

RestTemplate

 

먼저 RestTemplate에 대한 간략한 설명,

 

RestTemplate은 스프링 3.0부터 제공하는 HTTP 통신에 유용하게 쓸 수 있는 템플릿입니다.  HTTP 서버와의 통신을 단순화하고 RESTful 원칙을 지킵니다.

'org.springframework.http.client' 패키지에 있으며, HttpClient는 HTTP를 사용하여 통신하는 범용 라이브러리이고, RestTemplate은 HttpClient를 추상화(HttpEntity의 JSON, XML 등 변환)해서 사용하기 쉽게 제공해줍니다.

 

@Bean
public RestTemplate restTemplate() {
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
    factory.setReadTimeout(0);
    factory.setConnectTimeout(0);
    HttpClient httpClient = HttpClientBuilder.create()
            .setMaxConnTotal(120)
            .setMaxConnPerRoute(60)
            .setDefaultRequestConfig(requestConfig)
            .build();
    factory.setHttpClient(httpClient);     
    return new RestTemplate(factory);
}

(기본적으로 사용되는 RestTemplate입니다.)

 

 


 

 

RestTemplate Logging

 

Spring Web RestTemplate을 이용한 HTTP 통신에서 Interceptor를 활용하여 요청과 응답 데이터를 log로 남기는 방법입니다.

이 방법에서 주의해야 할 점은 ResponseEntity의 Body는 한번 사용되면 소멸되는 Stream 이기 때문에 로깅 인터셉터에서 Body Stream을 읽어 소비가 되면 실제 비즈니스 로직에서는 Body를 받아올 수 없는 문제점이 생기는 것인데요.

이러한 문제를 해결하기 위해서는 아래와 같이 RestTemplate Bean 설정에서 requestFactory에 BufferingClientHttpRequestFactory를 세팅해줘야 합니다.

 

  private final RestTemplateLoggingRequestInterceptor restTemplateLoggingRequestInterceptor;

  @Bean
  public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {

    // Apache HttpComponents
    HttpClient httpClient = HttpClientBuilder.create()
        .setMaxConnTotal(50) //최대 커넥션 수
        .setMaxConnPerRoute(20)
        .build(); //각 호스트(IP와 Port 의 조합)당 커넥션 풀에 생성가능한 커넥션 수

    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
    factory.setHttpClient(httpClient);

    return restTemplateBuilder
        //로깅 인터셉터에서 Stream 소비 하기 때문에 BufferingClientHttpRequestFactory 사용
        .requestFactory(() -> new BufferingClientHttpRequestFactory(factory))
        .setReadTimeout(Duration.ofSeconds(5)) // read timeout
        .setConnectTimeout(Duration.ofSeconds(3)) // connection timeout
        .additionalMessageConverters(new StringHttpMessageConverter(StandardCharsets.UTF_8)) //메시지 컨버터 추가
        .additionalInterceptors(restTemplateLoggingRequestInterceptor)
        .build();
  }

(RestTemplateConfig class)

 

BufferingClientHttpRequestFactory 세팅을 통해 Stream 콘텐츠를 메모리에 버퍼링 함으로써 Body 값을 두 번 읽을 수 있게 됩니다.

(첫 번째는 인터셉터에서, 두 번째는 원래 사용되는 비즈니스 로직에서)

 

 

@Slf4j
public class RestTemplateLoggingRequestInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        // request log
        URI uri = request.getURI();
        traceRequest(request, body);

        // execute
        ClientHttpResponse response = execution.execute(request, body);

        // response log
        traceResponse(response, uri);
        return response;
    }

    private void traceRequest(HttpRequest request, byte[] body) {
        StringBuilder reqLog = new StringBuilder();
        reqLog.append("[REQUEST] ")
        .append("Uri : ").append(request.getURI())
        .append(", Method : ").append(request.getMethod())
        .append(", Request Body : ").append(new String(body, StandardCharsets.UTF_8));
        log.info(reqLog.toString());
    }

    private void traceResponse(ClientHttpResponse response, URI uri) throws IOException {
        StringBuilder resLog = new StringBuilder();
        resLog.append("[RESPONSE] ")
        .append("Uri : ").append(uri)
        .append(", Status code : ").append(response.getStatusCode())
        .append(", Response Body : ").append(StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8));
        log.info(resLog.toString());
    }
}

(RestTemplateLoggingRequestInterceptor class)

 

 

***

하지만 이렇게 BuffringClientHttpRequestFactory를 사용하면 성능상의 단점이 발생하는데요.

전체 데이터의 Body를 메모리에 올리는 것은 성능상의 이슈가 될 수 있으며, 최악의 경우에는 'OutOfMemoryError'를 발생시킬 수도 있습니다.

 

이러한 문제를 방지할 수 있는 옵션으로는 아래 변형된 코드와 같이 Bean 등록되는 RestTemplate을 생성할 때 로직을 변경하여 로거에서 DEBUG 레벨이 활성화된 경우에만 BuffingClientHttpRequestFactory를 RestTemplate 인스턴스에 사용하도록 설정하는 방법과 추가로 인터셉터에서도 마찬가지로 DEBUG 로깅이 활성화된 경우에만 응답을 읽도록 설정하는 방법이 있습니다.

 

 

@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {

    // Apache HttpComponents
    HttpClient httpClient = HttpClientBuilder.create()
        .setMaxConnTotal(50)//최대 커넥션 수
        .setMaxConnPerRoute(20)
        .build(); //각 호스트(IP와 Port 의 조합)당 커넥션 풀에 생성가능한 커넥션 수

    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
    factory.setHttpClient(httpClient);

    RestTemplate restTemplate =  restTemplateBuilder
        .setReadTimeout(Duration.ofSeconds(5)) // read timeout
        .setConnectTimeout(Duration.ofSeconds(3)) // connection timeout
        .additionalMessageConverters(new StringHttpMessageConverter(StandardCharsets.UTF_8)) //메시지 컨버터 추가
        .additionalInterceptors(restTemplateLoggingRequestInterceptor)
        .build();
    
    // 로깅 DEBUG 레벨이 활성화된 경우에만 BufferingClientHttpRequest 사용 
    if (log.isDebugEnabled()) {
      ClientHttpRequestFactory clientHttpRequestFactory = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
      restTemplate.setRequestFactory(clientHttpRequestFactory);
      return restTemplate; 
    }
    return restTemplate;
  }

(메모리상 이슈를 해결하기 위한 옵션이 설정된 RestTemplateConfig class)

 

 

***

끝으로 Spring Framework5부터 'WebClient'라는 새로운 HTTP 클라이언트가 도입되었습니다.

WebClient는 RestTemplate을 대체할 수 있는 HTTP 클라이언트로, 기존의 동기식 API를 제공할 뿐만 아니라 효율적인 비 차단 및 비동기 접근 방식도 지원합니다. RestTemplate은 향후 버전에서 더 이상 사용되지 않기 때문에 새로 애플리케이션을 개발하게 되거나 기존 애플리케이션을 마이그래이션 하는 경우에는 WebClient를 사용하는 것이 추천됩니다.

 

 

 

< 참고 자료 >

 

마로의 Spring Framework 공부 - RestTemplate Logging(로그)

Spring REST 로깅 Spring Web RestTemplate을 이용한 HTTP 통신시 Interceptor를 활용하여 요청과 응답 로그를 남긴다. 주의할 점은, ResponseEntity의 Body는 Stream 이므로 로깅 인터셉터에서 Body Stream을 읽..

hoonmaro.tistory.com

 

 

 

< 함께 보면 좋은 자료 >

 

RestTemplate 4xx, 5xx Code 처리하는 방법 (HttpClientException)

RestTemplate이란 간략하게 spring framework 3.0부터 지원되는 http 통신에 사용되는 템플릿입니다. RESTful 형식을 따르며 json, xml 형식의 데이터를 쉽게 받을 수 있습니다. * RestTemplate에 대한 자세한 내..

wildeveloperetrain.tistory.com