您好,登錄后才能下訂單哦!
在使用Spring Boot開發(fā) web api 的時(shí)候希望把 request,request header ,response reponse header , uri, method 等等的信息記錄到我們的日志中,方便我們排查問題,也能對系統(tǒng)的數(shù)據(jù)做一些統(tǒng)計(jì)。
Spring 使用了 DispatcherServlet 來攔截并分發(fā)請求,我們只要自己實(shí)現(xiàn)一個(gè) DispatcherServlet 并在其中對請求和響應(yīng)做處理打印到日志中即可。
我們實(shí)現(xiàn)一個(gè)自己的分發(fā) Servlet ,它繼承于 DispatcherServlet,我們實(shí)現(xiàn)自己的 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); //創(chuàng)建一個(gè) 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 來獲取請求的數(shù)據(jù),但如果我們直接在這里讀取了流或內(nèi)容,到后面的邏輯將無法進(jìn)行下去,所以需要實(shí)現(xiàn)一個(gè)可以緩存的 HttpServletRequest。好在 Spring 提供這樣的類,就是 ContentCachingRequestWrapper 和 ContentCachingResponseWrapper, 根據(jù)官方的文檔這兩個(gè)類正好是來干這個(gè)事情的,我們只要將 HttpServletRequest 和 HttpServletResponse 轉(zhuǎn)化即可。
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.
實(shí)現(xiàn)好我們的 LoggableDispatcherServlet后,接下來就是要指定使用 LoggableDispatcherServlet 來分發(fā)請求。
@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(); } }
增加一個(gè)簡單的 Controller 來測試一下
@RestController @RequestMapping("/hello") public class HelloController { @RequestMapping(value = "/word", method = RequestMethod.POST) public Object hello(@RequestBody Object object) { return object; } }
使用 curl 發(fā)送一個(gè) 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" } }
當(dāng)然打印出來是在一行中的,我進(jìn)行了一下格式化。我們還可以在日志中增加請求的時(shí)間,耗費(fèi)的時(shí)間以及異常信息等。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。