springmvc controller自动打印出入参数以及打印其他有用信息
使用说明
com.xxx包下
加了@RestController注解的controller
打印的日志规格如下:
包含:ip地址、url、全限定类名+方法名、请求时间、请求参数(支持多个)、响应时间、响应参数、响应时间(毫秒)、关键字、序列号(用于和响应打印匹配)
# 请求打印 2020-12-22 16:15:08.473 INFO 9920 --- [nio-9600-exec-8] p.DefaltControllerPrintInputOutputAcpect : xxxx_cloud_aop_request: { "ipaddr":"127.0.0.1:9600", "url":"/testcfg4", "method":"com.xxx.biz.api.DictController.testcfg4", "requestTime":"2020-12-22 16:15:08", "request":"[{\"a\":\"aa\",\"b\":\"bb\",\"c\":\"cc\"}]", "keyword":"zxp", "sn":"1608624908470_58" } # 响应打印 2020-12-22 16:15:08.474 INFO 9920 --- [nio-9600-exec-8] p.DefaltControllerPrintInputOutputAcpect : xxxx_cloud_aop_response: { "ipaddr":"127.0.0.1:9600", "url":"/testcfg4", "method":"com.xxx.biz.api.DictController.testcfg4", "responseTime":"2020-12-22 16:15:08", "response":"{\"code\":200,\"msg\":\"\"}", "rt":4, "keyword":"zxp", "sn":"1608624908470_58" }
打印个性配置@PrintControllerLog
不打印请求报文
@RequestMapping(value = "/testdown", method = RequestMethod.GET) @PrintControllerLog(notPrintRequest = true) public Result downloadFile(HttpServletResponse response) {
不打印响应报文
@RequestMapping(value = "/testdown", method = RequestMethod.GET) @PrintControllerLog(notPrintResponse = true) public Result downloadFile(HttpServletResponse response) {
都不打印
@PrintControllerLog(notPrintResponse = true,notPrintRequest = true)
配置keyword
@RequestMapping(value = "/testdown", method = RequestMethod.GET) @PrintControllerLog(keyword = "zxp") public Result downloadFile(HttpServletResponse response) {
输出
2020-12-22 16:15:08.474 INFO 9920 --- [nio-9600-exec-8] p.DefaltControllerPrintInputOutputAcpect : xxxx_cloud_aop_response: { "ipaddr":"127.0.0.1:9600", "url":"/testcfg4", "method":"com.xxxx.biz.api.DictController.testcfg4", "responseTime":"2020-12-22 16:15:08", "response":"{\"code\":200,\"msg\":\"\"}", "rt":4, "keyword":"zxp", "sn":"1608624908470_58" }
配置pretty
可以配置输出是否格式化json,默认格式化
@PrintControllerLog(pretty = false)
2020-12-22 17:02:07.922 INFO 13516 --- [nio-9600-exec-1] p.DefaltControllerPrintInputOutputAcpect : xxxx_cloud_aop_response: { "ipaddr":"127.0.0.1:9600", "url":"/dict/batchcode", "method":"com.xxx.biz.api.DictController.getBatchCode", "authorization":"Bearer 02c28b9e-e554-453d-836d-0968f9c48e3c", "responseTime":"2020-12-22 17:02:07", "rt":287, "keyword":"", "sn":"1608627727565_70", "response":{ "code":200, "data":{ "opLogLevel":{ "1":"提示", "2":"警告", "3":"严重", "4":"致命" } }, "msg":"" } }
关键实现思路
- 切面切RestController,且可以限定包名
- 通过ThreadLocal实现rt计算以及sn,并在完成计算后remove ThreadLocal
- 可以根据PrintControllerLog做一些更灵活的配置
注解PrintControllerLog
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PrintControllerLog { //是否美化json输出 public boolean pretty() default true; //是否打印请求 public boolean notPrintRequest() default false; //是否打印返回 public boolean notPrintResponse() default false; //斌哥提的需求,设置keyword方便统一查找 public String keyword() default ""; }
切面类DefaltControllerPrintInputOutputAcpect实现
@Aspect @Component @Slf4j public class DefaltControllerPrintInputOutputAcpect { private ThreadLocal<PrintRunnerInfo> SN_CONTEXT = new ThreadLocal<>(); /** * XXX包下的切面 */ @Pointcut("within(com.XXX..*)") public void anController0() { } /** * 加了RestController注解的切面 */ @Pointcut("@within(org.springframework.web.bind.annotation.RestController)") public void anController99() { } @Before("anController99() && anController0()") public void before(JoinPoint joinPoint) { try{ printBaseAndRequest(joinPoint); }catch (Exception e){ } } @AfterReturning(returning = "ret",pointcut="anController99() && anController0()") public void after(JoinPoint joinPoint,Object ret){ try{ printResponse(ret,joinPoint); }catch (Exception e){ } } /** * 执行前打印 * @param joinPoint */ public void printBaseAndRequest(JoinPoint joinPoint) { PrintReqInfo printReqInfo = new PrintReqInfo(); //设置开始时间 printReqInfo.setRequestTime(genNow()); //获取一个sn,并对TL中的执行情况对象做相应设置 printReqInfo.setSn(getAndSetupSn()); // 设定方法路径 printReqInfo.setMethod(getMethod(joinPoint)); // 取配置 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); PrintControllerLog printControllerLog = methodSignature.getMethod().getAnnotation(PrintControllerLog.class); PrintCfgInfo printCfgInfo = getCfg(printControllerLog); printReqInfo.setKeyword(printCfgInfo.getKeyword()); //设置url printReqInfo.setUrl(getUrl()); //设置Authorization printReqInfo.setAuthorization(getAuthorization()); //设置IpAddr printReqInfo.setIpaddr(getIpAddr()); //设置请求参数 fillPrintReqInfo(joinPoint,printReqInfo,printCfgInfo.notPrintRequest); //打印请求参数 if (!printCfgInfo.isNotPrintRequest()) { if(printCfgInfo.isPretty()){ log.info("xxxx_cloud_aop_request: {}", JSON.toJSONString(printReqInfo,true)); }else{ log.info("xxxx_cloud_aop_request: {}", JSON.toJSONString(printReqInfo)); } } } /** * 获取配置 * @param printControllerLog * @return */ private PrintCfgInfo getCfg(PrintControllerLog printControllerLog){ PrintCfgInfo printCfgInfo = new PrintCfgInfo(); if (printControllerLog != null) { printCfgInfo.setKeyword(printControllerLog.keyword()); printCfgInfo.setNotPrintRequest(printControllerLog.notPrintRequest()); printCfgInfo.setNotPrintResponse(printControllerLog.notPrintResponse()); printCfgInfo.setPretty(printControllerLog.pretty()); }else{ printCfgInfo.setKeyword(""); printCfgInfo.setNotPrintRequest(false); printCfgInfo.setNotPrintResponse(false); printCfgInfo.setPretty(true); } return printCfgInfo; } /** * 执行后打印 * @param joinPoint */ public void printResponse(Object ret,JoinPoint joinPoint) { PrintResInfo printResInfo = new PrintResInfo(); //设置开始时间 printResInfo.setResponseTime(genNow()); //获取一个sn,并对TL中的执行情况对象做相应设置 printResInfo.setSn(getAndSetupSn()); //设置rt printResInfo.setRt(getRt()); //清理TL cleanTL(); // 设定方法路径 printResInfo.setMethod(getMethod(joinPoint)); // 取配置 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); PrintControllerLog printControllerLog = methodSignature.getMethod().getAnnotation(PrintControllerLog.class); PrintCfgInfo printCfgInfo = getCfg(printControllerLog); printResInfo.setKeyword(printCfgInfo.getKeyword()); //设置url printResInfo.setUrl(getUrl()); //设置Authorization printResInfo.setAuthorization(getAuthorization()); //设置IpAddr printResInfo.setIpaddr(getIpAddr()); //设置返回参数 fillPrintResInfo(ret,printResInfo,printCfgInfo.notPrintResponse); //打印返回结果 if (!printCfgInfo.isNotPrintResponse()) { if(printCfgInfo.isPretty()) { log.info("xxxx_cloud_aop_response: {}", JSON.toJSONString(printResInfo, true)); }else{ log.info("xxxx_cloud_aop_response: {}", JSON.toJSONString(printResInfo)); } } } /** * 填充剩余信息 * @param joinPoint * @param printReqInfo * @param notPrintReq */ private void fillPrintReqInfo(JoinPoint joinPoint,PrintReqInfo printReqInfo,boolean notPrintReq){ Object[] args = joinPoint.getArgs(); if(args != null && args.length > 0 ) { List<Object> objects = Arrays.asList(args).stream().filter(s -> !isFile(s)).collect(Collectors.toList()); if (objects != null && objects.size() > 0 && !notPrintReq) { try { printReqInfo.setRequest(args); }catch(Exception e){} } } } /** * 填充剩余信息 * @param ret * @param printResInfo * @param notPrintRes */ private void fillPrintResInfo(Object ret,PrintResInfo printResInfo,boolean notPrintRes){ if (ret != null && !notPrintRes) { try { printResInfo.setResponse(ret); }catch(Exception e){} } } private boolean isFile(Object obj){ if(obj instanceof MultipartFile){ return true; } return false; } /** * 获取一个sn,并对TL中的执行情况对象做相应设置 * 当第二次执行TL中已经有相应信息 * 此sn不能保证唯一,为了对应打印日志的请求和响应 * @return */ private String getAndSetupSn(){ if(SN_CONTEXT.get() != null && !StringUtils.isEmpty(SN_CONTEXT.get().getSn())){ SN_CONTEXT.get().setEnd(System.currentTimeMillis()); SN_CONTEXT.get().setRt(SN_CONTEXT.get().getEnd()-SN_CONTEXT.get().getStart()); return SN_CONTEXT.get().getSn(); }else{ String sn = System.currentTimeMillis()+"_"+new Random().nextInt(100); SN_CONTEXT.set(PrintRunnerInfo.builder().sn(sn).start(System.currentTimeMillis()).build()); return sn; } } /** * 获取rt * @return */ private Long getRt(){ if(SN_CONTEXT.get() != null){ return SN_CONTEXT.get().getRt(); }else{ return 0L; } } /** * 清楚TL */ private void cleanTL(){ SN_CONTEXT.remove(); } private String genNow(){ return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()); } /** * 获取当前请求的url * @return */ private String getUrl(){ ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); String requestURL = request.getRequestURI(); return requestURL; } /** * 获取当前请求的Authorization * @return */ private String getAuthorization(){ ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); return request.getHeader("Authorization"); } /** * 获取IpAddr * @return */ private String getIpAddr(){ ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); // 取得服务器IP String ip = request.getLocalAddr(); // 取得服务器端口 int port = request.getLocalPort(); return ip+":"+port; } /** * 获得方法名称 * @param joinPoint * @return */ private String getMethod(JoinPoint joinPoint){ String method = ""; try{ MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String methodPackage = methodSignature.getDeclaringTypeName(); method = methodPackage; if(methodSignature.getMethod() != null){ method+="."+methodSignature.getMethod().getName(); } return method; }catch (Exception e){ return method; } } /** * 配置对象 from PrintControllerLog */ @Data private static class PrintCfgInfo{ boolean pretty = true; //不打印基础信息 boolean notPrintRequest = false; //不打印基础信息 boolean notPrintResponse = false; //日志关键字 String keyword = ""; } /** * 运行数据 */ @Data @Builder private static class PrintRunnerInfo{ //此sn不能保证唯一,为了对应打印日志的请求和响应 private String sn; private Long start; private Long end; private Long rt; } /** * 请求打印 */ @Data private static class PrintReqInfo{ //ipaddr @JSONField(ordinal = 1) String ipaddr = ""; //此sn不能保证唯一,为了对应打印日志的请求和响应 @JSONField(ordinal = 8) String sn = ""; //url @JSONField(ordinal = 2) String url = ""; //日志关键字 @JSONField(ordinal = 7) String keyword = ""; //方法名(含全限定类名) @JSONField(ordinal = 3) String method = ""; //请求参数 @JSONField(ordinal = 10) Object[] request; //请求时间 @JSONField(ordinal = 5) String requestTime = ""; //Authorization @JSONField(ordinal = 4) String authorization = ""; } /** * 响应打印 */ @Data private static class PrintResInfo{ //ipaddr @JSONField(ordinal = 1) String ipaddr = ""; //此sn不能保证唯一,为了对应打印日志的请求和响应 @JSONField(ordinal = 9) String sn = ""; //url @JSONField(ordinal = 2) String url = ""; //日志关键字 @JSONField(ordinal = 8) String keyword = ""; //方法名(含全限定类名) @JSONField(ordinal = 3) String method = ""; //返回参数 @JSONField(ordinal = 10) Object response = ""; //响应时间 @JSONField(ordinal = 5) String responseTime = ""; //RT ms @JSONField(ordinal = 7) Long rt = 0L; //Authorization @JSONField(ordinal = 4) String authorization = ""; } }