转载:https://www.cnblogs.com/yuwenhui/p/9809671.html
介绍:
很多时候会需要提供一些统计记录的,比如某个服务一个月的被调用量、接口的调用次数、成功调用次数等等。
优点:
使用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 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 build;
public static Function getBuild() {
return build;
}
public static void registerBuildFunction(Function 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);
}
}
Health接口
@Component
public class RuntimeHealthIndicator extends AbstractHealthIndicator {
private static ILogger log = LoggerFactory.getLogger(ApplicationInstanceManager.class);
private static Map restApiInvokeStatuses = new HashMap<>();
public static long failedRequestCount = 0;
public static long successRequestCount = 0;
public static Map historyRequestRecode;
private Map 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 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;
}
}