我正在用spring boot开发REST API。我需要记录所有的请求与输入参数(与方法,例如。GET, POST等),请求路径,查询字符串,此请求对应的类方法,以及此操作的响应,包括成功和错误。例如:

成功的要求:

http://example.com/api/users/1

Log应该是这样的:

{
   HttpStatus: 200,
   path: "api/users/1",
   method: "GET",
   clientIp: "0.0.0.0",
   accessToken: "XHGu6as5dajshdgau6i6asdjhgjhg",
   method: "UsersController.getUser",
   arguments: {
     id: 1 
   },
   response: {
      user: {
        id: 1,
        username: "user123",
        email: "user123@example.com"   
      }
   },
   exceptions: []       
}

或请求错误:

http://example.com/api/users/9999

Log应该是这样的:

{
   HttpStatus: 404,
   errorCode: 101,                 
   path: "api/users/9999",
   method: "GET",
   clientIp: "0.0.0.0",
   accessToken: "XHGu6as5dajshdgau6i6asdjhgjhg",
   method: "UsersController.getUser",
   arguments: {
     id: 9999 
   },
   returns: {            
   },
   exceptions: [
     {
       exception: "UserNotFoundException",
       message: "User with id 9999 not found",
       exceptionId: "adhaskldjaso98d7324kjh989",
       stacktrace: ...................    
   ]       
}

我希望Request/Response是一个单独的实体,在成功和错误的情况下都具有与该实体相关的自定义信息。

春季实现这一目标的最佳做法是什么,可能是使用过滤器吗?如果是,能否提供具体的例子?

我使用过@ControllerAdvice和@ExceptionHandler,但正如我提到的,我需要在一个地方(和单个日志)处理所有成功和错误请求。


当前回答

下面是我如何在春季数据休息 通过使用 org.springframework.web.util.ContentCachingRequestWrapper和 org.springframework.web.util.ContentCachingResponseWrapper

/**
 * Doogies very cool HTTP request logging
 *
 * There is also {@link org.springframework.web.filter.CommonsRequestLoggingFilter}  but it cannot log request method
 * And it cannot easily be extended.
 *
 * https://mdeinum.wordpress.com/2015/07/01/spring-framework-hidden-gems/
 * http://stackoverflow.com/questions/8933054/how-to-read-and-copy-the-http-servlet-response-output-stream-content-for-logging
 */
public class DoogiesRequestLogger extends OncePerRequestFilter {

  private boolean includeResponsePayload = true;
  private int maxPayloadLength = 1000;

  private String getContentAsString(byte[] buf, int maxLength, String charsetName) {
    if (buf == null || buf.length == 0) return "";
    int length = Math.min(buf.length, this.maxPayloadLength);
    try {
      return new String(buf, 0, length, charsetName);
    } catch (UnsupportedEncodingException ex) {
      return "Unsupported Encoding";
    }
  }

  /**
   * Log each request and respponse with full Request URI, content payload and duration of the request in ms.
   * @param request the request
   * @param response the response
   * @param filterChain chain of filters
   * @throws ServletException
   * @throws IOException
   */
  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    StringBuffer reqInfo = new StringBuffer()
     .append("[")
     .append(startTime % 10000)  // request ID
     .append("] ")
     .append(request.getMethod())
     .append(" ")
     .append(request.getRequestURL());

    String queryString = request.getQueryString();
    if (queryString != null) {
      reqInfo.append("?").append(queryString);
    }

    if (request.getAuthType() != null) {
      reqInfo.append(", authType=")
        .append(request.getAuthType());
    }
    if (request.getUserPrincipal() != null) {
      reqInfo.append(", principalName=")
        .append(request.getUserPrincipal().getName());
    }

    this.logger.debug("=> " + reqInfo);

    // ========= Log request and response payload ("body") ========
    // We CANNOT simply read the request payload here, because then the InputStream would be consumed and cannot be read again by the actual processing/server.
    //    String reqBody = DoogiesUtil._stream2String(request.getInputStream());   // THIS WOULD NOT WORK!
    // So we need to apply some stronger magic here :-)
    ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
    ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);

    filterChain.doFilter(wrappedRequest, wrappedResponse);     // ======== This performs the actual request!
    long duration = System.currentTimeMillis() - startTime;

    // I can only log the request's body AFTER the request has been made and ContentCachingRequestWrapper did its work.
    String requestBody = this.getContentAsString(wrappedRequest.getContentAsByteArray(), this.maxPayloadLength, request.getCharacterEncoding());
    if (requestBody.length() > 0) {
      this.logger.debug("   Request body:\n" +requestBody);
    }

    this.logger.debug("<= " + reqInfo + ": returned status=" + response.getStatus() + " in "+duration + "ms");
    if (includeResponsePayload) {
      byte[] buf = wrappedResponse.getContentAsByteArray();
      this.logger.debug("   Response body:\n"+getContentAsString(buf, this.maxPayloadLength, response.getCharacterEncoding()));
    }

    wrappedResponse.copyBodyToResponse();  // IMPORTANT: copy content of response back into original response

  }


}

其他回答

如果您不介意尝试Spring AOP,这是我一直在探索的日志目的,它对我来说工作得很好。它不会记录未定义的请求和失败的请求尝试。

添加这三个依赖项

spring-aop, aspectjrt, aspectjweaver

将此添加到xml配置文件<aop:aspectj-autoproxy/>

创建一个可以用作切入点的注释

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface EnableLogging {
ActionType actionType();
}

现在注释你想要记录的所有API方法

@EnableLogging(actionType = ActionType.SOME_EMPLOYEE_ACTION)
@Override
public Response getEmployees(RequestDto req, final String param) {
...
}

现在来看方面。组件—扫描这个类所在的包。

@Aspect
@Component
public class Aspects {

@AfterReturning(pointcut = "execution(@co.xyz.aspect.EnableLogging * *(..)) && @annotation(enableLogging) && args(reqArg, reqArg1,..)", returning = "result")
public void auditInfo(JoinPoint joinPoint, Object result, EnableLogging enableLogging, Object reqArg, String reqArg1) {

    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
            .getRequest();

    if (result instanceof Response) {
        Response responseObj = (Response) result;

    String requestUrl = request.getScheme() + "://" + request.getServerName()
                + ":" + request.getServerPort() + request.getContextPath() + request.getRequestURI()
                + "?" + request.getQueryString();

String clientIp = request.getRemoteAddr();
String clientRequest = reqArg.toString();
int httpResponseStatus = responseObj.getStatus();
responseObj.getEntity();
// Can log whatever stuff from here in a single spot.
}


@AfterThrowing(pointcut = "execution(@co.xyz.aspect.EnableLogging * *(..)) && @annotation(enableLogging) && args(reqArg, reqArg1,..)", throwing="exception")
public void auditExceptionInfo(JoinPoint joinPoint, Throwable exception, EnableLogging enableLogging, Object reqArg, String reqArg1) {

    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
            .getRequest();

    String requestUrl = request.getScheme() + "://" + request.getServerName()
    + ":" + request.getServerPort() + request.getContextPath() + request.getRequestURI()
    + "?" + request.getQueryString();

    exception.getMessage();
    exception.getCause();
    exception.printStackTrace();
    exception.getLocalizedMessage();
    // Can log whatever exceptions, requests, etc from here in a single spot.
    }
}

@AfterReturning建议在匹配的方法执行返回时运行 正常。 @ afterthrows通知在匹配的方法执行由退出时运行 抛出异常。

如果你想详细阅读,请通读这个。 http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html

不要编写任何拦截器、过滤器、组件、方面等,这是一个非常常见的问题,并且已经解决了很多次。

Spring Boot有一个名为Actuator的模块,它提供了开箱即用的HTTP请求日志记录。有一个端点映射到/trace (SB1.x)或/actuator/httptrace (SB2.0+),它将显示最近100个HTTP请求。您可以自定义它以记录每个请求,或将其写入DB。

要获得您想要的端点,您需要spring-boot-starter-actuator依赖项,还需要将您正在寻找的端点“白名单”,并可能为其设置或禁用安全性。

另外,这个应用程序将在哪里运行?您将使用PaaS吗?托管提供商,例如Heroku,提供请求日志记录作为他们服务的一部分,你不需要做任何编码。

如果你已经配置了Spring引导配置服务器,那么只需要为类启用Debug记录器:

Http11InputBuffer.Http11InputBuffer.java

调试将记录每个请求的所有请求和响应

Note

 @Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
...
}

这种方法不适用于弹簧安全过滤器链。 你必须手动添加CommonsRequestLoggingFilter

protected void configure(HttpSecurity http) throws Exception {
         HttpSecurity filter = http
        .cors().and().addFilterBefore(new CommonsRequestLoggingFilter(), CorsFilter.class);
}

这里是我的解决方案(Spring 2.0.x)

添加maven依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

编辑应用程序。属性,并添加以下行:

management.endpoints.web.exposure.include=* 

一旦你的spring boot应用程序启动,你可以通过调用这个url来跟踪最新的100个http请求: http://localhost:8070/actuator/httptrace