如何在春假中记录所有请求 - 响应?

时间:2018-01-17 12:57:28

标签: spring rest spring-boot logging

应用程序应该在不影响客户端的情况下(在单独的线程中)记录以下信息。

  • 请求HTTP方法和URI
  • 请求标头(默认设置除外)
  • 客户的IP地址
  • 请求处理时间(以毫秒为单位)
  • 申请团体
  • 回复正文

如果我们在过滤器中消耗inputstream,那么它不能再被spring用于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));
    }
}

4 个答案:

答案 0 :(得分:3)

来自baeldung.com的答案:

  

Spring提供了一个内置的有效负载日志解决方案。我们可以用   通过使用插入Spring应用程序的现成过滤器   组态。 AbstractRequestLoggingFilter是一个过滤器   提供日志记录的基本功能。子类应该覆盖   beforeRequest()afterRequest()方法来执行实际操作   记录请求。 Spring框架提供以下内容   具体的实现类,可用于记录传入   请求。这些是:

           

可以通过添加bean定义来启用请求来配置Spring Boot应用程序   日志记录:

@Configuration
public class RequestLoggingFilterConfig {

    @Bean
    public CommonsRequestLoggingFilter logFilter() {
        CommonsRequestLoggingFilter filter
          = new CommonsRequestLoggingFilter();
        filter.setIncludeQueryString(true);
        filter.setIncludePayload(true);
        filter.setMaxPayloadLength(10000);
        filter.setIncludeHeaders(false);
        filter.setAfterMessagePrefix("REQUEST DATA : ");
        return filter;
    }
}
     

此外,此日志记录筛选器要求将日志级别设置为DEBUG。在   application.properties

logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG

要使日志记录异步,我们可以使用asynchronous appenders。不幸的是,它不支持记录响应有效负载。 :(

答案 1 :(得分:1)

您可以使用弹簧方面实现此目的。它为您提供了一些注释:@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的更多信息,请访问:

Spring docks about AOP

Sample article about AOP

答案 2 :(得分:0)

我想你最好的选择是在异步方法中进行日志记录。

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

请参阅:

Async

How to Async

答案 3 :(得分:0)

我会使用2个元素:来自Spring的LoggingFilterAsync支持。对于第一个,我将使用已经知道如何拦截HTTP请求的 CommonsRequestLoggingFilter ,为其创建配置和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。举个例子: