반응형
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
Tags
- fotmatter
- RestTemplate
- findAny
- ResponseBodyAdvice
- Thread Safety
- vaultTemplate
- AccessLevel
- 쓰레드 안전
- auto configuration
- @AutoConfiguration
- AOP this
- AOP 매개변수
- AOP target
- jpa
- JsonStringType
- spring
- Stream
- gradle
- Save Action
- Starter
- 개방/폐쇄 원칙
- ClientHttpRequestInterceptor
- 포맷터
- findFirst
- java
- restTemple
- AOP
- JsonType
- LogInterceptor
- Spring Boot
Archives
- Today
- Total
맨땅에 헤딩하는 개바른자
restTemplate LogInterceptor 본문
반응형
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;
}
}
반응형
'개발 TIP' 카테고리의 다른 글
CRUD 공통로직 만들기 (0) | 2022.05.10 |
---|---|
Request DTO Validate Aspect 적용기 (0) | 2022.05.10 |
MDC LogFilter 사용하기 (0) | 2022.05.02 |
restTemple 제네릭Type 사용 (0) | 2022.04.28 |
enum 비교 시 어떤게 좋을까? "equals" or "==" (0) | 2022.04.15 |