1. 程式人生 > 實用技巧 >springmvc controller自動打印出入引數以及列印其他有用資訊

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,並在完成計算後removeThreadLocal
  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 = "";
    }
}

RestController詳細X 沒有英漢互譯結果
請嘗試網頁搜尋