Spring AOP를 이용한 logging

murkgom·2023년 2월 15일
0

1. 목표

Controller 단의 각 메서드 시작, 끝에 파라미터와 리턴값을 logging하자

2. AOP 적용 전

in SampleController

@Slf4j
@RestController
@RequiredArgsConstructor
public class SampleController {
    private final SampleService sampleService;

    @GetMapping("/sample/request")
    public String request(RequestDto dto) {
    	log.info("###Start request {}", "request");
        log.info("\t{}", dto.toString());
        
        sampleService.sampleRun(dto.getId());
		String result = "ok";
        
        log.info("###End request {}", "request");
        log.info("\t{}", result);

        return result;
    }
    
    @GetMapping("/sample/request2")
    public String request2(RequestDto dto) {
    	log.info("###Start request {}", "request2");		//중복
        log.info("\t{}", dto.toString());					//중복
        
        sampleService.sampleRun2(dto.getId());
		String result = "ok2";
        
        log.info("###End request {}", "request2");			//중복
        log.info("\t{}", result);							//중복

        return result;
    }
}
  • @Slf4j 의존성 추가 필요
  • 각 메서드에 log 찍는 코드를 계속 추가해줘야 한다. 메서드 개수가 늘어날 때마다!
  • 메서드명을 log 코드에 추가해줘야 한다. Java의 Reflection을 사용할 수 있긴 하지만...
  • return값을 logging하기 위해 변수를 추가해줘야 한다(String result).

3. AOP 적용 후 미리보기

@RestController
@RequiredArgsConstructor
public class SampleController {
    private final SampleService sampleService;

    @GetMapping("/sample/request")
    public String request(RequestDto dto) {
    	sampleService.sampleRun(dto.getId());
		
        return "ok";
    }
    
    @GetMapping("/sample/request2")
    public String request2(RequestDto dto) {
    	sampleService.sampleRun2(dto.getId());
        
        return "ok2";
    }
}
  • 속이 후련하다! 얼른 AOP를 적용해보자

4. AOP Aspect 사용 준비하기

4.1. dependency 추가

implementation 'org.springframework.boot:spring-boot-starter-aop'

4.2. @EnableAspectJAutoProxy 추가

Application 클래스 혹은 @Configuration 추가한 클래스에 추가하기

5. Aspect 설정

5.1. 클래스 생성

@Component
@Aspect
@Slf4j
public class LogAspect {
	//...
}
  • @Slf4j : logger 사용을 위한 lombok 어노테이션. LoggerFactory를 이용해서 선언해도 됨

5.2. @Pointcut

: 특정 기능을 적용시킬 그룹을 설정

@Pointcut("within(com.example.log_prac.controller.*)") // 패키지 범위 설정
public void controller() {}

5.3. @Before

: 메서드가 실행되기 전에 동작

@Before("controller()")
public void beforeRequest(JoinPoint joinPoint) {
    log.info("###Start request {}", joinPoint.getSignature().toShortString());
    Arrays.stream(joinPoint.getArgs())
            .map(Object::toString)
            .map(str -> "\t" + str)
            .forEach(log::info);
}
  • JoinPoint::getSigniture : 호출된 메서드 정보를 담은 객체를 호출
  • JoinPoint::getArgs : 호출된 메서드의 Arguments들을 호출

5.4. @AfterReturning

: 메서드가 return값을 반환 후 동작

@AfterReturning(pointcut = "controller()", returning = "returnValue")
public void afterReturningLogging(JoinPoint joinPoint, Object returnValue) {
    log.info("###End request {}", joinPoint.getSignature().toShortString());

    if (returnValue == null) return;

    log.info("\t{}", returnValue.toString());
}
  • returing : 리턴 값을 담을 변수명 선언

5.5. @AfterThrowing

: Exception 발생 후 동작

@AfterThrowing(pointcut = troller()", throwing = "e")
public void afterThrowingLogging(JoinPoint joinPoint, Exception e) {
    log.error("###Occured error in request {}", joinPoint.getSignature().toShortString());
    log.error("\t{}", e.getMessage());
}
  • throwing : Exception 정보를 담을 변수명 선언

0개의 댓글