我正在用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,但正如我提到的,我需要在一个地方(和单个日志)处理所有成功和错误请求。


当前回答

@hahn的回答需要一些修改才能为我工作,但这是迄今为止我能得到的最可定制的东西。

它对我不起作用,可能是因为我也有一个HandlerInterceptorAdapter[?? ?但是我一直从那个版本的服务器得到不好的响应。这是我对它的修改。

public class LoggableDispatcherServlet extends DispatcherServlet {

    private final Log logger = LogFactory.getLog(getClass());

    @Override
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

        long startTime = System.currentTimeMillis();
        try {
            super.doDispatch(request, response);
        } finally {
            log(new ContentCachingRequestWrapper(request), new ContentCachingResponseWrapper(response),
                    System.currentTimeMillis() - startTime);
        }
    }

    private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache, long timeTaken) {
        int status = responseToCache.getStatus();
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("httpStatus", status);
        jsonObject.addProperty("path", requestToCache.getRequestURI());
        jsonObject.addProperty("httpMethod", requestToCache.getMethod());
        jsonObject.addProperty("timeTakenMs", timeTaken);
        jsonObject.addProperty("clientIP", requestToCache.getRemoteAddr());
        if (status > 299) {
            String requestBody = null;
            try {
                requestBody = requestToCache.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
            } catch (IOException e) {
                e.printStackTrace();
            }
            jsonObject.addProperty("requestBody", requestBody);
            jsonObject.addProperty("requestParams", requestToCache.getQueryString());
            jsonObject.addProperty("tokenExpiringHeader",
                    responseToCache.getHeader(ResponseHeaderModifierInterceptor.HEADER_TOKEN_EXPIRING));
        }
        logger.info(jsonObject);
    }
}

其他回答

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

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

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

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

日志库是专门为记录HTTP请求和响应而设计的。它使用一个特殊的启动器库支持Spring Boot。

要在Spring Boot中启用日志,您所需要做的就是将库添加到项目的依赖项中。例如,假设您正在使用Maven:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-spring-boot-starter</artifactId>
    <version>1.5.0</version>
</dependency>

默认情况下,日志输出如下所示:

{
  "origin" : "local",
  "correlation" : "52e19498-890c-4f75-a06c-06ddcf20836e",
  "status" : 200,
  "headers" : {
    "X-Application-Context" : [
      "application:8088"
    ],
    "Content-Type" : [
      "application/json;charset=UTF-8"
    ],
    "Transfer-Encoding" : [
      "chunked"
    ],
    "Date" : [
      "Sun, 24 Dec 2017 13:10:45 GMT"
    ]
  },
  "body" : {
    "thekey" : "some_example"
  },
  "duration" : 105,
  "protocol" : "HTTP/1.1",
  "type" : "response"
}

但是它不输出处理请求的类名。该库确实提供了一些用于编写自定义记录器的接口。

笔记

与此同时,库已经有了显著的发展,目前的版本是2.4.1,请参阅https://github.com/zalando/logbook/releases。例如,默认输出格式已经改变,可以配置,过滤等。

不要忘记将日志级别设置为TRACE,否则你将看不到任何东西:

logging:
  level:
    org.zalando.logbook: TRACE

如前所述,Logbook非常适合于此,但在使用Java模块时,由于在Logbook -api和Logbook -core之间有一个分离包,因此在设置它时遇到了一些麻烦。

对于我的Gradle + Spring Boot项目,我需要

build.gradle

dependencies {
    compileOnly group: 'org.zalando', name: 'logbook-api', version: '2.4.1'
    runtimeOnly group: 'org.zalando', name: 'logbook-spring-boot-starter', version: '2.4.1'
    //...
}

logback-spring.xml

<configuration>
    <!-- HTTP Requests and Responses -->
    <logger name="org.zalando.logbook" level="trace" />
</configuration>

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

Http11InputBuffer.Http11InputBuffer.java

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

如果有人还需要它,这里有一个简单的Spring HttpTrace执行器实现。但正如他们告诉上面的那样,它不会把尸体弄沉。

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository;
import org.springframework.stereotype.Repository;

@Slf4j
@Repository
public class LoggingInMemoryHttpTraceRepository extends InMemoryHttpTraceRepository {
    public void add(HttpTrace trace) {
        super.add(trace);
        log.info("Trace:" + ToStringBuilder.reflectionToString(trace));
        log.info("Request:" + ToStringBuilder.reflectionToString(trace.getRequest()));
        log.info("Response:" + ToStringBuilder.reflectionToString(trace.getResponse()));
    }
}