안녕하세요! 이번 포스팅은 spring cloud open feign 로그 사용자화에 대해 글을 써보고자 합니다.
포스팅을 하게된 이유는 spring 공식 래퍼런스에 나와있는 정형화된 로그방식을 커스텀화 하여 출력 하거나, 수집 목적인 분들에게 도움이 될듯 싶습니다.
1. Spring Cloud OpenFeign란?
간단하게 소개하면, OpenFeign은 원래 Netflix에서 개발한 후 오픈 소스 커뮤니티로 이전한 오픈 소스 프로젝트입니다. Feign은 FeignClient로 선언된 인터페이스의 동적 구현을 생성하는 선언적 나머지 클라이언트입니다. FeignClient의 도움으로 웹 서비스를 작성하는 것은 매우 쉽습니다. FeignClient는 주로 타사 또는 마이크로서비스에 의해 노출되는 REST API 엔드포인트를 사용하는 데 사용됩니다.
2. 적용 방법
2.1 기본방식
spring 공식 래퍼런스에 나와있는 기본 방법 - [참고] https://cloud.spring.io/spring-cloud-openfeign/2.2.x/reference/html/#feign-logging
FeignConfig.class
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
/**
* Feign 로그 정책 설정
* NONE : 로깅하지 않음(Default)
* BASIC : Request Method와 URL 그리고 Reponse 상태 코드와 실행 시간을 로깅합니다.
* HEADER : Request, Response Header 정보와 함께 BASIC 정보를 로깅합니다.
* FULL : Request와 Response의 Header, Body 그리고 메타데이터를 로깅합니다.
*/
@Bean
public Logger.Level loggerLevel(){
return Logger.Level.FULL;
}
}
application.yml
logging:
level:
${pacakage}.${class}: ${level}
출력결과
2023-08-30T20:21:37.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] ---> GET https://www.boredapi.com/api/activity HTTP/1.1
2023-08-30T20:21:37.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] ---> END HTTP (0-byte body)
2023-08-30T20:21:38.742+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] <--- HTTP/1.1 200 OK (998ms)
2023-08-30T20:21:38.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept
2023-08-30T20:21:38.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] access-control-allow-origin: *
2023-08-30T20:21:38.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] connection: keep-alive
2023-08-30T20:21:38.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] content-length: 155
2023-08-30T20:21:38.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] content-type: application/json; charset=utf-8
2023-08-30T20:21:38.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] date: Wed, 30 Aug 2023 11:21:38 GMT
2023-08-30T20:21:38.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] etag: W/"9b-2qtIT+CbaAfPwN/SIhZa8Fg8MEY"
2023-08-30T20:21:38.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] server: Cowboy
2023-08-30T20:21:38.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] via: 1.1 vegur
2023-08-30T20:21:38.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] x-powered-by: Express
2023-08-30T20:21:38.743+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get]
2023-08-30T20:21:38.744+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] {"activity":"Have a paper airplane contest with some friends","type":"social","participants":4,"price":0.02,"link":"","key":"8557562","accessibility":0.05}
2023-08-30T20:21:38.745+09:00 DEBUG 3979 --- [nio-8081-exec-3] c.example.feign.springfeign.BoredClient : [BoredClient#get] <--- END HTTP (155-byte body)
2.2 사용자화
로그 포맷을 사용자화 하는 방법은 io.github.openfeign:feign-core에 있는 Logger라는 추상 클래스를 상속해 재정의 하는 방식입니다.
해당 추상클래스는 http 통신 시 요청, 응답, 에러, 재시도 등등 상황에 따른 로깅 기능을 하는 클래스입니다.
다만 이번 글에선 요청, 응답, 에러에 대한 메소드만을 재정의해 로그 포맷을 수정해보도록 하겠습니다.



FeignConfig.class
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger customLogger() {
return new FeignCustomLogger();
}
}
FeignCustomLogger.class
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Logger;
import feign.Request;
import feign.Response;
import feign.Util;
import java.io.IOException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FeignCustomLogger extends Logger {
private final ObjectMapper mapper = new ObjectMapper();
public FeignCustomLogger() {
}
@Override
protected void log(String configKey, String format, Object... args) {
}
// ex) [요청] GET http://localhost:8081/api/v1/test
@SneakyThrows
@Override
protected void logRequest(String configKey, Level logLevel, Request request) {
String bodyData = request.body() != null ? mapper.writeValueAsString(request.body()) : "";
log.info(String.format("[요청] %s %s %s", request.httpMethod(), request.url(),
bodyData));
super.logRequest(configKey, logLevel, request);
}
// ex) [응답] 200 {"status" : "success"}
@SneakyThrows
@Override
protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response,
long elapsedTime) {
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
log.info(String.format("[응답] %d %s", response.status(), new String(bodyData)));
return response.toBuilder().body(bodyData).build();
}
// ex) [요청실패] reason....
@Override
protected IOException logIOException(String configKey, Level logLevel, IOException ioe,
long elapsedTime) {
log.error("[요청실패] {}", ioe.getMessage(), ioe);
return super.logIOException(configKey, logLevel, ioe, elapsedTime);
}
}
출력결과
2023-08-30T21:18:23.580+09:00 INFO 23484 --- [nio-8081-exec-1] c.e.feign.springfeign.FeignCustomLogger : [요청] GET http://localhost:8081/api/v1/test
2023-08-30T21:18:23.655+09:00 INFO 23484 --- [nio-8081-exec-1] c.e.feign.springfeign.FeignCustomLogger : [응답] 200 {"activity":"Watch a movie you'd never usually watch","price":"0.15","type":"relaxation","participants":"1"}
+ 주의할점
response body의 경우 stream 객체이므로 deep copy 하거나 byte로 변환후 재사용 하는 방식으로 처리하시는 걸 추천드립니다.
stream 닫는 것도 꼭!
2.3 응용
상황에 따라 로그를 출력 혹은 수집하게끔 운용 하려면 각각 bean에 @Profile 어노테이션을 활용하면 됩니다.
FeignConfig.class
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class FeignConfig {
/**
* Feign 로그 정책 설정
* NONE : 로깅하지 않음(Default)
* BASIC : Request Method와 URL 그리고 Reponse 상태 코드와 실행 시간을 로깅합니다.
* HEADER : Request, Response Header 정보와 함께 BASIC 정보를 로깅합니다.
* FULL : Request와 Response의 Header, Body 그리고 메타데이터를 로깅합니다.
*/
@Profile(value = {"local, test"})
@Bean
public Logger.Level loggerLevel(){
return Logger.Level.FULL;
}
@Profile(value = {"dev", "stage"})
@Bean
public Logger customLogger() {
return new FeignCustomLogger();
}
}
마무리
필력이 부족하지만 openFeign 로그 사용자화에 대해 간단하게 다뤄 보았습니다.
저는 restTemplate이 더 익숙해 자주 사용하는 편이지만, 쉽고 빠르게 개발할땐 openFeign을 주로 사용하다 보니 생긴 자그마한 애로사항?에 대해 간략하게 처리하는 방법을 공유차 적어 보았습니다. 좀 더 좋은 방법이 있으시거나 해소가 안될 시 댓글 부탁드립니다!
소스 원본
https://github.com/kgggh/java-spring-learning/tree/main/spring-open-feign
'개발' 카테고리의 다른 글
| ChatGPT 채팅 삭제, 크롬 확장 프로그램으로 자동화하기 (1) | 2025.03.28 |
|---|---|
| Java에서 Thread.UncaughtExceptionHandler 활용하기 (0) | 2025.02.27 |
| spring-boot docker-compose 사용법 (1) | 2023.05.14 |
| Easy Random을 활용한 Unit Test (0) | 2023.03.08 |
| [후기] NHN forward 2022 (0) | 2022.11.26 |