api 를 호출할 때마다 response, request 값의 로그를 log.info() 를 통해 작성하는 것이 비효율적이라고 느껴져서
공통적인 모듈을 사용해서 api 호출할때마다 로그를 안찍어도 출력될 수 있게 사용하고 싶었다.
로그를 남겨서 에러가 발생했을 때 빠른 대처와 api 흐름이 정상 동작하고 있는지에 대해 파악하기에도 좋다.
build.gradle 에 의존성 추가
// aop
implementation 'org.springframework.boot:spring-boot-starter-aop'
//Joiner
implementation 'com.google.guava:guava:31.1-jre'
aop 를 사용하기 위해 의존성을 추가해준다.
코드에서 joiner 를 사용하기 위해선 관련 의존성도 추가한다.
버전은 아래에서 골라서 사용해도 된다.
https://mvnrepository.com/artifact/com.google.guava/guava
어노테이션 추가
@EnableAspectJAutoProxy
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
@EnableAspectJAutoProxy
해당 어노테이션을 사용해서 AOP 를 사용할 수 있게 한다.
해당 어노테이션을 사용하면 스프링이 자동으로 개발자의 메소드를 호출하기 전에 가로챌 수 있도록 하는 기능이다.
코드 작성
LogAspect.java
package com.test.rewards.config.aop;
import com.google.common.base.Joiner;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.stream.Collectors;
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Around("within(com.test.rewards.restapi..*)")
public Object logging(ProceedingJoinPoint pjp) throws Throwable {
String params = getRequestParams();
long startAt = System.currentTimeMillis();
logger.info("----------> REQUEST : {}({}) = {}", pjp.getSignature().getDeclaringTypeName(),
pjp.getSignature().getName(), params);
Object result = pjp.proceed();
long endAt = System.currentTimeMillis();
logger.info("----------> RESPONSE : {}({}) = {} ({}ms)", pjp.getSignature().getDeclaringTypeName(),
pjp.getSignature().getName(), result, endAt-startAt);
return result;
}
// get requset value
private String getRequestParams() {
String params = "";
RequestAttributes requestAttribute = RequestContextHolder.getRequestAttributes();
if(requestAttribute != null){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
Map<String, String[]> paramMap = request.getParameterMap();
if(!paramMap.isEmpty()) {
params = " [" + paramMapToString(paramMap) + "]";
}
}
return params;
}
private String paramMapToString(Map<String, String[]> paramMap) {
return paramMap.entrySet().stream()
.map(entry -> String.format("%s -> (%s)",
entry.getKey(), Joiner.on(",").join(entry.getValue())))
.collect(Collectors.joining(", "));
}
}
@Around("within(com.bearbetter.rewards.restapi..*)")
특정 클래스 안에 있는 메서드들 모두를 지정하는 패턴을 작성할 수 있는 방법이다.
패키지명까지가 아니라 클래스명까지 지정할 수 있는 것을 유의해야한다.
within(패키지.패키지.패키지.패키지.클래스)에서
within 안에 사용될 클래스 범위의 경우
Controller 를 가지고 있는 파일 위치까지 적어주면 된다.
결과 확인
아무 API 나 호출하면 다음과 같은 결과가 나오는 것을 확인할 수 있다.
반응형