본문 바로가기
BackEnd/Project

[RealPJ] Ch02. 실무 스타일로 Feign Client 사용해보기 - Logger

by 개발 Blog 2024. 8. 30.

공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.

 

이번 포스팅에서는 Feign Client에서 로깅 기능을 커스터마이징하여 실무에서 유용한 로그를 남기는 방법에 대해 다룬다.

 

1. FeignCustomLogger 클래스 구현

먼저, logger 패키지 안에 FeignCustomLogger 클래스를 생성하여, Feign의 기본 Logger를 확장한다. 이 클래스는 요청과 응답의 정보를 로깅하고, 특정 조건을 만족할 때 추가적인 처리를 할 수 있도록 한다.

package dev.be.feign.feign.logger;

import static feign.Util.UTF_8;
import static feign.Util.decodeOrDefault;
import static feign.Util.valuesOrEmpty;

import java.io.IOException;

import feign.Logger;
import feign.Request;
import feign.Response;
import feign.Util;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@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) {
        // log를 어떤 형식으로 남길지 정해준다.
        System.out.println(String.format(methodTag(configKey) + format, args));
    }

    @Override
    protected void logRequest(String configKey, Logger.Level logLevel, Request request) {
        /**
         * [값]
         * configKey = DemoFeignClient#callGet(String,String,Long)
         * logLevel = BASIC # "feign.client.config.demo-client.loggerLevel" 참고
         *
         * [동작 순서]
         * `logRequest` 메소드 진입 -> 외부 요청 -> `logAndRebufferResponse` 메소드 진입
         *
         * [참고]
         * request에 대한 정보는
         * `logAndRebufferResponse` 메소드 파라미터인 response에도 있다.
         * 그러므로 request에 대한 정보를 [logRequest, logAndRebufferResponse] 중 어디에서 남길지 정하면 된다.
         * 만약 `logAndRebufferResponse`에서 남긴다면 `logRequest`는 삭제해버리자.
         */
        System.out.println(request);
    }

    @Override
    protected Response logAndRebufferResponse(String configKey, Logger.Level logLevel,
                                              Response response, long elapsedTime) throws IOException {
        /**
         * [참고]
         * - `logAndRebufferResponse` 메소드내에선 Request, Response에 대한 정보를 log로 남길 수 있다.
         * - 매소드내 코드는 "feign.Logger#logAndRebufferResponse(java.lang.String, feign.Logger.Level, feign.Response, long)"에서 가져왔다.
         *
         * [사용 예]
         * 예상 요청 처리 시간보다 오래 걸렸다면 "Slow API"라는 log를 출력시킬 수 있다.
         * ex) [DemoFeignClient#callGet] <--- HTTP/1.1 200 (115ms)
         *     [DemoFeignClient#callGet] connection: keep-alive
         *     [DemoFeignClient#callGet] content-type: application/json
         *     [DemoFeignClient#callGet] date: Sun, 24 Jul 2022 01:26:05 GMT
         *     [DemoFeignClient#callGet] keep-alive: timeout=60
         *     [DemoFeignClient#callGet] transfer-encoding: chunked
         *     [DemoFeignClient#callGet] {"name":"customName","age":1,"header":"CustomHeader"}
         *     [DemoFeignClient#callGet] [Slow API] elapsedTime : 3001
         *     [DemoFeignClient#callGet] <--- END HTTP (53-byte body)
         */

        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.HEADERS.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 메서드: 로그를 남길 형식을 정하며, methodTag를 사용해 메서드의 정보를 함께 출력한다.
  • logRequest 메서드: 요청에 대한 정보를 로그로 남긴다. 요청의 상세 정보를 출력하는 데 사용되며, 필요에 따라 로깅 위치를 선택할 수 있다.
  • logAndRebufferResponse 메서드: 응답을 처리하고, 응답 시간이 오래 걸리는 경우 "Slow API"라는 메시지를 로그로 남긴다. 이 기능을 통해 성능 문제가 있는 API를 쉽게 감지할 수 있다.

2. FeignCustomLogger 빈 등록

FeignCustomLogger를 Feign Client에서 사용할 수 있도록, FeignConfig 클래스에 빈으로 등록한다. 이를 통해 모든 Feign Client 요청에 대해 커스터마이징 된 로그가 남겨진다.

package dev.be.feign.config;

import dev.be.feign.feign.logger.FeignCustomLogger;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {

    @Bean
    public Logger feignLogger() {
        return new FeignCustomLogger();
    }
}

 

3. Slow API 탐지 및 처리

  • curl localhost:8080/post 명령을 통해 POST 요청을 보냈을 때, 요청과 응답에 대한 상세 로그가 출력된다.
  • FeignCustomLogger의 중요한 기능 중 하나는 logAndRebufferResponse 메서드에서 요청 시간이 일정 기준(예: 3초) 이상일 경우 "Slow API"라는 로그를 남기는 것이다. 이를 통해, 실시간으로 API 성능을 모니터링하고 성능 이슈를 감지할 수 있다.

  • 기본적으로 응답 시간이 27ms였지만, 테스트를 위해 5초 지연을 설정하면 "Slow API" 메시지가 로그에 출력된다. 이 기능을 통해 실시간 성능 모니터링과 문제 감지가 가능하다.

FeignCustomLogger를 사용하여 Feign Client의 요청과 응답을 커스터마이징된 방식으로 로깅함으로써, 실무에서 더 유용한 정보를 얻을 수 있다. 특히, 성능 문제가 발생할 수 있는 Slow API를 쉽게 탐지하고, 필요한 조치를 취할 수 있도록 설정하였다.

 

이러한 로깅 기능은 성능 최적화 및 실시간 모니터링에서 매우 유용하게 사용될 수 있으며, 실무에서의 안정적인 운영을 지원한다.