Programming/Spring Boot

Spring AOP를 활용한 모든 Request에 Log 남기기

빈쿵바라기 2023. 3. 29. 19:00

Controller에서 RequestMapping된 모든 요청에 로그를 남기고 싶은데, 메소드 하나하나에 로그를 찍는 것은 너무 생산성이 떨어지기에 Spring AOP를 활용하여 로그를 남겼던 방법을 기록합니다.

 

1. Dependency 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

 

2. @Aspect 클래스 작성

import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Component
public class LoggingAspect {

	// om.ocko.myproject.controller 이하 패키지의 모든 클래스 이하 모든 메서드에 적용
	@Pointcut("execution(* com.ocko.myproject.controller..*.*(..))")
	private void onRequest() {
	}

	// Pointcut에 의해 필터링된 경로로 들어오는 경우 메서드 호출 전에 적용
	@Before("onRequest()")
	public void beforeParameterLog(JoinPoint joinPoint) {
		Class clazz = joinPoint.getTarget().getClass();
		Logger logger = LoggerFactory.getLogger(clazz);
		
		HttpServletRequest request = 
		        ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

		logger.info("Request : {} {} {}", request.getMethod(), request.getRequestURI(), params(joinPoint).toString());
	}

	// Poincut에 의해 필터링된 경로로 들어오는 경우 메서드 리턴 후에 적용
	@AfterReturning(value = "onRequest()", returning = "returnObj")
	public void afterReturnLog(JoinPoint joinPoint, Object returnObj) {
		Class clazz = joinPoint.getTarget().getClass();
		Logger logger = LoggerFactory.getLogger(clazz);
		
		HttpServletRequest request = 
		        ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

		logger.info("Response {} : {}", request.getRequestURI(), returnObj);
	}


	/* printing request parameter or request body */
	private Map params(JoinPoint joinPoint) {
		CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
		String[] parameterNames = codeSignature.getParameterNames();
		Object[] args = joinPoint.getArgs();
		Map<String, Object> params = new HashMap<>();
		for (int i = 0; i < parameterNames.length; i++) {
			params.put(parameterNames[i], args[i]);
		}
		return params;
	}
}

 

AOP 주요 개념

  • Aspect : 흩어진 관심사를 모듈화 한 것
  • Advice : 실질적인 부가기능을 담은 구현체 (@Before, @After, @AfterReturning, @AfterThrowing, @Around)
  • JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점.
  • PointCut : JointPoint의 상세한 스펙을 정의한 것.

 

3. 실행결과

2023-03-29 18:58:02.491  INFO 162004 --- [nio-8081-exec-3] i.s.k.p.controller.AdminApiController    : Request : GET /api/a/board {id=165}
2023-03-29 18:58:02.521  INFO 162004 --- [nio-8081-exec-3] i.s.k.p.controller.AdminApiController    : Response /api/a/board : {id=165, title=hello, content=hello world}

 

Rererence

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/EnableAspectJAutoProxy.html