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 ThreadLocalSN_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