沉默的背影 X-Pacific

keep learning

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 = "";
    }
}

 

posted @ 2020-12-22 17:26  乂墨EMO  阅读(1991)  评论(0编辑  收藏  举报