• 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":""
        }
    }

    关键实现思路

    1. 切面切RestController,且可以限定包名
    2. 通过ThreadLocal实现rt计算以及sn,并在完成计算后remove ThreadLocal
    3. 可以根据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 = "";
        }
    }

  • 相关阅读:
    性能测试系列(1)-性能测试基本概念
    性能篇综合汇总
    【CTFHUB】Web技能树
    Flash XSS
    绕过CDN找到⽬标站点真实IP
    【网鼎杯2020白虎组】Web WriteUp [picdown]
    【网鼎杯2020朱雀组】Web WriteUp
    【网鼎杯2020青龙组】Web WriteUp
    利用DNSLog实现无回显注入
    Cobalt Stike使用教程
  • 原文地址:https://www.cnblogs.com/zxporz/p/14174234.html
Copyright © 2020-2023  润新知