티스토리 뷰

 

[AOP] 소요 시간 측정 AOP class level에 적용하기

진행 중인 프로젝트 메서드에 slow query 분석 또는 잘못된 비즈니스 로직을 해결하기 위해 시간 측정 AOP를 활용해봤다 우선 aop 관련 package를 따로 만들어주고 시간 측정할 메서드에 적용시킬 custom

ryumodrn.tistory.com

이전 글에 해당하는 내용

 

지난 버전에서는 LogExecutionTime이라는 custom annotation을 만들고

이 녀석을 적용시킬 수 있는 범위를 클래스까지 추가하여 클래스에 해당 애노테이션을 붙여 시간 측정을 했다

실제 운영 시에는 이와 같이 선별적인 방식으로 시간 측정할 클래스에 애노테이션을 붙여 사용해야 할 것인데

애노테이션을 붙이지 않고 더 간편하게 선별적으로 적용시키는 방법이 있다

대단한 건 아니고 Controller에 관례를 따르고 SpEL을 사용하는 방식이다

Controller를 표현할 때 BlahBlahController, BlahBlahRestController, BlahBlahEndPoint 등으로 사용할 텐데

프로젝트에서 이를 통일된 방식으로 사용하기만 하면 된다

아래는 응답 시간 측정을 위해 작성한 Aspect 코드다

나는 XXController로 통일하였고 단순히 Controller로 끝나는 빈에 모두 적용시킬 수 있게 만들었다

@Around(value = "bean(*Controller)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
  StopWatch stopWatch = new StopWatch();
  stopWatch.start(String.valueOf(joinPoint.getSignature()));
  Object proceed = joinPoint.proceed();
  stopWatch.stop();

  log.info("[WOOMOOL-STOPWATCH] :: -> {}", stopWatch.prettyPrint());
  return proceed;
}

 

애노테이션을 사용하는 방식보다 이게 나은 것 같다

애노테이션 하나 더 붙인다고 대단한 노력이 필요한 건 아니지만 필요한 AOP 수가 늘어갈수록 덕지덕지 붙을 것이고

애노테이션 작성 시간도 들며 중요한 애노테이션이 무엇인지 가리게 될 수도 있다

가장 중요한 이유는 굳이 애노테이션을 만들 필요가 없기 때문이다

 

이 방법을 응용해 로그에도 적용시킬 수 있다

로그는 애플리케이션 운영에 필수적이다

다만 소스 코드 내에 log.info("blahblah") 형식으로 도배가 되어있으면 정신이 혼미해진다

게다가 리팩토링을 거의 하지 않는 레거시 코드에서 7~8 단계에 가까운 중첩 if문, switch문과

log 난사가 함께 어우러진 한 클래스 파일이 10,000줄에 가까운 것도 본 경험이 있다

 

새로 진행하는 프로젝트나 클린 코드를 실천하려는 자세로 이런 방식의 코드를 지양하려면 어떡해야 할까?

공통된 부분을 모두 뽑아내 재사용하여 처리하면 될 것이다

아래 블로그를 참고하면 Request로 넘어오는 요청, 호출하는 메서드, 보내오는 인자 등을 쉽게 로깅할 수 있다

 

Spring에서 Request를 우아하게 로깅하기

스프링 기반의 웹 어플리케이션을 만들다 보면 요청을 처리하는데 맨 처음에 위치하고 있는 Controller(이하 컨트롤러)라는 레이어를 만들게 된다. 그럴때면 사용자가 어떤 요청(Request)을 하였는지

taetaetae.github.io

 

나는 Exception 처리에도 AOP를 사용하는데 Spring에서는 보통 @ControllerAdvice를 이용해 예외를 처리해준다

@RestControllerAdvice는 @ControllerAdvice에다가 @ResponseBody 붙인 것뿐이다

나는 GlobalExceptionHandler라는 이름으로 @RestControllerAdvice를 적용해 만들어 사용하는 중인데

Exception을 인자로 받아와서 여기에서 의미 있는 형식으로 변환해 클라이언트에 다시 던져주면 된다

이때, 어떤 메서드 호출에서 에러가 났는지 응답이 어떻게 나갔는지를 AOP로 처리해준다

시간 측정과 유사하게 작성하되 returning 옵션으로 response를 가져오고 이를 메서드 인자로 사용한다

joinPoint, response에서 유용한 정보를 뽑아와 로그로 남긴다 여기서는 위와 다르게 애노테이션 방식으로 적용해놨다

@AfterReturning(value = "@within(com.woomoolmarket.aop.annotation.LogForException)", returning = "response")
public void logForException(JoinPoint joinPoint, Object response) {
  log.info("[WOOMOOL-ERROR] :: method -> {}", joinPoint.getSignature().toShortString());
  log.info("[WOOMOOL-ERROR] :: target -> {}", joinPoint.getTarget());
  log.info("[WOOMOOL-ERROR] :: response -> {}", response);
}

 

이 방식의 장점은 소스 코드 상에서 아래와 같은 형식을 모두 제거할 수 있다는 것이다

validation 할 인자에 @Valid나 @Validated 붙여주기만 하고 BindingResult를 따로 잡아주지 않아도 된다

AOP로 공통 처리하기 때문에 bindingResult로 지지고 볶는 로직을 ExceptionHandler에서 처리해주면 된다

if(bindingResult.hasErrors()) {
  log.info("blahblah");
}

 

최근 이직한 곳에 적응 중이라 + 집과 거리가 너무 멀어 개인 프로젝트를 거의 진행하지 못하고 있는데

시간적 여유가 생긴다면 로그 부분도 더 개선시켜봐야겠다

로그는 비즈니스 로직에 있어서는 안 되는 놈이지만 현실적으로는 다양한 조건이 뒤따르기 때문에 

실천하기 어렵고 최대한 줄이는 방향으로 가야겠다

또한 이 방식은 소스 코드에 쌩으로 log 박는 방식보다 성능 저하가 올 수 있음은 확실하고

실제 운영에서는 성능 테스트를 꼭 거친 후에 사용해야 할 것이다

 

내가 애용하는 @Profile로 적용될 profile을 선택해서 AOP를 설정할 수도 있기 때문에

개발 / 테스트 단계에는 막 적용해놓고 운영 단계에서는 정제된 형태로 적용시킬 수도 있을 것이다

댓글
링크
글 보관함
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Total
Today
Yesterday