SpringBoot记录Http请求日志的方法

所属分类: 软件编程 / java 阅读数: 41
收藏 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"
  }
}

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

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

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

java实现背单词程序

这篇文章主要为大家详细介绍了java实现背单词程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

java实现单词查询小程序

这篇文章主要为大家详细介绍了java实现单词查询小程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Java程序开发环境配置图文教程

这篇文章主要为大家详细介绍了Java程序开发环境配置图文教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

详解ssh框架原理及流程

在本文中小编给大家整理的是关于ssh框架原理及流程的相关知识点内容,有此需要的朋友们可以学习下。
收藏 0 赞 0 分享

Java实现弹窗效果的基本操作

这篇文章主要为大家详细介绍了Java实现弹窗效果的基本操作,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

详解springmvc常用5种注解

在本篇里我们给大家总结了关于springmvc常用5种注解相关知识点以及实例代码,需要的朋友们参考下。
收藏 0 赞 0 分享

Java实现弹窗效果的基本操作(2)

这篇文章主要为大家详细介绍了Java实现弹窗效果的基本操作第二篇,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Spring Boot假死诊断实战记录

这篇文章主要给大家介绍了关于Spring Boot假死诊断的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring Boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
收藏 0 赞 0 分享

Java计时新姿势StopWatch详解

这篇文章主要介绍了Java计时新姿势StopWatch,最近公司来了个大佬,从他那里学到不少东西,其中一个就是计时的新姿势「StopWatch」,需要的朋友可以参考下
收藏 0 赞 0 分享

java实现点击按钮弹出新窗体功能

这篇文章主要为大家详细介绍了java实现点击按钮弹出新窗体功能,旧窗体不进行操作,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享
查看更多