zoukankan      html  css  js  c++  java
  • 写个日志切面追踪,可以更直接查看项目执行的各种信息打印。

    项目在进入联调阶段时,服务层的接口需要和协议层进行交互,协议层需要将入参[json字符串]组装成服务层所需的json字符串,组装的过程中很容易出错。

    入参出错导致接口调试失败问题在联调中出现很多次,因此就想写一个请求日志切面把入参信息打印一下,同时协议层调用服务层接口名称对不上也出现了几次,通过请求日志切面就可以知道上层是否有没有发起调用,方便前后端甩锅还能拿出证据

     

    切面介绍

    面向切面编程是一种编程范式,是作为OOP面向对象编程的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理权限控制缓存控制日志打印等等。

     

    AOP把软件的功能模块分为两个部分:核心关注点横切关注点业务处理的主要功能为核心关注点,而非核心,需要拓展的功能为横切关注点。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点进行分离,使用切面有以下好处:

     

    • 可以集中处理某一关注点/横切逻辑

    • 可以很方便的添加或者删除关注点

    • 侵入性少,增强代码可读性及可维护性 因此当想打印请求日志时很容易想到切面,对控制层代码0侵入

    切面的使用【基于注解】

    • @Aspect => 声明该类为一个注解类

    切点注解:

    • @Pointcut => 定义一个切点,可以简化代码

    通知注解:

    • @Before => 在切点之前执行代码

    • @After => 在切点之后执行代码

    • @AfterReturning => 切点返回内容后执行代码,可以对切点的返回值进行封装

    • @AfterThrowing => 切点抛出异常后执行

    • @Around => 环绕,在切点前后执行代码

    1-1 首先使用@Pointcut定义一个切点,因为是请求日志切边,因此切点定义的是Controller包下的所有类下的所有方法,

    定义以后就在通知注解中直接使用requestServer方法即可

    //定义切点
    @Pointcut("execution(* com.xxx.xxx.controller..*(..))") public void requestServer() { }

    1.2 执行Controller的方法前打印调用方IP、请求URL、HTTP请求类型、调用的方法名

    @Before("requestServer()")      //这里使用的注解里面的方法就是之前定义的切点
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
    
        LOGGER.info("===============================Start========================");
        LOGGER.info("IP                 : {}", request.getRemoteAddr());
        LOGGER.info("URL                : {}", request.getRequestURL().toString());
        LOGGER.info("HTTP Method        : {}", request.getMethod());
        LOGGER.info("Class Method       : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
    }

    1.3 使用@Around注解打印进入控制层(Controller)的入参

    @Around("requestServer()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        LOGGER.info("Request Params       : {}", getRequestParams(proceedingJoinPoint));
        LOGGER.info("Result               : {}", result);
        LOGGER.info("Time Cost            : {} ms", System.currentTimeMillis() - start);
    
        return result;
    }

     1.4 打印了入参、结果以及耗时 ---- getRequestParams 方法

     通过@PathVariable以及@RequestParam注解传递的参数无法直接打印参数名,需要手动拼接参数名以及需要对文件对象进行特殊处理,仅获取文件名

    private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {
         Map<String, Object> requestParams = new HashMap<>();
    
          //参数名
         String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
         //参数值
         Object[] paramValues = proceedingJoinPoint.getArgs();
    
         for (int i = 0; i < paramNames.length; i++) {
             Object value = paramValues[i];
    
             //如果是文件对象
             if (value instanceof MultipartFile) {
                 MultipartFile file = (MultipartFile) value;
                 value = file.getOriginalFilename();  //获取文件名
             }
    
             requestParams.put(paramNames[i], value);
         }
    
         return requestParams;
     }

    1.5 @After:方法调用后执行,仅仅只是通知方法执行完毕的结尾

    @After("requestServer()")
    public void doAfter(JoinPoint joinPoint) {
        LOGGER.info("===============================End========================");
    }

    完整的切面类代码,包含上面所有定义的类,但是不建议放一起,避免耦合

    @Component
    @Aspect
    public class RequestLogAspect {
        private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class);
    
        @Pointcut("execution(* xxx.xxx.xx.controller..*(..))")            //注意这里是你自己项目结构
        public void requestServer() {
        }
    
        @Before("requestServer()")
        public void doBefore(JoinPoint joinPoint) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) 
    RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
    
            LOGGER.info("===============================Start========================");
            LOGGER.info("IP                 : {}", request.getRemoteAddr());
            LOGGER.info("URL                : {}", request.getRequestURL().toString());
            LOGGER.info("HTTP Method        : {}", request.getMethod());
            LOGGER.info("Class Method       : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), 
     joinPoint.getSignature().getName());
        }
    
    
        @Around("requestServer()")
        public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            long start = System.currentTimeMillis();
            Object result = proceedingJoinPoint.proceed();
            LOGGER.info("Request Params     : {}", getRequestParams(proceedingJoinPoint));
            LOGGER.info("Result               : {}", result);
            LOGGER.info("Time Cost            : {} ms", System.currentTimeMillis() - start);
    
            return result;
        }
    
        @After("requestServer()")
        public void doAfter(JoinPoint joinPoint) {
            LOGGER.info("===============================End========================");
        }
    
        /**
         * 获取入参
         * @param proceedingJoinPoint
         *
         * @return
         * */
        private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {
            Map<String, Object> requestParams = new HashMap<>();
    
            //参数名
            String[] paramNames = 
    ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
            //参数值
            Object[] paramValues = proceedingJoinPoint.getArgs();
    
            for (int i = 0; i < paramNames.length; i++) {
                Object value = paramValues[i];
    
                //判断是否为文件对象
                if (value instanceof MultipartFile) {
                    MultipartFile file = (MultipartFile) value;
                    value = file.getOriginalFilename();  //获取文件名
                }
    
                requestParams.put(paramNames[i], value);
            }
    
            return requestParams;
        }
    }

    分析:如果按照上面的方式进行日志追踪,正常情况下是没有问题的,但是如果是在高并发的情况下使用则会出现日志打印串行的问题,所以接下来是高并发下的处理方案,如生产环境有高并发的情况则需要下面的一些补充和设置

     1、首先创建一个参数信息类(RequestInfo),定义需要的参数

    @Data
    public class RequestInfo {
        private String ip;
        private String url;
        private String httpMethod;
        private String classMethod;
        private Object requestParams;
        private Object result;
        private Long timeCost;
    }

    2.1、定义环绕方法通知体,将参数信息封装成RequestInfo对象,然后序列化打印对象,这样可以更加清晰直接查看打印信息,而且在解决串行问题的同时添加了对异常信息的打印,通过使用@AfterThrowing注解对抛出异常的方法进行处理

    //这是封装RequestInfo对象信息类
    @Around("requestServer()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long start = System.currentTimeMillis(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Object result = proceedingJoinPoint.proceed(); RequestInfo requestInfo = new RequestInfo(); requestInfo.setIp(request.getRemoteAddr()); requestInfo.setUrl(request.getRequestURL().toString()); requestInfo.setHttpMethod(request.getMethod()); requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName())); requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint)); requestInfo.setResult(result); requestInfo.setTimeCost(System.currentTimeMillis() - start); LOGGER.info("Request Info : {}", JSON.toJSONString(requestInfo)); return result; }

    2.2、异常类参数信息定义

    //异常参数类定义
    @Data
    public class RequestErrorInfo { private String ip; private String url; private String httpMethod; private String classMethod; private Object requestParams; private RuntimeException exception; }

    2.3、出现异常时,打印耗时是没太大意义的,只需要获取异常信息即可

    //异常通知环绕体
    @AfterThrowing(pointcut = "requestServer()", throwing = "e") public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); RequestErrorInfo requestErrorInfo = new RequestErrorInfo(); requestErrorInfo.setIp(request.getRemoteAddr()); requestErrorInfo.setUrl(request.getRequestURL().toString()); requestErrorInfo.setHttpMethod(request.getMethod()); requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName())); requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint)); requestErrorInfo.setException(e); LOGGER.info("Error Request Info : {}", JSON.toJSONString(requestErrorInfo)); }

    完整的日志切面代码,包含参数定义类、异常处理类所有代码,但是不建议放一起,避免耦合

    @Component
    @Aspect
    public class RequestLogAspect {
        private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class);
    
        @Pointcut("execution(* your_package.controller..*(..))")
        public void requestServer() {
        }
    
        @Around("requestServer()")
        public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            long start = System.currentTimeMillis();
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            Object result = proceedingJoinPoint.proceed();
            RequestInfo requestInfo = new RequestInfo();
                    requestInfo.setIp(request.getRemoteAddr());
            requestInfo.setUrl(request.getRequestURL().toString());
            requestInfo.setHttpMethod(request.getMethod());
            requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),
                    proceedingJoinPoint.getSignature().getName()));
            requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));
            requestInfo.setResult(result);
            requestInfo.setTimeCost(System.currentTimeMillis() - start);
            LOGGER.info("Request Info      : {}", JSON.toJSONString(requestInfo));
    
            return result;
        }
    
    
        @AfterThrowing(pointcut = "requestServer()", throwing = "e")
        public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            RequestErrorInfo requestErrorInfo = new RequestErrorInfo();
            requestErrorInfo.setIp(request.getRemoteAddr());
            requestErrorInfo.setUrl(request.getRequestURL().toString());
            requestErrorInfo.setHttpMethod(request.getMethod());
            requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(),
                    joinPoint.getSignature().getName()));
            requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
            requestErrorInfo.setException(e);
            LOGGER.info("Error Request Info      : {}", JSON.toJSONString(requestErrorInfo));
        }
    
        /**
         * 获取入参
         * @param proceedingJoinPoint
         *
         * @return
         * */
        private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
            //参数名
            String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
            //参数值
            Object[] paramValues = proceedingJoinPoint.getArgs();
    
            return buildRequestParam(paramNames, paramValues);
        }
    
        private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {
            //参数名
            String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();
            //参数值
            Object[] paramValues = joinPoint.getArgs();
    
            return buildRequestParam(paramNames, paramValues);
        }
    
        private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
            Map<String, Object> requestParams = new HashMap<>();
            for (int i = 0; i < paramNames.length; i++) {
                Object value = paramValues[i];
    
                //如果是文件对象
                if (value instanceof MultipartFile) {
                    MultipartFile file = (MultipartFile) value;
                    value = file.getOriginalFilename();  //获取文件名
                }
    
                requestParams.put(paramNames[i], value);
            }
    
            return requestParams;
        }
    
        @Data
        public class RequestInfo {
            private String ip;
            private String url;
            private String httpMethod;
            private String classMethod;
            private Object requestParams;
            private Object result;
            private Long timeCost;
        }
    
        @Data
        public class RequestErrorInfo {
            private String ip;
            private String url;
            private String httpMethod;
            private String classMethod;
            private Object requestParams;
            private RuntimeException exception;
        }
    }
  • 相关阅读:
    ABAP-Spotlight on ABAP for SAP HANA – Again
    ABAP-ABAP Development
    ABAP-Performance Guidelines for ABAP Development on the SAP HANA Database
    ABAP-Getting Started with ABAP Core Data Services (CDS)
    ABAP-Technology and Runtime Environment
    ABAP-Test and Analysis Tools
    ABAP-Connectivity Wiki
    python爬虫
    python爬虫
    python爬虫
  • 原文地址:https://www.cnblogs.com/liaoyuanping-24/p/14331706.html
Copyright © 2011-2022 走看看