Spring Boot 记录 Http 请求日志

在使用 Spring Boot
开发 web api
的时候希望request
request header
response
reponse header
, uri
, method
等等的信息记录到我们的日志中,方便我们排查问题,也能对系统的数据做一些统计

Spring
使用了 DispatcherServlet
来拦截并分发请求,我们只要自己实现一个 DispatcherServlet
并在其中对请求和响应做处理打印到日志中即可。

我们实现一个自己的分发 Servlet
,它继承于 DispatcherServlet
,我们实现自己的 doDispatch(HttpServletRequest request, HttpServletResponse response)
方法。

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
中的 InputStream
reader
来获取请求的数据,但如果我们直接在这里读取了流或内容,到后面的逻辑将无法进行下去,所以需要实现一个可以缓存HttpServletRequest
。好在 Spring
提供这样的类,就是 ContentCachingRequestWrapper
ContentCachingResponseWrapper
, 根据官方的文档这两个类正好是来干这个事情的,我们只要将 HttpServletRequest
HttpServletResponse
转化即可。

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.

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

@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
测试一下

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

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

使用 curl
发送一个 Post
请求:

$ 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"
    }
}

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

原文 

https://segmentfault.com/a/1190000018535456

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Spring Boot 记录 Http 请求日志

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址