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

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

728x90

반응형

'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를 사용하는 것이 추천됩니다.

< 참고 자료 >

< 함께 보면 좋은 자료 >

728x90

반응형

from http://wildeveloperetrain.tistory.com/102 by ccl(A) rewrite - 2021-12-23 00:27:40