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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章