SpringBoot记录Http请求日志的方法

所属分类: 软件编程 / java 阅读数: 23
收藏 0 赞 0 分享

在使用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"
  }
}

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

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

更多精彩内容其他人还在看

SpringBoot环境搭建及第一个程序运行(小白教程)

这篇文章主要介绍了SpringBoot环境搭建及第一个程序运行,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

过滤器 和 拦截器的 6个区别(别再傻傻分不清了)

这篇文章主要介绍了过滤器 和 拦截器的 6个区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

SpringBoot整合SpringTask实现定时任务的流程

这篇文章主要介绍了SpringBoot整合SpringTask实现定时任务的流程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

vscode快速引入第三方jar包发QQ邮件

这篇文章主要介绍了vscode快速引入第三方jar包发QQ邮件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

Java Enum和String及int的相互转化示例

这篇文章主要介绍了Java Enum和String及int的相互转化示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

Spring boot如何快速的配置多个Redis数据源

这篇文章主要介绍了Spring boot如何快速的配置多个Redis数据源,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

JAVA 对接腾讯云直播的实现

这篇文章主要介绍了JAVA 对接腾讯云直播的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

JavaSE static final及abstract修饰符实例解析

这篇文章主要介绍了JavaSE static final及abstract修饰符实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
收藏 0 赞 0 分享

SpringBoot定时任务参数运行代码实例解析

这篇文章主要介绍了SpringBoot定时任务运行代码实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Spring Boot调用 Shell 脚本实现看门狗功能

这篇文章主要介绍了Spring Boot调用 Shell 脚本实现看门狗功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享
查看更多