AOP 에 대한 기본적인 개념을 이전 포스팅에서 알아보았다.
https://pius712.tistory.com/33
이 AOP 를 활용하여, JPA repository 메서드 호출에 대한 로깅을 간단하게 구현하고자 한다.
요구사항
- jpa repository 모든 메서드에 대해 로그를 남겨야함
- 어떤 메서드가 호출했는지, 어떤 메서드가 호출되었는지 로그를 남겨야함
- jpa repository 의 time elapsed 로그를 남겨야함
- 예외 발생시 message 를 남길 수 있어야함.
구현
1. jpa repository 모든 메서드에 대해 로그
우선 모든 jpa repository 에 대한 로그 남겨야하는데, 이를 위해서는 jpa repository 의 구조를 살펴볼 필요가 있다. 일반적으로는 JpaRepository 를 구현하기 때문에 이를 포인트 컷으로 잡아도될거 같은데, 아래 블로그와 같은 케이스도 있으니, 최상위 인터페이스를 포인트컷으로 잡으려고 한다.
https://ohzzi.io/jpa-repository-no-repository-yes
@Aspect
class JpaAccessAspect {
private val log = LoggerFactory.getLogger(javaClass)
@Around("execution(public * org.springframework.data.repository.Repository+.*(..))")
fun logAll(joinPoint: ProceedingJoinPoint): Any? {
}
}
2. 어떤 메서드가 호출되었는지 로그
joinPoint 를 통해 aop 가 적용된 메서드의 시그니처를 가져올 수 있다.
이를 통해 메서드 이름을 가져올 수 있다.
private fun getMethodName(joinPoint: ProceedingJoinPoint): String {
return joinPoint.signature.name
}
3. 어떤 메서드가 호출하였는지 로그
이를 위해서는 stack trace 를 찾아봐야한다.
우선 stack trace 중 나의 패키지에 속하는 데이터만 필터링해준다.
// 패키지 네임으로 필터링
private fun getStack(): String {
return Thread.currentThread().stackTrace.filter {
it.className.contains("com.pius")
}
}
필터링 후의 스택은 아래와 같다.
따라서, 2번째의 stackTraceElement 를 문자열로 가져온다.
private fun getStack(): String {
return Thread.currentThread().stackTrace.filter {
it.className.contains("com.pius")
}.toTypedArray()[2].toString()
}
4. time elapsed 로그
이 경우, 간단하게 아래와 같이 구현한다.
val startTime = System.currentTimeMillis()
val result = joinPoint.proceed()
val timeElapsed = System.currentTimeMillis() - startTime
5. 예외 발생 message 로그
별 다른 추가 요구사항이 없기 때문에, spring 에서 변환해준 에러의 메세지를 로그로 찍으려고 한다.
var exception: Exception? = null
// or var exceptionMessage: String = ""
try{
// 생략
}catch (e: Exception) {
exception = e
throw e
} finally {
// 아래 메시지 사용
exception?.cause?.message
}
완성
@Aspect
class JpaAccessAspect {
private val log = LoggerFactory.getLogger(javaClass)
@Around("execution(public * org.springframework.data.repository.Repository+.*(..))")
fun logAllJpaRepository(joinPoint: ProceedingJoinPoint): Any? {
val startTime = System.currentTimeMillis()
var exception: Exception? = null
try {
return joinPoint.proceed()
} catch (e: Exception) {
exception = e
throw e
} finally {
val timeElapsed = System.currentTimeMillis() - startTime
write(getMethodName(joinPoint), getStack(), timeElapsed, exception?.cause?.message)
}
}
private fun getMethodName(joinPoint: ProceedingJoinPoint): String {
return joinPoint.signature.name
}
private fun getStack(): String {
return Thread.currentThread().stackTrace.filter {
it.className.contains("com.pius")
}.toTypedArray()[2].toString()
}
private fun write(methodName: String, stack: String, timeElapsed: Long, message: String?) {
log.info
mutableMapOf(
"methodName" to methodName,
"stack" to stack,
"timeElapsed" to timeElapsed,
"message" to message
).toString()
)
}
}
'스프링부트' 카테고리의 다른 글
스프링부트 테스트(2) - configuration 과 property (1) | 2024.07.23 |
---|---|
스프링부트 테스트(1) - 동작원리와 어노테이션 친해지기 (1) | 2024.07.23 |
스프링 AOP - 톺아보기 (1) | 2024.06.06 |
나는 테스트에서 @Transactional 붙이지 않겠다.. (feat. 동시성 삽질기) (0) | 2024.05.02 |
jackson for kotlin part3. 삽질기 (feat. jacksonObjectMapper) (0) | 2024.03.10 |