1. 程式人生 > >SpringBoot(審計) 統計介面呼叫次數及成功率

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