Spring Boot 记录 Http 日志

在使用Spring Boot开发 web api 的时候但愿把 requestrequest headerresponse reponse header , uri, method 等等的信息记录到咱们的日志中,方便咱们排查问题,也能对系统的数据作一些统计。java

Spring 使用了 DispatcherServlet 来拦截并分发请求,咱们只要本身实现一个 DispatcherServlet 并在其中对请求和响应作处理打印到日志中便可。web

咱们实现一个本身的分发 Servlet ,它继承于 DispatcherServlet,咱们实现本身的 doDispatch(HttpServletRequest request, HttpServletResponse response) 方法。shell

public class LoggableDispatcherServlet extends DispatcherServlet {

    private static final Logger logger = LoggerFactory.getLogger("HttpLogger");

    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
        //建立一个 json 对象,用来存放 http 日志信息
        ObjectNode rootNode = mapper.createObjectNode();
        rootNode.put("uri", requestWrapper.getRequestURI());
        rootNode.put("clientIp", requestWrapper.getRemoteAddr());
        rootNode.set("requestHeaders", mapper.valueToTree(getRequestHeaders(requestWrapper)));
        String method = requestWrapper.getMethod();
        rootNode.put("method", method);
        try {
            super.doDispatch(requestWrapper, responseWrapper);
        } finally {
            if(method.equals("GET")) {
                rootNode.set("request", mapper.valueToTree(requestWrapper.getParameterMap()));
            } else {
                JsonNode newNode = mapper.readTree(requestWrapper.getContentAsByteArray());
                rootNode.set("request", newNode);
            }

            rootNode.put("status", responseWrapper.getStatus());
            JsonNode newNode = mapper.readTree(responseWrapper.getContentAsByteArray());
            rootNode.set("response", newNode);

            responseWrapper.copyBodyToResponse();

            rootNode.set("responseHeaders", mapper.valueToTree(getResponsetHeaders(responseWrapper)));
            logger.info(rootNode.toString());
        }
    }

    private Map<String, Object> getRequestHeaders(HttpServletRequest request) {
        Map<String, Object> headers = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
        return headers;

    }

    private Map<String, Object> getResponsetHeaders(ContentCachingResponseWrapper response) {
        Map<String, Object> headers = new HashMap<>();
        Collection<String> headerNames = response.getHeaderNames();
        for (String headerName : headerNames) {
            headers.put(headerName, response.getHeader(headerName));
        }
        return headers;
    }
复制代码

LoggableDispatcherServlet 中,咱们能够经过 HttpServletRequest 中的 InputStreamreader 来获取请求的数据,但若是咱们直接在这里读取了流或内容,到后面的逻辑将没法进行下去,因此须要实现一个能够缓存的 HttpServletRequest。好在 Spring 提供这样的类,就是 ContentCachingRequestWrapperContentCachingResponseWrapper, 根据官方的文档这两个类正好是来干这个事情的,咱们只要将 HttpServletRequestHttpServletResponse 转化便可。json

HttpServletRequest wrapper that caches all content read from the input stream and reader, and allows this content to be retrieved via a byte array.api

Used e.g. by AbstractRequestLoggingFilter. Note: As of Spring Framework 5.0, this wrapper is built on the Servlet 3.1 API.缓存

HttpServletResponse wrapper that caches all content written to the output stream and writer, and allows this content to be retrieved via a byte array. Used e.g. by ShallowEtagHeaderFilter. Note: As of Spring Framework 5.0, this wrapper is built on the Servlet 3.1 API.bash

实现好咱们的 LoggableDispatcherServlet后,接下来就是要指定使用 LoggableDispatcherServlet 来分发请求。app

@SpringBootApplication
public class SbDemoApplication implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(SbDemoApplication.class, args);
    }
    @Bean
    public ServletRegistrationBean dispatcherRegistration() {
        return new ServletRegistrationBean(dispatcherServlet());
    }
    @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet() {
        return new LoggableDispatcherServlet();
    }
}
复制代码

增长一个简单的 Controller 来测试一下curl

@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping(value = "/word", method = RequestMethod.POST)
    public Object hello(@RequestBody Object object) {
        return object;
    }
}
复制代码

使用 curl 发送一个 Post 请求:ide

$ curl --header "Content-Type: application/json" \
  --request POST \
  --data '{"username":"xyz","password":"xyz"}' \
  http://localhost:8080/hello/word
{"username":"xyz","password":"xyz"}
复制代码

查看打印的日志:

{
    "uri":"/hello/word",
    "clientIp":"0:0:0:0:0:0:0:1",
    "requestHeaders":{
        "content-length":"35",
        "host":"localhost:8080",
        "content-type":"application/json",
        "user-agent":"curl/7.54.0",
        "accept":"*/*"
    },
    "method":"POST",
    "request":{
        "username":"xyz",
        "password":"xyz"
    },
    "status":200,
    "response":{
        "username":"xyz",
        "password":"xyz"
    },
    "responseHeaders":{
        "Content-Length":"35",
        "Date":"Sun, 17 Mar 2019 08:56:50 GMT",
        "Content-Type":"application/json;charset=UTF-8"
    }
}
复制代码

固然打印出来是在一行中的,我进行了一下格式化。咱们还能够在日志中增长请求的时间,耗费的时间以及异常信息等。

相关文章
相关标签/搜索