Spring cloud-stream-kafka 송/수신 Logger 모듈화

김준성
FRIP
Published in
6 min readNov 2, 2022

안녕하세요 프립 백엔드 개발자 김준성입니다.

spring-cloud-stream-kafka를 사용하는 서버들의 kafka 이벤트 송/수신 로그를 남길 수 있는 모듈을 만들어 적용하게 되었는데요. 이를 만들면서 배웠던 내용을 공유드립니다.

배경

기존 로그는 아래와 같이 이벤트를 송/수신하는 함수에서 개별적으로 log를 남기고 있었습니다. 대부분의 이벤트가 로그로 남겨져 있긴 했지만 서버마다 로그를 남기는 규칙이 통일되어 있지 않았고 로그를 남기는 방식에 변경이 있을 경우에 모든 곳을 찾아서 변경해야 했기에 불편함이 있었습니다.

기존 로그 예시 — Frip 정산 API 중 일부

로그 규칙이 달라 모니터링에 어려움이 있고 변경에도 어려움이 있어 Kafka 송/수신에 대한 로거를 모듈화 하기로 결정하였습니다!

개발 환경

  • Spring boot version — 2.4.5
  • JDK version — 1.8
  • Spring Boot Cloud version — Hoxton.SR6

진행 내용

  • Spring AOP를 사용하여 Kafka message를 송/수신 하는 함수가 발생하기 전 or 후에 로그를 발생시키도록 하였습니다.
  • springframework.messaging 함수에 로깅을 하게 된다면 API들에서 따로 kafka 로그에 관련된 코드 수정이 필요 없어집니다.
참고
Spring AOP는 런타임시 위빙 방식이고 Proxy를 이용하여 동작합니다. 따라서 Bean으로 등록되어 있어야합니다.

@Pointcut 어노테이션 execution 값에 springframework.messaging 패키지를 설정하고 Kafka message를 송/수신 테스트를 진행해보았습니다. 다행히 AOP가 잘 적용이 되었고, 송/수신 시 동작하는 메소드를 찾을 수 있었습니다.

  • 송신 시 발생 메소드org.springframework.messaging.converter.CompositeMessageConverter.toMessage
  • 수신 시 발생 메소드 org.springframework.messaging.converter.CompositeMessageConverter.fromMessage

수신은 성공 여부에 상관없이 로그를 남기기 위해 @Before 시 로그를 남겼습니다.

송신은 정상적으로 이벤트를 받았을 경우에만 로그를 남기기 위해 @AfterReturning 시 로그를 남겼습니다.

최종 코드

@Aspect
@Component
@Logger
class KafkaLogger {
@Pointcut("execution(* org.springframework.messaging.converter.CompositeMessageConverter.toMessage*(..))")
private fun producerLogging() {
}

@Pointcut("execution(* org.springframework.messaging.converter.CompositeMessageConverter.fromMessage*(..))")
private fun consumerLogging() {
}

@Before("producerLogging()")
fun before(joinPoint: JoinPoint) {
logger.info("send : ${joinPoint.args.get(0)}")
}

@AfterReturning(value = "consumerLogging()", returning = "obj")
fun afterReturning(joinPoint: JoinPoint?, obj: Any?) {
logger.info("receive : ${obj}")
}
}

현 작업까지는 모듈화를 진행한 것은 아니고 KafkaLogger Class를 서버에서 직접 만들고 테스트하였을 때, 정상적으로 로그가 남는 것을 확인할 수 있었습니다.

모듈화

  • KafkaLogger 클래스를 내부적으로 사용하던 frientrip-domain 라이브러리에 포함시켰습니다.
  • 각 라이브러리를 사용하던 서버에서는 해당 클래스가 포함된 버전으로 올려서 배포를 해야합니다.

[문제 발생]

  • 라이브러리에 해당 클래스를 추가 후 배포를 한 뒤, 서버에서 해당 라이브러리를 사용해도 로그가 정상적으로 찍히지 않았습니다.
  • KafkaLogger 클래스의 위치는 com.frientrip.shared.domain.event 패키지 안에 있었습니다.
  • 따라서 서버가 실행되는 @ComponentScan 패키지 범위 안에 포함되어 있지 않기 때문에 bean으로 등록되지 않고, 따라서 proxy 객체를 호출할 수 없게 되었습니다.

[해결책]

  • 라이브러리에 있는 KafkaLogger 클래스를 Bean으로 등록하기 위한 방법을 찾아야 했습니다.

@ComponentScancom.frientrip.shared.domain.event를 추가하는 방식

  • 동작은 잘 되었으나 사용하는 서버마다 해당 코드를 추가해줘야하는 이슈가 발생하였습니다.
  • 라이브러리에 클래스 위치가 달라져 패키지 주소가 변경될 경우도 문제가 발생할 수 있었습니다.

@EnableAutoConfiguration 설정을 이용한 방식

  • 라이브러리 resource/META-INF 폴더에 spring.factories 파일을 추가하고 com.frientrip.shared.domain.event.KafkaLogger 를 Bean으로 등록하였습니다.
  • 서버에서 추가적인 작업 필요 없이 KafkaLogger가 Bean으로 등록이 되었습니다.

라이브러리 적용 시 META-INF 패키지에 spring.factories 파일이 있는 것을 확인할 수 있고 Bean으로 등록됩니다.

@EnableAutoConfiguration을 이용한 방식이 서버에 추가 개발 공수 없이 로그를 남길 수 있어 적용하였습니다

마무리

작업을 하면서 TransactionalEventListener, Spring AOP, AspectJ, Bean을 등록하는 방식, 라이브러리 적용하는 방식 등 다양한 기술을 익히고 적용해볼 수 있었습니다.

기술들을 찾으면서 참고했었던 사이트들도 공유드립니다.

[참고 사이트]

[Spring] Spring의 Event를 어떻게 사용하는지에 대해서 알아봅시다. - @TransactionalEventListener에 대해서

[Spring] Spring 기초

[Spring] AOP 사용 방법 (예제 코드)

Spring AOP와 AspectJ 비교하기

[02] Spring boot 자동 설정

.jar로 배포하기

빌드 종속 항목 추가

--

--