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" } }
当然打印出来是在一行中的,我进行了一下格式化。我们还可以在日志中增加请求的时间,耗费的时间以及异常信息等。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 剑指Offer LeetCode 面试题10- II. 青蛙跳台阶问题
- 剑指Offer LeetCode 面试题10- I. 斐波那契数列
- 蓝桥杯 1的个数
- 蓝桥杯-试题 算法训练 数据交换
- 解决mysql导入新数据库大小写问题(Table 'zup.Domain_System' doesn't exist)
- 蓝桥杯vip试题 报时助手
- 蓝桥杯-基础练习 查找整数
- 蓝桥杯-基础练习 数列排序
- 无线网络-何为ISM频段?
- 蓝桥杯vip测试题系统试题-算法提高 矩阵转置
- 蓝桥杯vip测试题系统-数组求和(解题思路以及解题代码,手画思路图虽然丑丑的)
- 蓝桥杯vip测试题-找零钱(解题思路以及解题代码)
- 剑指Office-二进制中1的个数
- 剑指Office-旋转数组的最小数
- Mysql调优你不知道这几点,就太可惜了