Spring Boot/Feign Client

Feign Client - Error Decoder 및 logger

수수한개발자 2023. 1. 15.
728x90

DemoFeignErrorDecoder

public class DemoFeignErrorDecoder implements ErrorDecoder {
    private final ErrorDecoder errorDecoder = new Default();

    @Override
    public Exception decode(String methodKey, Response response) {
        HttpStatus httpStatus = HttpStatus.resolve(response.status());

        if (httpStatus == HttpStatus.NOT_FOUND) {
            System.out.println("[DemoFeignErrorDecoder] Http Status = " + httpStatus);
            throw new RuntimeException(String.format("[RuntimeException] Http Status is %s", httpStatus));
        }

        return errorDecoder.decode(methodKey, response);
    }
}

 

 

DemoFeignConfig

@Configuration
@RequiredArgsConstructor
public class DemoFeignConfig {
    @Bean
    public DemoFeignInterceptor feignInterceptor() {
        return DemoFeignInterceptor.of();
    }

    @Bean
    public DemoFeignErrorDecoder demoFeignErrorDecoder() {
        return new DemoFeignErrorDecoder();
    }
}

 

 

DemoFeignClient

@FeignClient(
        name = "demo-client",
        url = "${feign.url.prefix}",
        configuration = DemoFeignConfig.class
)
public interface DemoFeignClient {

    @GetMapping("/get") // -> http://localhost:8080/target_server/get  get 으로 요청이 감
    ResponseEntity<BaseResponseInfo> callGet(@RequestHeader("CustomHeaderName") String customHeader,
                                             @RequestParam("name") String name,
                                             @RequestParam("age") Long age);

    @PostMapping("/post") // -> http://localhost:8080/target_server/post  post 으로 요청이 감
    ResponseEntity<BaseResponseInfo> callPost(@RequestHeader("CustomHeaderName") String customHeader,
                                                           @RequestBody BaseRequestInfo baseRequestInfo);

    @GetMapping("/error") // -> http://localhost:8080/target_server/error  get 으로 요청이 감
    ResponseEntity<BaseResponseInfo> callErrorDecoder();

}

 

위와 같이 코드 작성 후 외부 API 와의 연동 시 예외가 발생하면 ErrorDecoder에서 받게 됩니다.

이제 예외 상황 마다 대응하는 코드를 작성하면 됩니다.

 

 

이제 로그를 찍어보겠습니다.

feign에서 제공하는 Logger 클래스가 있습니다. 이 Logger 클래스를 상속받은 뒤 구현체를 구현해 주면 됩니다.

 

FeignCustomLogger

 

@Slf4j
@RequiredArgsConstructor
public class FeignCustomLogger extends Logger {
    private static final int DEFAULT_SLOW_API_TIME = 3_000;
    private static final String SLOW_API_NOTICE = "Slow API";

    @Override
    protected void log(String configKey, String format, Object... args) {
        //로그를 남길때 어떤 형식으로 남길지 정해준다.
        System.out.println(String.format(methodTag(configKey) + format, args));
//        log.info(String.format(methodTag(configKey) + format, args));
    }

    @Override
    protected void logRequest(String configKey, Level logLevel, Request request) {
        System.out.println("[logRequest] : " + request);
//        super.logRequest(configKey, logLevel, request);
//        log.info(String.valueOf(request));
    }

    /*
    이 메서드 하나로 위의 메서드를 컨트롤 할 수 있다.
     request, response 둘다 컨트롤 가능
     */
    @Override
    protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
        String protocolVersion = resolveProtocolVersion(response.protocolVersion());
        String reason =
                response.reason() != null && logLevel.compareTo(Level.NONE) > 0 ? " " + response.reason()
                        : "";
        int status = response.status();
        log(configKey, "<--- %s %s%s (%sms)", protocolVersion, status, reason, elapsedTime);
        if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {

            for (String field : response.headers().keySet()) {
                if (shouldLogResponseHeader(field)) {
                    for (String value : valuesOrEmpty(response.headers(), field)) {
                        log(configKey, "%s: %s", field, value);
                    }
                }
            }

            int bodyLength = 0;
            if (response.body() != null && !(status == 204 || status == 205)) {
                // HTTP 204 No Content "...response MUST NOT include a message-body"
                // HTTP 205 Reset Content "...response MUST NOT include an entity"
                if (logLevel.ordinal() >= Level.FULL.ordinal()) {
                    log(configKey, ""); // CRLF
                }
                byte[] bodyData = Util.toByteArray(response.body().asInputStream());
                bodyLength = bodyData.length;
                if (logLevel.ordinal() >= Level.FULL.ordinal() && bodyLength > 0) {
                    log(configKey, "%s", decodeOrDefault(bodyData, UTF_8, "Binary data"));
                }

                if (elapsedTime > DEFAULT_SLOW_API_TIME) {
                    log(configKey, "[%s] elapsedTime : %s", SLOW_API_NOTICE, elapsedTime);
                }

                log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
                return response.toBuilder().body(bodyData).build();
            } else {
                log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
            }
        }
        return response;
    }
}

 

log 메서드는 로그를 어떤 형식으로 남길지에 대해 정의 하는 메서드입니다.

logRequest는 요청할때의 로그를 남기게 됩니다.

logAndRebufferResponse는 요청과 응답에 대해 모두 로그를 남기게 됩니다.

 

저는 기본 시간 3초를 DEFAULT_SLOW_API_TIME 으로 정의해두고 만약 외부 API와의 통신 시 이 시간보다 길어지게 되면 Slow API라는 로그를 남기게 되어 어떤 API 호출 시 내가 예상한 시간보다 오래 걸리는지 알 수 있게 설정해 두었습니다.

 

이제 이 로그가 찍히게 되면 알림이라던지 후처리 로직을 통해 현재 문제가 있음을 알아갈 수 있는 방법을 생각해 보면 될 것 같습니다.

 

 

 

 

728x90

'Spring Boot > Feign Client' 카테고리의 다른 글

Feign Client - Interceptor  (0) 2023.01.15
Feign Client 기본 구성 및 흐름  (0) 2023.01.14

댓글