공부/Spring

[spring] api 호출 시 response, request 값 로그에 출력하기 - AOP 활용한 logging 출력

작은소행성 2023. 3. 20. 11:11

 

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 나 호출하면 다음과 같은 결과가 나오는 것을 확인할 수 있다. 

 

반응형