资讯专栏INFORMATION COLUMN

Spring Boot 记录 Http 请求日志

SmallBoyO / 1253人阅读

摘要:在使用开发的时候希望把,,等等的信息记录到我们的日志中,方便我们排查问题,也能对系统的数据做一些统计。使用了来拦截并分发请求,我们只要自己实现一个并在其中对请求和响应做处理打印到日志中即可。

在使用Spring Boot开发 web api 的时候希望把 requestrequest headerresponse 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 getRequestHeaders(HttpServletRequest request) {
        Map headers = new HashMap<>();
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
        return headers;

    }

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

LoggableDispatcherServlet 中,我们可以通过 HttpServletRequest 中的 InputStreamreader 来获取请求的数据,但如果我们直接在这里读取了流或内容,到后面的逻辑将无法进行下去,所以需要实现一个可以缓存的 HttpServletRequest。好在 Spring 提供这样的类,就是 ContentCachingRequestWrapperContentCachingResponseWrapper, 根据官方的文档这两个类正好是来干这个事情的,我们只要将 HttpServletRequestHttpServletResponse 转化即可。

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

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73727.html

相关文章

  • Spring Boot 2.x(十一):AOP实战--打印接口日志

    摘要:接口日志有啥用在我们日常的开发过程中,我们可以通过接口日志去查看这个接口的一些详细信息。在切入点返回内容之后切入内容可以用来对处理返回值做一些加工处理。 接口日志有啥用 在我们日常的开发过程中,我们可以通过接口日志去查看这个接口的一些详细信息。比如客户端的IP,客户端的类型,响应的时间,请求的类型,请求的接口方法等等,我们可以对这些数据进行统计分析,提取出我们想要的信息。 怎么拿到接口...

    Youngdze 评论0 收藏0
  • Spring-Boot学习笔记

    摘要:学习笔记使用很容易创建一个独立运行运行内嵌容器准生产级别的基于框架的项目,使用你可以不用或者只需要很少的配置。异常消息如果这个错误是由异常引起的。错误发生时请求的路径。 Spring-Boot 1.5 学习笔记 使用Spring Boot很容易创建一个独立运行(运行jar,内嵌Servlet容器)、准生产级别的基于Spring框架的项目,使用Spring Boot你可以不用或者只需要很...

    curlyCheng 评论0 收藏0
  • Spring Boot应用监控实战

    摘要:概述之前讲过容器的可视化监控,即监控容器的运行情况,包括使用率内存占用网络状况以及磁盘空间等等一系列信息。实战一下中添加依赖启动应用程序之后,只要在浏览器中输入端点信息就能获得应用的一些状态信息。 showImg(https://segmentfault.com/img/remote/1460000014684947); 概述 之前讲过Docker容器的可视化监控,即监控容器的运行情...

    mtunique 评论0 收藏0
  • Spring Boot 2.0.2 参考指南(通用的应用程序属性 ②)中文文档

    摘要:通用的应用程序属性代理主机代理端口嵌入式服务器配置属性服务器应该绑定到的网络地 通用的应用程序属性 ② sendgrid(SendGridAutoConfiguration) spring.sendgrid.api-key= # SendGrid API key spring.sendgrid.proxy.host= # SendGrid 代理主机 spring.sendgrid.pr...

    WelliJhon 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<