SpringBoot记录Http请求日志的方法

 更新时间:2019年03月18日 14:53:28   作者:Simeone_xu  
这篇文章主要介绍了SpringBoot记录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 转化即可。

HttpServletRequest wrapper that caches all content read from the input stream and reader, and allows this content to be retrieved via a byte array.
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"
  }
}

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 详解Java子线程异常时主线程事务如何回滚

    详解Java子线程异常时主线程事务如何回滚

    如果主线程向线程池提交了一个任务,如果执行这个任务过程中发生了异常,如何让主线程捕获到该异常并且进行事务的回滚?本篇文章带给你答案
    2022-03-03
  • 解决JDK版本冲突显示问题(双版本冲突)

    解决JDK版本冲突显示问题(双版本冲突)

    这篇文章主要介绍了解决JDK版本冲突显示问题(双版本冲突),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • 基于Java快速实现一个简单版的HashMap详解

    基于Java快速实现一个简单版的HashMap详解

    这篇文章主要为大家详细介绍了如何利用Java简单实现一个底层数据结构为数组 + 链表的HashMap,不考虑链表长度超过8个时变为红黑树的情况,需要的可以参考一下
    2023-02-02
  • 深入解析Java的Servlet过滤器的原理及其应用

    深入解析Java的Servlet过滤器的原理及其应用

    这篇文章主要介绍了深入解析Java的Servlet过滤器的原理及应用,Java编写的Servlet通常是一个与网页一起作用于浏览器客户端的程序,需要的朋友可以参考下
    2016-01-01
  • 详解Maven optional关键字透彻图解

    详解Maven optional关键字透彻图解

    这篇文章主要介绍了详解Maven optional关键字透彻图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • Java并发编程创建并运行线程的方法对比

    Java并发编程创建并运行线程的方法对比

    这篇文章主要为大家详细介绍了Java并发编程创建并运行线程的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • 安装多个jdk导致eclipse打不开问题解决方案

    安装多个jdk导致eclipse打不开问题解决方案

    这篇文章主要介绍了安装多个jdk导致eclipse打不开问题解决方案,帮助大家更好的理解和使用eclipse,感兴趣的朋友可以了解下
    2020-11-11
  • GC调优实战之高分配速率High Allocation Rate

    GC调优实战之高分配速率High Allocation Rate

    这篇文章主要为大家介绍了GC调优之高分配速率High Allocation Rate的实战示例分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-01-01
  • servlet实现文件上传与下载功能

    servlet实现文件上传与下载功能

    这篇文章主要为大家详细介绍了servlet实现文件上传与下载功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-03-03
  • Java集合操作之List接口及其实现方法详解

    Java集合操作之List接口及其实现方法详解

    这篇文章主要介绍了Java集合操作之List接口及其实现方法,详细分析了Java集合操作中List接口原理、功能、用法及操作注意事项,需要的朋友可以参考下
    2015-07-07

最新评论