본문 바로가기
IT/Spring

RestControllerAdvice 사용하여 로깅

by 봉즙 2023. 9. 25.

@RestControllerAdvice 혹은 @ControllerAdvice 를 사용하여 로깅하는 방법도 존재한다.

Request

import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.core.MethodParameter
import org.springframework.http.HttpInputMessage
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter
import java.lang.reflect.Type

@RestControllerAdvice
class RequestBodyLoggingAdvice : RequestBodyAdviceAdapter() {
    private val logger = KotlinLogging.logger {}

    override fun supports(
        methodParameter: MethodParameter,
        targetType: Type,
        converterType: Class<out HttpMessageConverter<*>>
    ): Boolean {
        return true
    }

    override fun afterBodyRead(
        body: Any,
        inputMessage: HttpInputMessage,
        parameter: MethodParameter,
        targetType: Type,
        converterType: Class<out HttpMessageConverter<*>>
    ): Any {
        logger.info { "[ADVICE Request] ${parameter.method.name} body : $body" }
        return body
    }
}

RequestBodyAdviceAdapter 는 abstract class 이며 RequestBodyAdvice 인터페이스를 구현하였다.

여기서 supports()가 가장 처음에 실행되는 메서드이며 이를 통해 다음으로 실행될 메서드인 beforeBodyRead() 또는 handleEmptyBody() 그리고 afterBodyRead()

실행될지 결정한다.

beforeBodyRead()는 body 를 처리하기 전에 사용되며 현재 찍으려는 로그에는 body 가 포함되어야 하므로 오버라이딩 하지 않았다.

handleEmptyBody()는 비어있는 경우 처리할 로직이기에 역시 따로 오버라이딩 하지 않았다.

afterBodyRead()는 body를 읽고 나서 파라미터로 이번 글에서 원하는 값을 받고 있어서 이 메서드를 오버라이딩 한 후 로그를 추가 해 주었다.

Response

import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.core.MethodParameter
import org.springframework.http.HttpInputMessage
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter
import java.lang.reflect.Type

@RestControllerAdvice
class ResponseBodyLoggingAdvice : ResponseBodyAdvice<Any> {
    private val logger = KotlinLogging.logger {}

    override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>): Boolean {
        return true
    }

    override fun beforeBodyWrite(
        body: Any?,
        returnType: MethodParameter,
        selectedContentType: MediaType,
        selectedConverterType: Class<out HttpMessageConverter<*>>,
        request: ServerHttpRequest,
        response: ServerHttpResponse
    ): Any? {
        logger.info { "[ADVICE Response] body : $body" }
        return body
    }
}

Response 에는 ResponseBodyAdviceAdapter가 존재하지 않아 바로 ResponseBodyAdvice인터페이스를 구현 하여 사용하였다.

supports()beforeBodyWrite() 만 존재하며 supports()RequestBodyAdvice에서 처럼 filter 와 같은 역할을 처리하며

beforeBodyWrite() 에서 원하는 로직을 처리하도록 하였다.

RestControllerAdvice (ControllerAdvice)

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {
    @Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBody advice beans
        initControllerAdviceCache();
        initMessageConverters();

        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.initBinderArgumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }

    private void initControllerAdviceCache() {
        if (getApplicationContext() == null) {
            return;
        }

        List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());

        List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

        for (ControllerAdviceBean adviceBean : adviceBeans) {
            Class<?> beanType = adviceBean.getBeanType();
            if (beanType == null) {
                throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
            }
            Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
            if (!attrMethods.isEmpty()) {
                this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
            }
            Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
            if (!binderMethods.isEmpty()) {
                this.initBinderAdviceCache.put(adviceBean, binderMethods);
            }
            if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
                requestResponseBodyAdviceBeans.add(adviceBean);
            }
        }

        if (!requestResponseBodyAdviceBeans.isEmpty()) {
            this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
        }

        if (logger.isDebugEnabled()) {
            int modelSize = this.modelAttributeAdviceCache.size();
            int binderSize = this.initBinderAdviceCache.size();
            int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
            int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
            if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
                logger.debug("ControllerAdvice beans: none");
            }
            else {
                logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +
                        " @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
            }
        }
    } 
}

@ControllerAdviceRequestMappingHandlerAdapter클래스의 afterPropertiesSet()가 실행된 이후 initControllerAdviceCache()에서

ControllerAdviceBean.findAnnotatedBeans()을 통하여 해당 어노테이션이 달려있는 모든 찾아서 미리 리스트에 등록 해둔 다음 타겟에 해당하는 메서드가 실행 될때 마다 처리하도록 한다.

 

https://github.com/hanbong5938/practice/tree/master/logging

'IT > Spring' 카테고리의 다른 글

스프링의 트랜잭션  (0) 2023.10.12
Spring AOP 분석  (0) 2023.10.10
AOP 사용하여 로깅  (0) 2023.09.25
Interceptor를 사용하여 Request/Reponse Logging  (0) 2023.09.20
Private Method Test 하기  (0) 2023.09.14

댓글