• SpringBoot(审计) 统计接口调用次数及成功率


    介绍:

      很多时候会需要提供一些统计记录的,比如某个服务一个月的被调用量、接口的调用次数、成功调用次数等等。

    优点:

      使用AOP+Hendler对业务逻辑代码无侵入,完全解耦。通过spring boot自带的健康检查接口(/health)方便、安全。

    注意:

      数据没有被持久化,只保存在内存中,重启后数据将被重置。可按需自己实现 

    代码:

      AOP:在AOP中调用Handler

    @Component
    @Aspect
    public class ControllerAdvice {
        private static ILogger log = LoggerFactory.getLogger(ControllerAdvice.class);
    
        @Around("execution(public * *..*controller.*.*(..))")
        public Object handle(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            Object result;
            try {
                Function<ProceedingJoinPoint, AbstractControllerHandler> build = AbstractControllerHandler.getBuild();
                if (null == build) {
                    AbstractControllerHandler.registerBuildFunction(DefaultControllerHandler::new);
                }
                build = AbstractControllerHandler.getBuild();
                AbstractControllerHandler controllerHandler = build.apply(proceedingJoinPoint);
                if (null == controllerHandler) {
                    log.warn(String.format("The method(%s) do not be handle by controller handler.", proceedingJoinPoint.getSignature().getName()));
                    result = proceedingJoinPoint.proceed();
                } else {
                    result = controllerHandler.handle();
                }
            } catch (Throwable throwable) {
                RuntimeHealthIndicator.failedRequestCount++;
                log.error(new Exception(throwable), "Unknown exception- -!");
    
                throw throwable;
            }
    
            return result;
        }
    }

     Handler:执行记录的逻辑

        抽象类:AbstractControllerHandler 

    public abstract class AbstractControllerHandler {
        private static ILogger log = LoggerFactory.getLogger(AbstractControllerHandler.class);
    
        private static Function<ProceedingJoinPoint, AbstractControllerHandler> build;
    
        public static Function<ProceedingJoinPoint, AbstractControllerHandler> getBuild() {
            return build;
        }
    
        public static void registerBuildFunction(Function<ProceedingJoinPoint, AbstractControllerHandler> build) {
            Assert.isNotNull(build, "build");
    
            AbstractControllerHandler.build = build;
        }
    
        protected ProceedingJoinPoint proceedingJoinPoint;
        protected HttpServletRequest httpServletRequest;
        protected String methodName;
        protected String uri;
        protected String requestBody;
        protected String ip;
        protected Method method;
        protected boolean inDataMasking;
        protected boolean outDataMasking;
    
    
        public AbstractControllerHandler(ProceedingJoinPoint proceedingJoinPoint) {
            Assert.isNotNull(proceedingJoinPoint, "proceedingJoinPoint");
    
            this.proceedingJoinPoint = proceedingJoinPoint;
            Signature signature = this.proceedingJoinPoint.getSignature();
            this.httpServletRequest = this.getHttpServletRequest(this.proceedingJoinPoint.getArgs());
            this.methodName = signature.getName();
            this.uri = null == this.httpServletRequest ? null : this.httpServletRequest.getRequestURI();
            this.requestBody = this.formatParameters(this.proceedingJoinPoint.getArgs());
            this.ip = null == this.httpServletRequest ? "" : CommonHelper.getIp(this.httpServletRequest);
            this.inDataMasking = false;
            this.outDataMasking = false;
            if (signature instanceof MethodSignature) {
                MethodSignature methodSignature = (MethodSignature) signature;
                try {
                    this.method = proceedingJoinPoint.getTarget().getClass().getMethod(this.methodName, methodSignature.getParameterTypes());
                    if (null != this.method) {
                        LogDataMasking dataMasking = this.method.getDeclaredAnnotation(LogDataMasking.class);
                        if (null != dataMasking) {
                            this.inDataMasking = dataMasking.in();
                            this.outDataMasking = dataMasking.out();
                        }
                    }
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public abstract Object handle() throws Throwable;
    
        protected void logIn() {
            String requestBody = this.requestBody;
            if (this.inDataMasking) {
                requestBody = "Data Masking";
            }
            log.info(String.format("Start-[%s][%s][%s][body: %s]", this.ip, this.uri, this.methodName, requestBody));
        }
    
        protected void logOut(long elapsedMilliseconds, boolean success, String responseBody) {
            if (success) {
                if (this.outDataMasking) {
                    responseBody = "Data Masking";
                }
                log.info(
                        String.format(
                                "Success(%s)-[%s][%s][%s][response body: %s]",
                                elapsedMilliseconds,
                                this.ip,
                                this.uri,
                                this.methodName,
                                responseBody));
            } else {
                log.warn(
                        String.format(
                                "Failed(%s)-[%s][%s][%s][request body: %s][response body: %s]",
                                elapsedMilliseconds,
                                this.ip,
                                this.uri,
                                this.methodName,
                                this.requestBody,
                                responseBody));
            }
        }
    
        protected HttpServletRequest getHttpServletRequest(Object[] parameters) {
            try {
                if (null != parameters) {
                    for (Object parameter : parameters) {
                        if (parameter instanceof HttpServletRequest) {
                            return (HttpServletRequest) parameter;
                        }
                    }
                }
    
                return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            } catch (Exception e) {
                log.error(e);
    
                return null;
            }
        }
    
        protected String formatParameters(Object[] parameters) {
            if (null == parameters) {
                return null;
            } else {
                StringBuilder stringBuilder = new StringBuilder();
                for (int i = 0; i < parameters.length; i++) {
                    Object parameter = parameters[i];
                    if (parameter instanceof HttpServletRequest || parameter instanceof HttpServletResponse) {
                        continue;
                    }
    
                    stringBuilder.append(String.format("[%s]: %s.", i, JSON.toJSONString(parameter)));
                }
    
                return stringBuilder.toString();
            }
        }

      实现类:

    public class DefaultControllerHandler extends AbstractControllerHandler {
        private static ILogger log = LoggerFactory.getLogger(DefaultControllerHandler.class);
        private static int currentMonth = Calendar.getInstance().get(Calendar.MONTH) + 1;
    
        public DefaultControllerHandler(ProceedingJoinPoint proceedingJoinPoint) {
            super(proceedingJoinPoint);
        }
    
        @Override
        public Object handle() throws Throwable {
            long timestamp = System.currentTimeMillis();
            this.logIn();
    
            ResponseDto responseDto;
            boolean success = false;
            try {
                Object result = proceedingJoinPoint.proceed();
                if (result instanceof ResponseDto) {
                    responseDto = (ResponseDto) result;
                } else {
                    responseDto = ResponseDto.success(result);
                }
                success = true;
                RuntimeHealthIndicator.successRequestCount++;
            } catch (BusinessException e) {
    //            RuntimeHealthIndicator.failedRequestCount++;
                if (this.isDebugLogLevel()) {
                    log.error(e);
                }
    
                responseDto = new ResponseDto<>(e.getCode(), e.getMessage(), null);
            } catch (Exception e) {
                RuntimeHealthIndicator.failedRequestCount++;
    
                if (this.isDebugLogLevel()) {
                    log.error(e);
                }
    
                responseDto = ResponseDto.failed(ExceptionDefinitions.ServerError, e.getMessage(), null);
            } finally {
                Calendar cale = Calendar.getInstance();
                if (currentMonth != (cale.get(Calendar.MONTH) + 1)) {
                    String recodeKey = String.format("%d年%d月",
                            cale.get(Calendar.YEAR), cale.get(Calendar.MONTH) + 1);
                    String recodeValue = "successCount:" + RuntimeHealthIndicator.successRequestCount +
                            " failedCount:" + RuntimeHealthIndicator.failedRequestCount;
                    RuntimeHealthIndicator.historyRequestRecode.put(recodeKey, recodeValue);
                    RuntimeHealthIndicator.successRequestCount = 0;
                    RuntimeHealthIndicator.failedRequestCount = 0;
                    currentMonth = cale.get(Calendar.MONTH);
                }
            }
    
            long duration = System.currentTimeMillis() - timestamp;
            RuntimeHealthIndicator.markRestApiInvoked(this.methodName, (int) duration);
            this.logOut(duration, success, JSON.toJSONString(responseDto));
    
            return responseDto;
        }
    
        public boolean isDebugLogLevel() {
            return log.isEnabled(LogLevel.DEBUG);
        }
    }
    View Code

    Health接口

    @Component
    public class RuntimeHealthIndicator extends AbstractHealthIndicator {
        private static ILogger log = LoggerFactory.getLogger(ApplicationInstanceManager.class);
        private static Map<String, RestApiInvokeStatus> restApiInvokeStatuses = new HashMap<>();
        public static long failedRequestCount = 0;
        public static long successRequestCount = 0;
    
        public static Map<String, Object> historyRequestRecode;
        private Map<String, Object> details;
    
        public RuntimeHealthIndicator() {
            this.details = new HashMap<>();
            RuntimeHealthIndicator.historyRequestRecode = new HashMap<>();
    
            this.details.put("startTime", new Date(ManagementFactory.getRuntimeMXBean().getStartTime()));
            this.details.put("path", RuntimeHealthIndicator.class.getClassLoader().getResource("").getPath());
            this.details.put("osName", System.getProperty("os.name"));
            this.details.put("osVersion", System.getProperty("os.version"));
            this.details.put("javaVersion", System.getProperty("java.version"));
            try {
                this.details.put("ip", ZGHelper.getIpV4());
            } catch (SocketException e) {
                log.error(e, "Failed to get Ipv4.");
            }
        }
    
        @Override
        protected void doHealthCheck(Health.Builder builder) throws Exception {
    
            ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
            while (null != threadGroup.getParent()) {
                threadGroup = threadGroup.getParent();
            }
            this.details.put("threadCount", threadGroup.activeCount());
            OperatingSystemMXBean operatingSystemMXBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
            this.details.put("cpuUsageRate", operatingSystemMXBean.getSystemCpuLoad());
            this.details.put(
                    "memoryUsageRate",
                    (float) (operatingSystemMXBean.getTotalPhysicalMemorySize() - operatingSystemMXBean.getFreePhysicalMemorySize()) / (float) operatingSystemMXBean.getTotalPhysicalMemorySize());
            this.details.put("failedRequestCount", RuntimeHealthIndicator.failedRequestCount);
            this.details.put("successRequestCount", RuntimeHealthIndicator.successRequestCount);
            this.details.put("restApiInvokeStatuses", RuntimeHealthIndicator.restApiInvokeStatuses);
            this.details.put("historyRequestRecode",RuntimeHealthIndicator.historyRequestRecode);
    
            for (Map.Entry<String, Object> detail : this.details.entrySet()) {
                builder.withDetail(detail.getKey(), detail.getValue());
            }
            builder.up();
        }
    
        public static void markRestApiInvoked(String name, int duration) {
            if (StringUtils.isBlank(name)) {
                return;
            }
    
            if (!RuntimeHealthIndicator.restApiInvokeStatuses.containsKey(name)) {
                RuntimeHealthIndicator.restApiInvokeStatuses.put(name, new RestApiInvokeStatus(name));
            }
    
            RestApiInvokeStatus restApiInvokeStatus = RuntimeHealthIndicator.restApiInvokeStatuses.get(name);
            restApiInvokeStatus.setDuration(duration);
        }
    }
    public class RestApiInvokeStatus {
        private String name;
        private Date startDate;
        private Date latestDate;
        private long times;
        private float averageDuration;
        private int minDuration;
        private int maxDuration;
        private int[] durations;
    
        public String getName() {
            return name;
        }
    
        public Date getStartDate() {
            return startDate;
        }
    
        public Date getLatestDate() {
            return latestDate;
        }
    
        public long getTimes() {
            return times;
        }
    
        public int getMinDuration() {
            return minDuration;
        }
    
    
        public int getMaxDuration() {
            return maxDuration;
        }
    
        public RestApiInvokeStatus(String name) {
            Assert.isNotBlank(name, "name");
    
            this.name = name;
            this.durations = new int[1000];
            this.minDuration = Integer.MAX_VALUE;
            this.maxDuration = Integer.MIN_VALUE;
            Date now = new Date();
            this.startDate = now;
            this.latestDate = now;
    
        }
    
        public void setDuration(int duration) {
            this.durations[(int) (this.times % this.durations.length)] = duration;
            this.maxDuration = this.maxDuration > duration ? this.maxDuration : duration;
            this.minDuration = this.minDuration < duration ? this.minDuration : duration;
            this.latestDate = new Date();
            this.times++;
        }
    
        public float getAverageDuration() {
            long length = this.times < this.durations.length ? this.times : this.durations.length;
    
            int count = 0;
            for (int i = 0; i < length; i++) {
                count += this.durations[i];
            }
            this.averageDuration = (float) count / (float) length;
    
            return this.averageDuration;
        }
    }
  • 相关阅读:
    JUnit测试框架使用
    Android开发环境搭建与SD card
    深入Java泛型(Java泛型擦除机制,使用泛型强转时机,擦除对复写影响,协变返回类型)
    DHTML5(控件动态效果综合应用与表单校验)
    DHTML4(select与checkbox应用)
    DHTML3(表格动态创建,删除行/列,表格行排序,行颜色交替高亮显示)
    DHTML2(window对象,下拉列表)
    DHTML1(节点操作)
    JavaScript_语法,语句,函数,对象
    Html/CSS2_了解CSS
  • 原文地址:https://www.cnblogs.com/yuwenhui/p/9809671.html
Copyright © 2020-2023  润新知