복붙노트

[SPRING] Spring Rest에서 모든 요청 - 응답을 기록하는 방법은 무엇입니까?

SPRING

Spring Rest에서 모든 요청 - 응답을 기록하는 방법은 무엇입니까?

응용 프로그램은 비동기 적으로 (별도의 스레드에서) 클라이언트에 영향을주지 않고 다음 정보를 기록해야합니다.

필터에서 입력 스트림을 소비하면 json과 객체 매핑을 위해 봄에 다시 소비 될 수 없습니다. 객체 매핑에 대한 입력 스트림 중 어딘가에서 로거를 연결할 수 있습니까?

최신 정보:

우리는 MessageConverter에서 로깅 코드를 작성할 수 있지만 좋은 생각은 아닙니다.

public class MyMappingJackson2MessageConverter extends AbstractHttpMessageConverter<Object> {
    ...
    protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        InputStream inputStream = inputMessage.getBody();
        String requestBody = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
        String method = request.getMethod();
        String uri = request.getRequestURI();
        LOGGER.debug("{} {}", method, uri);
        LOGGER.debug("{}", requestBody);
        return objectMapper.readValue(requestBody, clazz);
    }

    protected void writeInternal(Object o, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        String responseBody = objectMapper.writeValueAsString(o);
        LOGGER.debug("{}", responseBody);
        outputMessage.getBody().write(responseBody.getBytes(StandardCharsets.UTF_8));
    }
}

해결법

  1. ==============================

    1.baeldung.com의 답변 :

    baeldung.com의 답변 :

    로깅을 비동기로 만들기 위해 비동기 애 퍼처를 사용할 수 있습니다. 불행히도 로깅 응답 페이로드는 지원하지 않습니다. :(

  2. ==============================

    2.당신은 봄 양상을 사용하여 이것을 달성 할 수있다. @Before, @AfterReturning, @AfterThrowing 등의 주석을 제공합니다. 모든 끝점 로그가 필요하지 않을 수도 있으므로 여기에 패키지 기반의 필터가 있습니다. 여기 예시들이 있습니다 :

    당신은 봄 양상을 사용하여 이것을 달성 할 수있다. @Before, @AfterReturning, @AfterThrowing 등의 주석을 제공합니다. 모든 끝점 로그가 필요하지 않을 수도 있으므로 여기에 패키지 기반의 필터가 있습니다. 여기 예시들이 있습니다 :

    요청 :

    @Before("within(your.package.where.is.endpoint..*)")
        public void endpointBefore(JoinPoint p) {
            if (log.isTraceEnabled()) {
                log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
                Object[] signatureArgs = p.getArgs();
    
    
                ObjectMapper mapper = new ObjectMapper();
                mapper.enable(SerializationFeature.INDENT_OUTPUT);
                try {
    
                    if (signatureArgs[0] != null) {
                        log.trace("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
                    }
                } catch (JsonProcessingException e) {
                }
            }
        }
    
    here `@Before("within(your.package.where.is.endpoint..*)")` has the package path. All endpoints within this package will generate the log.
    

    응답 :

    @AfterReturning(value = ("within(your.package.where.is.endpoint..*)"),
                returning = "returnValue")
        public void endpointAfterReturning(JoinPoint p, Object returnValue) {
            if (log.isTraceEnabled()) {
                ObjectMapper mapper = new ObjectMapper();
                mapper.enable(SerializationFeature.INDENT_OUTPUT);
                try {
                    log.trace("\nResponse object: \n" + mapper.writeValueAsString(returnValue));
                } catch (JsonProcessingException e) {
                    System.out.println(e.getMessage());
                }
                log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
            }
        }
    
    here `@AfterReturning("within(your.package.where.is.endpoint..*)")` has the package path. All endpoints within this package will generate the log. Also Object returnValue has the response.
    

    예외 사항 :

    @AfterThrowing(pointcut = ("within(your.package.where.is.endpoint..*)"), throwing = "e")
    public void endpointAfterThrowing(JoinPoint p, Exception e) throws DmoneyException {
        if (log.isTraceEnabled()) {
            System.out.println(e.getMessage());
    
            e.printStackTrace();
    
    
            log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
        }
    }
    here `@AfterThrowing(pointcut = ("within(your.package.where.is.endpoint..*)"), throwing = "e")`  has the package path. All endpoints within this package will generate the log. Also Exception e has the error response.
    

    전체 코드는 다음과 같습니다.

    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    import org.apache.log4j.Logger;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Order(1)
    @Component
    //@ConditionalOnExpression("${endpoint.aspect.enabled:true}")
    public class EndpointAspect {
        static Logger log = Logger.getLogger(EndpointAspect.class);
    
        @Before("within(your.package.where.is.endpoint..*)")
        public void endpointBefore(JoinPoint p) {
            if (log.isTraceEnabled()) {
                log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
                Object[] signatureArgs = p.getArgs();
    
    
                ObjectMapper mapper = new ObjectMapper();
                mapper.enable(SerializationFeature.INDENT_OUTPUT);
                try {
    
                    if (signatureArgs[0] != null) {
                        log.trace("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
                    }
                } catch (JsonProcessingException e) {
                }
            }
        }
    
        @AfterReturning(value = ("within(your.package.where.is.endpoint..*)"),
                returning = "returnValue")
        public void endpointAfterReturning(JoinPoint p, Object returnValue) {
            if (log.isTraceEnabled()) {
                ObjectMapper mapper = new ObjectMapper();
                mapper.enable(SerializationFeature.INDENT_OUTPUT);
                try {
                    log.trace("\nResponse object: \n" + mapper.writeValueAsString(returnValue));
                } catch (JsonProcessingException e) {
                    System.out.println(e.getMessage());
                }
                log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
            }
        }
    
    
        @AfterThrowing(pointcut = ("within(your.package.where.is.endpoint..*)"), throwing = "e")
        public void endpointAfterThrowing(JoinPoint p, Exception e) throws Exception {
            if (log.isTraceEnabled()) {
                System.out.println(e.getMessage());
    
                e.printStackTrace();
    
    
                log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
            }
        }
    }
    

    AOP에 대한 자세한 정보는 여기를 방문하십시오 :

    AOP에 관한 스프링 부두

    AOP에 관한 샘플 기사

  3. ==============================

    3.가장 좋은 방법은 비동기 메서드에서 로깅을하는 것입니다.

    가장 좋은 방법은 비동기 메서드에서 로깅을하는 것입니다.

    @Async
    public void asyncMethodWithVoidReturnType() {
      System.out.println("Execute method asynchronously. "
        + Thread.currentThread().getName());
    }
    

    다음을 참조하십시오 :

    비동기

    비동기 방법

  4. ==============================

    4.LoggingFilter와 Async 지원은 Spring의 2 가지 요소를 사용합니다. 첫 번째 경우, CommonsRequestLoggingFilter를 사용합니다. CommonsRequestLoggingFilter는 이미 HTTP 요청을 가로 채고이를위한 구성과 Async를 구성하는 방법을 이미 알고 있습니다. 다음과 같이 할 수 있습니다.

    LoggingFilter와 Async 지원은 Spring의 2 가지 요소를 사용합니다. 첫 번째 경우, CommonsRequestLoggingFilter를 사용합니다. CommonsRequestLoggingFilter는 이미 HTTP 요청을 가로 채고이를위한 구성과 Async를 구성하는 방법을 이미 알고 있습니다. 다음과 같이 할 수 있습니다.

    먼저 비동기 지원 활성화

    @Configuration
    @EnableAsync
    public class SpringAsyncConfig { ... }
    

    그런 다음 loggingFilter를 만듭니다.

    public class LoggingFilter extends CommonsRequestLoggingFilter {
    
    @Override
    protected void beforeRequest(final HttpServletRequest request, final String message) {
        // DO something
        myAsyncMethodRequest(request, message)
    }
    
    @Override
    protected void afterRequest(final HttpServletRequest request, final String message) {
        // Do something
       myAsyncMethodResponse(request, message)
    }
    
    // -----------------------------------------
    // Async Methods
    // -----------------------------------------
    
       @Async
       protected void myAsyncMethodRequest(HttpServletRequest request, String message) {
    
        // Do your thing
        // You can use message that has a raw message from the properties
       // defined in the logFilter() method. 
       // Also you can extract it from the HttpServletRequest using: 
       // IOUtils.toString(request.getReader());
    
       }
    
       @Async
       protected void myAsyncMethodResponse(HttpServletRequest request, String message) {
    
        // Do your thing
       }
    
    }
    

    그런 다음 만든 필터에 대한 사용자 지정 로깅 구성을 만듭니다.

    @Configuration
    public class LoggingConfiguration {
    
        @Bean
        public LoggingConfiguration logFilter() {
            LoggingFilter filter
                    = new LoggingFilter();
            filter.setIncludeQueryString(true);
            filter.setIncludePayload(true);
            filter.setIncludeHeaders(true);
    
            return filter;
        }
    }
    

    요청에서 데이터를 추출하려면 message 매개 변수를 사용하거나 HttpServletRequest를 처리 할 수 ​​있습니다. 예를 들면 다음과 같습니다.

  5. from https://stackoverflow.com/questions/48301764/how-to-log-all-the-request-responses-in-spring-rest by cc-by-sa and MIT license