下面书写一个我们最实用的例子,也是我用在我们所有微服务项目中的一个例子,此处贴出来供以参考:
@ResponseBody @PostMapping(value = "/hello") public String helloPost(@RequestBody Map<String, Object> object) { //System.out.println(object); return "hello...Post"; }
get请求,不输出日志,来一个put请求如下:
日志输出如下:
日志输出里有个小细节,我们看到请求开始里没有payload,输出的时候却有了,怎么回事呢?原因其实是Spring提供的ContentCachingRequestWrapper具有懒加载的特性,所以才会出现这种现象。我个人觉得没什么影响,payload输出一遍反倒是我最希望的效果。至于根本原因,后面的博文会专门分析Spring给我们提供的一些web工具类里,会提到~
贴上我书写的代码,各位若有需要,只需要配置此Filter即可。这个Filter我放在我们公司的私服里,所有的服务都可以直接使用了,若有任何问题,随时留言交流~~
/** * 打印请求日志,默认打印get放,只打印POST、PUT、DELETE方法的请求日志哦~ * 会计算出请求耗时、client请求的ip地址等等 contentType也会记录打印出来 信息比较全 方便查找问题~ * * @author fangshixiang * @description * @date 2019-02-16 22:04 */ @Slf4j @WebFilter(urlPatterns = "/*") public class RequestLogFilter extends AbstractRequestLoggingFilter { //这些配置都可以在init-param中进行设置,但是基于注解的,这里就不要这么麻烦了,统一在初始化的时候设置值吧 //private boolean includeQueryString = false; //private boolean includeClientInfo = false; //private boolean includeHeaders = false; //private boolean includePayload = false; private static final String PROCESS_START_TIME_SUFFIX = ".PROCESS_START_TIME"; @Override protected void initFilterBean() throws ServletException { super.setIncludeQueryString(true); super.setIncludeClientInfo(true); //因为headers里面的信息太多了 所以这里不输出了,否则过于干扰,只手动把content-type等关键信息输出即可 super.setIncludeHeaders(false); super.setIncludePayload(true); super.setMaxPayloadLength(1000); //最大支持到1000个字符 //头信息 super.setBeforeMessagePrefix("请求开始 ["); super.setBeforeMessageSuffix("]"); super.setAfterMessagePrefix("请求结束 ["); super.setAfterMessageSuffix("]"); } @Override protected boolean shouldLog(HttpServletRequest request) { String method = request.getMethod(); return HttpMethod.POST.matches(method) || HttpMethod.PUT.matches(method) || HttpMethod.DELETE.matches(method); } @Override protected void beforeRequest(HttpServletRequest request, String message) { logger.info(calcRequestTime(request) .concat(getConfigTypeLog(request)) .concat(getThreadId()) .concat(message)); } @Override protected void afterRequest(HttpServletRequest request, String message) { logger.info(calcRequestTime(request) .concat(getConfigTypeLog(request)) .concat(getThreadId()) .concat(message)); } //拼装contentType private String getConfigTypeLog(HttpServletRequest request) { String contentType = request.getContentType(); String method = request.getMethod(); return "[contentType=" + contentType + "] " + method.toUpperCase() + " "; } //计算请求耗时 private String calcRequestTime(HttpServletRequest request) { long mills = 0; String requestTimeUniqueName = getRequestTimeUniqueName(); Object processStartTime = request.getAttribute(requestTimeUniqueName); if (processStartTime == null) { //首次 放置值 request.setAttribute(requestTimeUniqueName, Instant.now()); } else { //请求结束的处理 开始计算吧 Instant start = (Instant) processStartTime; Instant now = Instant.now(); mills = Duration.between(start, now).toMillis(); request.removeAttribute(requestTimeUniqueName); } return mills == 0 ? "" : ("[耗时:" + mills + "ms] "); } private String getRequestTimeUniqueName() { return this.getClass().getName().concat(PROCESS_START_TIME_SUFFIX); } private String getThreadId() { return "[ThreadId:" + Thread.currentThread().getId() + "] "; } }
备注:此Filter自己定制日志。若你结束请求时还想输出response里面的的内容。比如状态码,返回body里面的内容等等(其实我倒觉得还挺重要的),自己可以去加工实现。
这里比较坑爹的是,Spring提供给我们复写的两个方法,都没返回给我们response,所以若有需要,各位小伙伴自己实现哈。我在我项目中有实现,每个人实现方式不一样,这里就不贴出这部分源码了哈~
此请求日志Filter一般放在TokenFilter后面执行(如果业务特殊,放在前面执行也可)
拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调
最后
Spring作为当下实际的Java EE标准,非常的流行,设计也是非常的好。因此熟练的掌握Spring为我们提供的一些实用类,很多时候能够让我们事半功倍~