Web/spring-boot

[Spring] AOP 와 커스텀 어노테이션을 활용한 Logging

부에나온다 2022. 9. 5. 16:34

1.  Spring AOP (Aspect Oriented Programming)

  • AOP는 관점지향 프로그래밍이라고 불린다.
  • 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점을 나눠보고, 그 관점을 기준으로 각각 모듈화 하는 것이다.
  • 예를 들면 비즈니스 로직에서 핵심 로직이 아닐 수 있는 로깅, 보안 등과 같은 내용을 AOP를 통해 분리시키는 것

2.  Spring AOP의 주요 용어

  • Aspect : AOP를 통해 분리시켜 모듈화 한것
  • Target : Aspect를 적용 할 수 있는곳( Class, Method 등)
  • Advice : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체
  • JoinPoint : Advice가 적용 될 위치, 끼어들 수 있는 지점. 메서드 진입 지점. 생성자 호출 지점. 필드에서 값을 꺼내 올때                                       등 다양한 시점에서 적용 가능
  • PointCut : JointPoin의 상세한ㄴ 스펙을 정의한 것, 'A란 메서드의 진입시점에 호출할 것' 과 같은 더욱 구체적인 Advice                                  실행될 지점을 정할 수 있음

3.  AOP와 커스텀 어노테이션을 활용한 Logging

  • 로깅하지 않을 곳은 커스텀 어노테이션을 생성하여 어노테이션을 붙인곳은 로깅이 되지 않도록 한다.
  • @NoLogging 커스텀 어노테이션 생성
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoLogging {
}
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Resource
    Tracer tracer;

    @Around("within(kr.co.lunasoft.smartstorecoreapi.controller.*) " + "&& !@annotation(kr.co.lunasoft.smartstorecoreapi.common.NoLogging)")
    public Object logging(ProceedingJoinPoint pjp) throws Throwable {

        String params = getRequestParams();

        long startAt = System.currentTimeMillis();
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
                .getRequest();

        log.info("[{}] " + LogbackKafkaFilter.KAFKA_LOG_FILTER + " REQUEST {} [{}] [{}]", method.getName(), params, request.getRequestURI(), request.getRemoteHost());

        Object result = pjp.proceed();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();

        long endAt = System.currentTimeMillis();

        log.info("[{}] " + LogbackKafkaFilter.KAFKA_LOG_FILTER + " RESPONSE [status : {}] ({}ms)", method.getName(), response.getStatus(), endAt-startAt);
        response.setHeader("zipkinId", tracer.currentSpan().context().traceIdString());
        return result;

    }
}
  • 전체 코드를 남길수 없지만 예를 들어서 설명을 하면,
  • 먼저 AOP를 적용하기 위해서는 해당 클래스를 @Component 선언하여 Bean을 등록해주고,
  • @Aspect 선언하여 이 클래스가 Aspect를 나타내는 (AOP를 적용하겠다는) 클래스라는 것을 명시해줍니다.
  • 다음은 타깃 메서드의 Aspect 실행 시점을 지정할 수 있는 어노테이션을 클래스에 선언해줍니다.
    • @Around(메소드 실행 전후) : 어드바이스가 타깃메서드를 감싸서 타깃 메서드 호출 전과 후에 어드바이스 기능을 수행
    • @Before(메소드 실행 전) : 타깃메서드 실행 전 어드바이스 기능을 수행
    • @After(메소드 실행 후) : 타깃메서드 실행 후 어드바이스 기능을 수행
    • @AfterReturning(정상적 반환 이후) : 타깃메서드가 성공적으로 결과값을 반환 후에 어드바이스 기능을 수행
    • @AfterThrowing(예외 발생 시) : 타깃메서드가 수행 중 예외를 던지게 되면 어드바이스 기능을 수행
  • !@annotation 을 선언하여 @NoLogging이 아닐때 어드바이스가 실행되도록 선언해줬습니다.
  • within(컨트롤러) 해당 컨트롤러로 호출시 어드바이스 실행 완료되고 어드바이스를 실행하게 해줘서 로그를 남기도록 해줬습니다.
  • 저희는 MSA 를 적용하고 있어서 traceId를 추가하여 로깅을 좀더 효율적으로 할 수 있도록 해줬습니다.