업무를 하다 개인정보를 처리할 수 있는 비즈니스 로직에 접근하는 API에 대한 Logging이 필요해져 구현하기 위해 공부하고 정리한 포스트 입니다.
Logging은 크게 세가지 방식 [Filter, Interceptor, AOP] 을 사용 할 수 있다,
그 중 Spring AOP를 사용하여 처리 했는데 AOP를 사용한 이유와
나머지 방식의 차이에 대해 정리 하였음
웹 개발을 하다 보면 실제 비즈니스 로직이 호출되기 이전
, 이후
에 공통적으로 처리해야 할 기능들이 존재하는데
대표적인 예로 Logging, 인증, 인가, 인코딩 변환 등등이 있다.
공통적인 기능의 코드를 모든 모듈 및 페이지에서 작성하게 되면 코드의 중복이 발생하게 되고 MSA 기반에서는 각 모듈마다 다른 코드가 작성되어 관리가 힘들 수 있다.
따라서 공통 기능을 모아서 처리 할 수 있는 방법
으로 위 세가지가 사용된다.
하지만 이 세가지는 약간의 차이점이 존재하는데 우선 호출되는 시기
가 다르다.
그림 참조 블로그 링크
간단히 설명 하면 Filter는 Dispatcher Servlet이 실행 되기전에 수행된다.
여기서 Dispatcher Servlet 이란 적합한 컨트롤러를 찾아 위임 해 주는 역할을 함.
(Ex. Get, Post 처리 ⭐️자세한 정리는 다음 블로그 링크 참조 Dispatcer Servlet 개념 및 동작 과정)
Filter는 Spring이 실행 되기전 실행되며 톰캣과 같은 웹컨테이너(WAS)에서 처리를 해주게 된다.
Request / Response에 대한 조작이 가능함
(HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 처리 함)
따라서 아래와 같은 처리에 적합하다.
Interceptor는 Dispatcher Servlet에 N개 등록 될 수 있다. - DS가 해당 요청을 처리 가능한 Intercepter에게 할당
(Ex. 로그인 체크, 권한체크, 프로그램 실행시간 계산작업, 로그확인 Interceptor)
스프링의 모든 빈 객체에 접근
할 수있다.
따라서 아래와 같은 처리에 적합하다.
Interceptor와 Filter는 주소(URL)로 대상을 구분해서 걸러내야하는 반면,
AOP는 주소, 파라미터, 애노테이션 등 PointCut이 지원하는 다양한 방법으로 대상을 지정할 수 있다.
즉 URL 기반이 아닌 PointCut 단위로 동작한다.
이 때문에 비즈니스 로직의 메서드 실행 전, 후 단위까지 핸들링 할 수 있다.
참고: https://goddaehee.tistory.com/154
Logging 요구사항이 개인정보를 처리하는 api들에 대한 로그를 생성하는 것이었다.
따라서 모든 api에 대한 로그와 개인정보를 처리하는 로그를 구분할 필요가 있었고,
메소드 혹은 클래스단위의 처리가 필요해 보였으며 추후 모든 로그를 통합할때도 유연하게 확장을 할 수 있을 것이라고 생각하여 AOP를 선택해 구현 했다.
dependencies {
implementation("org.springframework.boot:spring-boot-starter-aop")
}
main이 있는 최상위 클래스에 어노테이션을 등록해 AOP(@Aspect)를 찾을 수 있게 해준다.
@EnableAspectJAutoProxy
@SpringBootApplication
class LoggingApplication
fun main(args: Array<String>) {
runApplication<LoggingApplication>(*args)
}
아래 코드는 단순히 로그를 출력해 주는 기능의 코드다.
@Aspect
@Component
class LogAspect {
val log: Logger = LoggerFactory.getLogger(LogAspect::class.java)
@Before("bean(*Controller)")
fun beforeLog(joinPoint: JoinPoint) {
log.info("이름이 Controller로 끝나는 모든 @Bean 호출 전 실행되는 로그 입니다.")
}
@After("execution(* com.miot2j.service.*.*(..))
fun beforeLog(joinPoint: JoinPoint) {
log.info("service 패키지내의 모든 메소드 호출 후 실행되는 로그 입니다.")
}
}
JoinPoint라는 개념을 설명하기 전에 기본적인 AOP의 개념을 설명 하겠다.
위 코드에서 알 수 있듯이 @Before("bean(*Controller)")
와 같이
PointCut을 이름이 Controller로 끝나는 모든 @Bean 호출 전 실행
으로 설정 할 수있다.
대표적인 PointCut 어노테이션은 다음과 같다
또한 포인트 컷 표현식을 사용할 수 있는데 여기선 @Bean, 인터페이스, 패키지 단위도 설정이 가능하다.