代码来咯,看代码多是一件美事呦,你他奶奶的为什么不看代码,给我看。
工作的时候,尤其是接口联调的时候,打印http请求记录是甩锅和解决问题的最好的方式。尤其是请求体里的内容。就是这个获取请求体的内容其实还有点门道,我就被坑过,你直接在拦截器里直接获取request里面的body,拦截器里能获取到,Controller里面就获取不到了。具体原因不深究,就是Body是用流传的,你读取了之后这个流就被消耗了,然后后面的代码再从request里面就读取不到了。
引入spring-boot-starter-actuator
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency >
引入之后,配置存储方式httpTraceRepository
,这个实例就是添加在内存中存储最近100次请求记录
1 2 3 4 5 6 7 @Configuration public class ConfigTrace { @Bean public HttpTraceRepository httpTraceRepository () { return new InMemoryHttpTraceRepository (); } }
最后在application.properties中加入
1 management.endpoints.web.exposure.include=*
运行项目访问http://localhost:8080/actuator/httptrace
得到这样的JSON数据(已经格式化),
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 { "traces" : [ { "timestamp" : "2022-02-19T05:08:05.370267100Z" , "principal" : null , "session" : null , "request" : { "method" : "GET" , "uri" : "http://localhost:8080/index?a=%E4%BB%8E%E5%89%8D%E4%BB%8E%E5%89%8D&b=%E6%9C%89%E4%B8%AA%E4%BA%BA%E7%88%B1%E4%BD%A0%E5%BE%88%E4%B9%85" , "headers" : { "host" : [ "localhost:8080" ] , "content-type" : [ "application/x-www-form-urlencoded" ] , "connection" : [ "Keep-Alive" ] , "accept-encoding" : [ "gzip,deflate" ] , "user-agent" : [ "Apache-HttpClient/4.5.13 (Java/11.0.12)" ] } , "remoteAddress" : null } , "response" : { "status" : 200 , "headers" : { "Keep-Alive" : [ "timeout=60" ] , "Connection" : [ "keep-alive" ] , "Content-Length" : [ "35" ] , "Date" : [ "Sat, 19 Feb 2022 05:08:05 GMT" ] , "Content-Type" : [ "text/plain;charset=UTF-8" ] } } , "timeTaken" : 120 } ] }
引入这个依赖的好处,就是几乎不需要写任何代码,引入,然后配置一下即可查看,缺点也有就是不能记录post请求的请求体,也无法记录返回值,持久化,目前我没有研究,但是我感觉可以。可以自行查阅资料研究。
使用springmvc自带的一个类 增加一个配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Bean CommonsRequestLoggingFilter loggingFilter () { CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter (); loggingFilter.setIncludeClientInfo(true ); loggingFilter.setIncludeHeaders(true ); loggingFilter.setIncludePayload(true ); loggingFilter.setMaxPayloadLength(10000 ); loggingFilter.setIncludeQueryString(true ); return loggingFilter; }
CommonsRequestLoggingFilter
可以点进去看一下,就是一个springboot提供得一个类。可以自己进行改造。接下来再配置一下logback就行。配置如下。看不懂或者不理解看这个logback配置详情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?xml version="1.0" encoding="UTF-8" ?> <configuration > <appender name ="soutAppender" class ="ch.qos.logback.core.ConsoleAppender" > <layout class ="ch.qos.logback.classic.PatternLayout" > <pattern > %date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern > </layout > </appender > <appender name ="fileAppender" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <append > true</append > <prudent > false</prudent > <File > logs/log.log</File > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <fileNamePattern > logs/%d{yyyy-MM-dd}/log%i.log</fileNamePattern > <maxHistory > 30</maxHistory > <timeBasedFileNamingAndTriggeringPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP" > <maxFileSize > 10MB</maxFileSize > </timeBasedFileNamingAndTriggeringPolicy > </rollingPolicy > <encoder class ="ch.qos.logback.classic.encoder.PatternLayoutEncoder" > <pattern > %date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern > <charset > utf-8</charset > </encoder > </appender > <logger name ="org.springframework.web.filter.CommonsRequestLoggingFilter" level ="debug" > </logger > <root level ="info" > <appender-ref ref ="soutAppender" /> <appender-ref ref ="fileAppender" /> </root > </configuration >
配置好打印的日志应该是这样
1 2 3 4 5 6 7 8 9 2022 -02 -19 19 : 02 : 28 , 608 DEBUG [ http-nio-8080 -exec-1 ] o.s.w.f.CommonsRequestLoggingFilter.CommonsRequestLoggingFilter#beforeRequest[ CommonsRequestLoggingFilter.java: 47 ] Before request [ GET /index?a=%E4%BB%8 E%E5%89 %8 D%E4%BB%8 E%E5%89 %8 D&b=%E6%9 C%89 %E4%B8%AA%E4%BA%BA%E7%88 %B1%E4%BD%A0%E5%BE%88 %E4%B9%85 , client=127.0 .0 .1 , headers=[ host: "localhost:8080" , connection: "Keep-Alive" , user-agent: "Apache-HttpClient/4.5.13 (Java/11.0.12)" , accept-encoding: "gzip,deflate" , Content-Type: "application/x-www-form-urlencoded;charset=UTF-8" ] ] 2022 -02 -19 19 : 02 : 28 , 668 DEBUG [ http-nio-8080 -exec-1 ] o.s.w.f.CommonsRequestLoggingFilter.CommonsRequestLoggingFilter#afterRequest[ CommonsRequestLoggingFilter.java: 55 ] After request [ GET /index?a=%E4%BB%8 E%E5%89 %8 D%E4%BB%8 E%E5%89 %8 D&b=%E6%9 C%89 %E4%B8%AA%E4%BA%BA%E7%88 %B1%E4%BD%A0%E5%BE%88 %E4%B9%85 , client=127.0 .0 .1 , headers=[ host: "localhost:8080" , connection: "Keep-Alive" , user-agent: "Apache-HttpClient/4.5.13 (Java/11.0.12)" , accept-encoding: "gzip,deflate" , Content-Type: "application/x-www-form-urlencoded;charset=UTF-8" ] ] 2022 -02 -19 19 : 02 : 39 , 737 DEBUG [ http-nio-8080 -exec-3 ] o.s.w.f.CommonsRequestLoggingFilter.CommonsRequestLoggingFilter#beforeRequest[ CommonsRequestLoggingFilter.java: 47 ] Before request [ POST /save, client=127.0 .0 .1 , headers=[ content-length: "14" , host: "localhost:8080" , connection: "Keep-Alive" , user-agent: "Apache-HttpClient/4.5.13 (Java/11.0.12)" , accept-encoding: "gzip,deflate" , Content-Type: "application/x-www-form-urlencoded;charset=UTF-8" ] ] 2022 -02 -19 19 : 02 : 39 , 743 DEBUG [ http-nio-8080 -exec-3 ] o.s.w.f.CommonsRequestLoggingFilter.CommonsRequestLoggingFilter#afterRequest[ CommonsRequestLoggingFilter.java: 55 ] After request [ POST /save, client=127.0 .0 .1 , headers=[ content-length: "14" , host: "localhost:8080" , connection: "Keep-Alive" , user-agent: "Apache-HttpClient/4.5.13 (Java/11.0.12)" , accept-encoding: "gzip,deflate" , Content-Type: "application/x-www-form-urlencoded;charset=UTF-8" ] , payload=a=%E4%BD%A0%E5%A5%BD%E4%B8%96 %E7%95 %8 C]
payload中得东西为请求体。一次请求会打印两条日志,Before request为过滤器前,After request为过滤器后的记录,这种方式其实已经挺好用了。但是还有个问题就是不能记录返回值,也有方法。
记录返回值 继承ResponseBodyAdvice
和使用RestControllerAdvice
注解来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 @Slf4j @RestControllerAdvice public class RestBodyAdvice implements ResponseBodyAdvice <Object> { private static final int DEFAULT_MAX_PAYLOAD_LENGTH = 10000 ; public static final String REQUEST_MESSAGE_PREFIX = "Request [" ; public static final String REQUEST_MESSAGE_SUFFIX = "]" ; private final ObjectMapper objectMapper = new ObjectMapper (); @ExceptionHandler(Exception.class) ResponseEntity<Object> handlerException (Exception e, HttpServletRequest rq, HttpServletResponse rp) { log.error("顶级异常: {}" ,e.getMessage()); e.printStackTrace(); return ResponseEntity.ok("系统错误" ); } @Override public boolean supports (MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true ; } @SneakyThrows @Override public Object beforeBodyWrite (Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request; log.debug(createRequestMessage(servletServerHttpRequest.getServletRequest(), REQUEST_MESSAGE_PREFIX, REQUEST_MESSAGE_SUFFIX)); log.debug("Response Body [" + objectMapper.writeValueAsString(body) +"]" ); return body; } private boolean checkPrimitive (Object body) { Class<?> clazz = body.getClass(); return clazz.isPrimitive() || clazz.isArray() || Collection.class.isAssignableFrom(clazz) || body instanceof Number || body instanceof Boolean || body instanceof Character || body instanceof String; } protected String createRequestMessage (HttpServletRequest request, String prefix, String suffix) { StringBuilder msg = new StringBuilder (); msg.append(prefix); msg.append(request.getMethod()).append(" " ); msg.append(request.getRequestURI()); String queryString = request.getQueryString(); if (queryString != null ) { msg.append('?' ).append(queryString); } String client = request.getRemoteAddr(); if (StringUtils.hasLength(client)) { msg.append(", client=" ).append(client); } HttpSession session = request.getSession(false ); if (session != null ) { msg.append(", session=" ).append(session.getId()); } String user = request.getRemoteUser(); if (user != null ) { msg.append(", user=" ).append(user); } HttpHeaders headers = new ServletServerHttpRequest (request).getHeaders(); msg.append(", headers=" ).append(headers); String payload = getMessagePayload(request); if (payload != null ) { msg.append(", payload=" ).append(payload); } msg.append(suffix); return msg.toString(); } protected String getMessagePayload (HttpServletRequest request) { ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class); if (wrapper != null ) { byte [] buf = wrapper.getContentAsByteArray(); if (buf.length > 0 ) { int length = Math.min(buf.length, DEFAULT_MAX_PAYLOAD_LENGTH); try { return new String (buf, 0 , length, wrapper.getCharacterEncoding()); } catch (UnsupportedEncodingException ex) { return "[unknown]" ; } } } return null ; } }
然后再配置logback
1 2 3 <logger name ="com.example.springboothttptrace.RestBodyAdvice" level ="debug" > </logger >
配置一下,输出的日志为
1 2 3 4 2022 -02 -21 10 : 19 : 29 , 880 DEBUG [ http-nio-8080 -exec-3 ] c.e.s.RestBodyAdvice.RestBodyAdvice#beforeBodyWrite[ RestBodyAdvice.java: 64 ] Request [ POST /save, client=127.0 .0 .1 , headers=[ content-length: "14" , host: "localhost:8080" , connection: "Keep-Alive" , user-agent: "Apache-HttpClient/4.5.13 (Java/11.0.12)" , accept-encoding: "gzip,deflate" , Content-Type: "application/x-www-form-urlencoded;charset=UTF-8" ] , payload=a=%E4%BD%A0%E5%A5%BD%E4%B8%96 %E7%95 %8 C] 2022 -02 -21 10 : 19 : 29 , 880 DEBUG [ http-nio-8080 -exec-3 ] c.e.s.RestBodyAdvice.RestBodyAdvice#beforeBodyWrite[ RestBodyAdvice.java: 66 ] Response Body [ "你好世界" ]
至此,http的请求记录就全部记录到了,payload中为请求体,请求体和返回内容。自己可以按需使用,开发过程中建议打印全部日志,方便调试,上线后,可以不打印。去掉也很方便,直接在logback配置中配置一下就可以了。
封面