개발 TIP
restTemplate LogInterceptor
앵낄낄
2022. 5. 3. 21:20
반응형
restTemplate 통신 시 내가 원하는 형태로 로깅을 할 수 있는 방법을 알아보려고 합니다.
restTemplate에서는 Interceptor 기능이 존재하는데 말그대로 중간에 가로채서 다른 행위를 하고플 때 작용되는 구간입니다.
Interceptor의 사용 사례는 다음과 같습니다.
- 요청 및 응답 로깅
- 구성 가능한 백 오프 전략으로 요청 재시도
- 특정 요청 매개 변수를 기반으로 요청 거부
- 요청 URL 주소 변경
restTemplate 과정에서 Interceptor가 어느 구간에서 캐치되는지는 정확하게 분석하진 못함..
[restTemplate Configuration]
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(120) //연결을 유지할 최대 숫자
.setMaxConnPerRoute(100) //특정 경로당 최대 숫자
.setConnectionTimeToLive(5, TimeUnit.SECONDS) // keep - alive
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient); //HttpComponentsClientHttpRequestFactory 생성자에 주입
//인터셉터가 요청 / 응답 로거로서 기능하도록하려면 인터셉터가 처음으로, 클라이언트가 두 번째로 두 번 읽어야한다.
//기본 구현에서는 응답 스트림을 한 번만 읽을 수 있습니다.
// 이러한 특정 시나리오를 제공하기 위해 Spring은 BufferingClientHttpRequestFactory 라는 특수 클래스를 제공.
// 이름에서 알 수 있듯이이 클래스는 여러 용도로 JVM 메모리에서 요청 / 응답을 버퍼링합니다.
BufferingClientHttpRequestFactory bufferingClientHttpRequestFactory = new BufferingClientHttpRequestFactory(factory);
return restTemplateBuilder
.requestFactory(() -> bufferingClientHttpRequestFactory)
.setConnectTimeout(Duration.ofMillis(5000)) //읽기시간초과, ms
.setReadTimeout(Duration.ofMillis(5000)) //연결시간초과, ms
// .additionalInterceptors(new RequestResponseLoggingInterceptor()) // 여기서 설정해도 되지만 Option으로 on, off가 가능하게끔 할 예정
.build();
}
}
[restTemplate Interceptor 적용]
public abstract class AbstractApiClient implements InitializingBean, ApplicationContextAware {
private ApplicationContext ctx;
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.ctx = ctx;
}
....
....
.... 생략
....
/**
* restTemplate customize 한다.
*/
@Override
public void afterPropertiesSet() {
// request 및 response 를 일괄 logging 을 남긴다
List<ClientHttpRequestInterceptor> interceptors = this.restTemplate.getInterceptors();
ClientHttpRequestInterceptor logInterceptor = new RestClientLogInterceptor();
log.info("### [{}] logging interceptor enable : [{}]", this.getClass().getSimpleName(), logInterceptor.hashCode());
interceptors.add(logInterceptor);
}
}
로그를 남기는 ClientHttpRequestInterceptor 구현체는 아래와 같습니다.
@Slf4j
public class RestClientLogInterceptor implements ClientHttpRequestInterceptor {
public RestClientLogInterceptor() {
try {
host = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
host = "unknown";
}
}
private String host;
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
LocalDateTime start = LocalDateTime.now();
StringBuilder sb = new StringBuilder();
sb.append("\n==== API CALL BEGIN ====\n");
ClientHttpResponse response;
try {
sb.append(" * HOST = ").append(host).append("\n");
sb.append(" * LogKey = ").append(LogKey.get()).append("\n");
sb.append(" * URI = ").append(request.getMethod()).append(" ").append(request.getURI()).append("\n");
sb.append(" * HEADER = ").append(request.getHeaders()).append("\n");
sb.append(" * BODY = ").append(new String(body, Charset.defaultCharset())).append("\n");
response = execution.execute(request, body);
} catch (Exception e) {
sb.append(" == RESPONSE ERROR ====\n");
sb.append(" * ERROR = ").append(e).append("\n");
sb.append(" * DURATION = ").append(Duration.between(start, LocalDateTime.now()).toMillis()).append(" (ms) \n");
sb.append("==== API CALL END ====\n");
// Exception 발생시 로깅을 마무리 하고 rethrow 한다.
log.warn(sb.toString());
throw e;
}
sb.append(" == RESPONSE ====\n");
sb.append(" * STATUS = ").append(response.getStatusCode()).append("\n");
sb.append(" * BODY = ").append(Objects.nonNull(response.getBody()) ? MvcUtils.inputStreamToString(response.getBody()) : "EMPTY");
sb.append(" * DURATION = ").append(Duration.between(start, LocalDateTime.now()).toMillis()).append(" (ms) \n");
sb.append("==== API CALL END ====\n");
log.info(sb.toString());
return response;
}
}
반응형