Initial commit
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0"?>
|
||||
<project
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-dependencies</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../lingniu-framework-dependencies/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>lingniu-framework-plugin-prometheus</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.lingniu.framework</groupId>
|
||||
<artifactId>lingniu-framework-plugin-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.lingniu.framework.plugin.prometheus;
|
||||
|
||||
/**
|
||||
* Counter 类型代表一种样本数据单调递增的指标,即只增不减,除非监控系统发生了重置
|
||||
**/
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface PrometheusCounter {
|
||||
|
||||
/**
|
||||
* 如果name有设置值,使用name作为Metric name
|
||||
*/
|
||||
String name() default "";
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
String[] labels() default "";
|
||||
/**
|
||||
* SPEL 参数列表
|
||||
*/
|
||||
String[] parameterKeys() default "";
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
String memo() default "default";
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.lingniu.framework.plugin.prometheus;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface PrometheusMetrics {
|
||||
|
||||
/**
|
||||
* 默认为空,程序使用method signature作为Metric name
|
||||
* 如果name有设置值,使用name作为Metric name
|
||||
*/
|
||||
String name() default "";
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.lingniu.framework.plugin.prometheus;
|
||||
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Summary 类型
|
||||
**/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface PrometheusSummary {
|
||||
|
||||
/**
|
||||
* 如果name有设置值,使用name作为Metric name
|
||||
*/
|
||||
String name() default "";
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
String[] labels() default "";
|
||||
/**
|
||||
* SPEL 参数列表
|
||||
*/
|
||||
String[] parameterKeys() default "";
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
String memo() default "default";
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cn.lingniu.framework.plugin.prometheus.aspect;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 基础类
|
||||
**/
|
||||
@Slf4j
|
||||
public abstract class BasePrometheusAspect {
|
||||
|
||||
/**
|
||||
* SpelExpressionParser 获取参数值
|
||||
*/
|
||||
protected <T> T getValue(EvaluationContext context, String key, Class<T> clazz) {
|
||||
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
|
||||
Expression expression = spelExpressionParser.parseExpression(key);
|
||||
return expression.getValue(context, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数容器
|
||||
*/
|
||||
protected EvaluationContext getContext(Object[] arguments, Method signatureMethod) {
|
||||
String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(signatureMethod);
|
||||
if (parameterNames == null) {
|
||||
throw new IllegalArgumentException("parameterNames is null");
|
||||
}
|
||||
EvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
evaluationContext.setVariable(parameterNames[i], arguments[i]);
|
||||
}
|
||||
return evaluationContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package cn.lingniu.framework.plugin.prometheus.aspect;
|
||||
|
||||
import cn.lingniu.framework.plugin.prometheus.PrometheusCounter;
|
||||
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Count实现类
|
||||
**/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
public class PrometheusCounterAspect extends BasePrometheusAspect {
|
||||
|
||||
@Autowired
|
||||
PrometheusService prometheusService;
|
||||
|
||||
@Pointcut("@annotation(cn.lingniu.framework.plugin.prometheus.PrometheusCounter)")
|
||||
public void prometheusCounterAop() {
|
||||
}
|
||||
|
||||
@Around("prometheusCounterAop() && @annotation(prometheusCounter)")
|
||||
public Object prometheusCounterAspect(ProceedingJoinPoint point, PrometheusCounter prometheusCounter) throws Throwable {
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
Method signatureMethod = signature.getMethod();
|
||||
String countName = ObjectEmptyUtils.isEmpty(prometheusCounter.name()) ? signatureMethod.getName() : prometheusCounter.name();
|
||||
Object val = null;
|
||||
try {
|
||||
val = point.proceed();
|
||||
if (ObjectEmptyUtils.isNotEmpty(prometheusCounter.parameterKeys())) {
|
||||
prometheusService.count(countName, () -> buildTags(point, prometheusCounter, "SUCCESS"));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (ObjectEmptyUtils.isNotEmpty(prometheusCounter.parameterKeys())) {
|
||||
prometheusService.count(countName, () -> buildTags(point, prometheusCounter, "FAILED"));
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
private List<Tag> buildTags(ProceedingJoinPoint point, PrometheusCounter prometheusCounter, String responseStatus) {
|
||||
EvaluationContext context = getContext(point.getArgs(), ((MethodSignature) point.getSignature()).getMethod());
|
||||
List<Tag> tags = new ArrayList<>();
|
||||
|
||||
if (ObjectEmptyUtils.isEmpty(prometheusCounter.labels()) || prometheusCounter.labels().length != prometheusCounter.parameterKeys().length) {
|
||||
// 使用parameterKeys作为标签名
|
||||
for (String parameterKey : prometheusCounter.parameterKeys()) {
|
||||
if (!ObjectEmptyUtils.isEmpty(parameterKey)) {
|
||||
String cleanKey = parameterKey.replace("#", "");
|
||||
tags.add(Tag.of(cleanKey, getValue(context, parameterKey, String.class)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 使用labels作为标签名
|
||||
for (int i = 0; i < prometheusCounter.labels().length; i++) {
|
||||
tags.add(Tag.of(prometheusCounter.labels()[i], getValue(context, prometheusCounter.parameterKeys()[i], String.class)));
|
||||
}
|
||||
}
|
||||
tags.add(Tag.of("response", responseStatus));
|
||||
return tags;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.lingniu.framework.plugin.prometheus.aspect;
|
||||
|
||||
import cn.lingniu.framework.plugin.prometheus.PrometheusMetrics;
|
||||
import io.prometheus.client.Counter;
|
||||
import io.prometheus.client.Histogram;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
public class PrometheusMetricsAspect {
|
||||
|
||||
private static final Counter requestTotal = Counter.build().name("api_request_total").labelNames("api").help
|
||||
("total request counter of api").register();
|
||||
private static final Counter requestError = Counter.build().name("api_request_error_total").labelNames("api").help
|
||||
("response error counter of api").register();
|
||||
private static final Histogram histogram = Histogram.build().name("api_response_duration_seconds").labelNames("api").help
|
||||
("response consuming of api").register();
|
||||
|
||||
@Pointcut("@annotation(cn.lingniu.framework.plugin.prometheus.PrometheusMetrics)")
|
||||
public void prometheusMetricsAop() {
|
||||
}
|
||||
|
||||
@Around(value = "prometheusMetricsAop() && @annotation(prometheusMetrics)")
|
||||
public Object prometheusMetricsAspect(ProceedingJoinPoint joinPoint, PrometheusMetrics prometheusMetrics) throws Throwable {
|
||||
String name;
|
||||
if (StringUtils.isNotEmpty(prometheusMetrics.name())) {
|
||||
name = prometheusMetrics.name();
|
||||
} else {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
if (requestAttributes == null) { // 如果没有请求上下文,使用方法名作为指标名
|
||||
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||
name = methodSignature.getMethod().getName();
|
||||
} else {// 对URI进行安全过滤,防止路径注入
|
||||
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
String requestURI = request.getRequestURI();
|
||||
name = sanitizeUri(requestURI);
|
||||
}
|
||||
}
|
||||
requestTotal.labels(name).inc();
|
||||
Histogram.Timer requestTimer = histogram.labels(name).startTimer();
|
||||
Object object;
|
||||
try {
|
||||
object = joinPoint.proceed();
|
||||
} catch (Throwable t) {
|
||||
requestError.labels(name).inc();
|
||||
throw t;
|
||||
} finally {
|
||||
requestTimer.observeDuration();
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除可能的危险字符,只保留字母、数字、斜杠、点、连字符和下划线
|
||||
*/
|
||||
private String sanitizeUri(String uri) {
|
||||
if (uri == null) {
|
||||
return "unknown";
|
||||
}
|
||||
return uri.replaceAll("[^a-zA-Z0-9/._-]", "_");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package cn.lingniu.framework.plugin.prometheus.aspect;
|
||||
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.ToDoubleFunction;
|
||||
|
||||
/**
|
||||
* PrometheusService 接口
|
||||
**/
|
||||
public interface PrometheusService {
|
||||
|
||||
/**
|
||||
* 摘要统计指标
|
||||
* @param metricName 指标名称,不能为空
|
||||
* @param labelsProvider 标签提供者,不能为空
|
||||
* @param amt 统计值,不能为NaN或无穷大
|
||||
*/
|
||||
void summary(String metricName, Supplier<List<Tag>> labelsProvider, double amt);
|
||||
|
||||
/**
|
||||
* 计数指标
|
||||
* @param metricName 指标名称,不能为空
|
||||
* @param labelsProvider 标签提供者,不能为空
|
||||
*/
|
||||
void count(String metricName, Supplier<List<Tag>> labelsProvider);
|
||||
|
||||
/**
|
||||
* 观察值指标
|
||||
* @param metricName 指标名称,不能为空
|
||||
* @param labelsProvider 标签提供者,不能为空
|
||||
* @param amt 观察值,不能为空且必须为有效数值
|
||||
* @param <T> 数值类型,必须继承自Number
|
||||
*/
|
||||
<T extends Number> void gauge(String metricName, Supplier<List<Tag>> labelsProvider, T amt);
|
||||
|
||||
/**
|
||||
* 观察值指标(带转换函数)
|
||||
* @param metricName 指标名称,不能为空
|
||||
* @param labelsProvider 标签提供者,不能为空
|
||||
* @param amt 观察值,不能为空
|
||||
* @param valueFunction 值转换函数,不能为空
|
||||
* @param <T> 数值类型,必须继承自Number
|
||||
*/
|
||||
<T extends Number> void gauge(String metricName, Supplier<List<Tag>> labelsProvider, T amt, ToDoubleFunction<T> valueFunction);
|
||||
|
||||
/**
|
||||
* 计时统计
|
||||
* @param metricName 指标名称,不能为空
|
||||
* @param labelsProvider 标签提供者,不能为空
|
||||
* @param amt 时间值,不能为null且不能为NaN或无穷大
|
||||
*/
|
||||
void timer(String metricName, Supplier<List<Tag>> labelsProvider, Double amt);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package cn.lingniu.framework.plugin.prometheus.aspect;
|
||||
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.ToDoubleFunction;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class PrometheusServiceImpl implements PrometheusService {
|
||||
|
||||
/**
|
||||
* @param metricName
|
||||
* @param lablesProvider
|
||||
*/
|
||||
@Override
|
||||
public void count(String metricName, Supplier<List<Tag>> lablesProvider) {
|
||||
if (metricName == null || lablesProvider == null) {
|
||||
throw new IllegalArgumentException("metricName and lablesProvider cannot be null");
|
||||
}
|
||||
try {
|
||||
List<Tag> labels = lablesProvider.get();
|
||||
if (labels == null) {
|
||||
throw new IllegalArgumentException("Labels from lablesProvider cannot be null");
|
||||
}
|
||||
Metrics.counter(metricName, labels).increment();
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Number> void gauge(String metricName, Supplier<List<Tag>> lablesProvider, T amt) {
|
||||
if (metricName == null || lablesProvider == null || amt == null) {
|
||||
throw new IllegalArgumentException("metricName, lablesProvider, and amt cannot be null");
|
||||
}
|
||||
try {
|
||||
List<Tag> labels = lablesProvider.get();
|
||||
if (labels == null) {
|
||||
throw new IllegalArgumentException("Labels from lablesProvider cannot be null");
|
||||
}
|
||||
Metrics.gauge(metricName, labels, amt, (l) -> l.doubleValue());
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Number> void gauge(String metricName, Supplier<List<Tag>> lablesProvider, T amt, ToDoubleFunction<T> valueFunction) {
|
||||
if (metricName == null || lablesProvider == null || amt == null || valueFunction == null) {
|
||||
throw new IllegalArgumentException("metricName, lablesProvider, amt, and valueFunction cannot be null");
|
||||
}
|
||||
try {
|
||||
List<Tag> labels = lablesProvider.get();
|
||||
if (labels == null) {
|
||||
throw new IllegalArgumentException("Labels from lablesProvider cannot be null");
|
||||
}
|
||||
Metrics.gauge(metricName, labels, amt, valueFunction);
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void timer(String metricName, Supplier<List<Tag>> lablesProvider, Double amt) {
|
||||
if (metricName == null || lablesProvider == null || amt == null) {
|
||||
throw new IllegalArgumentException("metricName, lablesProvider, and amt cannot be null");
|
||||
}
|
||||
try {
|
||||
List<Tag> labels = lablesProvider.get();
|
||||
if (labels == null) {
|
||||
throw new IllegalArgumentException("Labels from lablesProvider cannot be null");
|
||||
}
|
||||
long duration = amt.longValue();
|
||||
if (duration < 0) {
|
||||
throw new IllegalArgumentException("Timer duration cannot be negative: " + amt);
|
||||
}
|
||||
Metrics.timer(metricName, labels).record(duration, TimeUnit.MICROSECONDS);
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param metricName
|
||||
* @param lablesProvider
|
||||
*/
|
||||
@Override
|
||||
public void summary(String metricName, Supplier<List<Tag>> lablesProvider, double amt) {
|
||||
if (metricName == null || lablesProvider == null) {
|
||||
throw new IllegalArgumentException("metricName and lablesProvider cannot be null");
|
||||
}
|
||||
try {
|
||||
List<Tag> labels = lablesProvider.get();
|
||||
if (labels == null) {
|
||||
throw new IllegalArgumentException("Labels from lablesProvider cannot be null");
|
||||
}
|
||||
Metrics.summary(metricName, labels).record(amt);
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package cn.lingniu.framework.plugin.prometheus.aspect;
|
||||
|
||||
import cn.lingniu.framework.plugin.prometheus.PrometheusSummary;
|
||||
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description: Summary实现类
|
||||
**/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
public class PrometheusSummaryAspect extends BasePrometheusAspect {
|
||||
|
||||
@Autowired
|
||||
PrometheusService prometheusService;
|
||||
|
||||
@Pointcut("@annotation(cn.lingniu.framework.plugin.prometheus.PrometheusSummary)")
|
||||
public void prometheusSummaryAop() {
|
||||
}
|
||||
|
||||
|
||||
@Around("prometheusSummaryAop() && @annotation(prometheusSummary)")
|
||||
public Object prometheusSummaryAspect(ProceedingJoinPoint point, PrometheusSummary prometheusSummary) throws Throwable {
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
Method signatureMethod = signature.getMethod();
|
||||
String summaryName = ObjectEmptyUtils.isEmpty(prometheusSummary.name()) ? signatureMethod.getName() : prometheusSummary.name();
|
||||
|
||||
if (ObjectEmptyUtils.isEmpty(prometheusSummary.parameterKeys())) {
|
||||
return point.proceed();
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
boolean success = false;
|
||||
Object result = null;
|
||||
|
||||
try {
|
||||
result = point.proceed();
|
||||
success = true;
|
||||
return result;
|
||||
} finally {
|
||||
long endTime = System.currentTimeMillis();
|
||||
long duration = endTime - startTime;
|
||||
|
||||
List<Tag> tags = buildTags(point, signatureMethod, prometheusSummary, success ? "SUCCESS" : "FAILED");
|
||||
prometheusService.summary(summaryName, () -> tags, duration);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Tag> buildTags(ProceedingJoinPoint point, Method method, PrometheusSummary prometheusSummary, String responseStatus) {
|
||||
EvaluationContext context = getContext(point.getArgs(), method);
|
||||
List<Tag> tags = new ArrayList<>();
|
||||
|
||||
String[] parameterKeys = prometheusSummary.parameterKeys();
|
||||
String[] labels = prometheusSummary.labels();
|
||||
|
||||
// 构建标签,优先使用labels数组,否则使用parameterKeys作为标签名
|
||||
if (ObjectEmptyUtils.isEmpty(labels) || labels.length != parameterKeys.length) {
|
||||
for (String paramKey : parameterKeys) {
|
||||
if (!ObjectEmptyUtils.isEmpty(paramKey)) {
|
||||
String cleanKey = sanitizeTagKey(paramKey.replace("#", ""));
|
||||
String value = getValue(context, paramKey, String.class);
|
||||
tags.add(Tag.of(cleanKey, value));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < labels.length; i++) {
|
||||
String cleanLabel = sanitizeTagKey(labels[i]);
|
||||
String value = getValue(context, parameterKeys[i], String.class);
|
||||
tags.add(Tag.of(cleanLabel, value));
|
||||
}
|
||||
}
|
||||
tags.add(Tag.of("response", responseStatus));
|
||||
return tags;
|
||||
}
|
||||
|
||||
private String sanitizeTagKey(String key) {
|
||||
// 防止标签键名中的特殊字符造成问题
|
||||
if (key == null) {
|
||||
return "unknown";
|
||||
}
|
||||
// 只保留字母数字下划线连字符,并限制长度
|
||||
return key.replaceAll("[^a-zA-Z0-9_-]", "_").substring(0, Math.min(key.length(), 64));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.lingniu.framework.plugin.prometheus.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@ConfigurationProperties(PrometheusConfig.PRE_FIX)
|
||||
public class PrometheusConfig {
|
||||
|
||||
public static final String PRE_FIX = "framework.lingniu.prometheus";
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private Boolean enabled = true;
|
||||
/**
|
||||
* 端点暴露端口,对外应用接口必须指定
|
||||
*/
|
||||
private Integer port = 30290;
|
||||
/**
|
||||
* 是否特殊节点:一般当同机器部署多应用时启用,true时才允许启用自定义管理端点端口
|
||||
*/
|
||||
private Boolean allowAssignPort = false;
|
||||
/**
|
||||
* 端点暴露
|
||||
*/
|
||||
private String exposures = "env, health, info, metrics, prometheus, threaddump";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package cn.lingniu.framework.plugin.prometheus.init;
|
||||
|
||||
|
||||
import cn.lingniu.framework.plugin.prometheus.aspect.PrometheusCounterAspect;
|
||||
import cn.lingniu.framework.plugin.prometheus.aspect.PrometheusMetricsAspect;
|
||||
import cn.lingniu.framework.plugin.prometheus.aspect.PrometheusSummaryAspect;
|
||||
import cn.lingniu.framework.plugin.prometheus.config.PrometheusConfig;
|
||||
import cn.lingniu.framework.plugin.prometheus.aspect.PrometheusService;
|
||||
import cn.lingniu.framework.plugin.prometheus.aspect.PrometheusServiceImpl;
|
||||
import cn.lingniu.framework.plugin.util.config.PropertyUtils;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.prometheus.client.CollectorRegistry;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
|
||||
@Configuration
|
||||
@Import({PrometheusCounterAspect.class, PrometheusMetricsAspect.class, PrometheusSummaryAspect.class})
|
||||
@EnableConfigurationProperties({PrometheusConfig.class})
|
||||
@Slf4j
|
||||
public class PrometheusConfiguration implements CommandLineRunner {
|
||||
|
||||
@Autowired
|
||||
PrometheusConfig prometheusConfig;
|
||||
@Value("${spring.application.name: 填写项目名称}")
|
||||
private String applicationName;
|
||||
|
||||
@Bean
|
||||
MeterRegistryCustomizer<MeterRegistry> appMetricsCommonTags() {
|
||||
return registry -> registry.config().commonTags("application", applicationName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) {
|
||||
if (log.isInfoEnabled()) {
|
||||
try {
|
||||
String port = (String) PropertyUtils.getProperty("management.server.port");
|
||||
// 验证端口号格式
|
||||
if (port != null && !port.trim().isEmpty()) {
|
||||
try {
|
||||
int portNum = Integer.parseInt(port.trim());
|
||||
if (portNum < 1 || portNum > 65535) {
|
||||
log.warn("Invalid management server port configuration: {}", port);
|
||||
port = "unknown";
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Management server port is not a valid number: {}", port);
|
||||
port = "invalid";
|
||||
}
|
||||
} else {
|
||||
port = "not configured";
|
||||
}
|
||||
log.info("\r\n\t\t框架已开启Prometheus监控,信息收集端口:{},请至Grafana查询服务监控信息 ! ", port);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to get management server port, error: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
CollectorRegistry metricRegistry() {
|
||||
return CollectorRegistry.defaultRegistry;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public PrometheusService prometheusService() {
|
||||
return new PrometheusServiceImpl();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package cn.lingniu.framework.plugin.prometheus.init;
|
||||
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.lingniu.framework.plugin.core.config.CommonConstant;
|
||||
import cn.lingniu.framework.plugin.util.string.StringUtil;
|
||||
import cn.lingniu.framework.plugin.util.config.PropertyUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Profiles;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Order(Integer.MIN_VALUE + 400)
|
||||
public class PrometheusInit implements ApplicationContextInitializer<ConfigurableApplicationContext> {
|
||||
//server port
|
||||
public static final String SERVER_PORT_PROPERTY = "server.port";
|
||||
//默认管理端点端口
|
||||
public static final Integer DEFAULT_ACTUATOR_PORT = 30290;
|
||||
private static final String DEFAULT_ENDPOINT = "prometheus,feign,metrics";
|
||||
private static boolean initialized = false;
|
||||
private String applicationName;
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
if (applicationContext == null) {
|
||||
throw new IllegalArgumentException("Application context cannot be null");
|
||||
}
|
||||
ConfigurableEnvironment environment = applicationContext.getEnvironment();
|
||||
if (environment == null) {
|
||||
throw new IllegalStateException("Environment cannot be null");
|
||||
}
|
||||
applicationName = environment.getProperty(CommonConstant.SPRING_APP_NAME_KEY);
|
||||
if (applicationName == null || applicationName.trim().isEmpty()) {
|
||||
throw new IllegalStateException("Application name property is required");
|
||||
}
|
||||
String profile = environment.getProperty(CommonConstant.ACTIVE_PROFILES_PROPERTY);
|
||||
// profile can be null, which is acceptable
|
||||
this.initializeSystemProperty(environment, applicationContext, applicationName, profile);
|
||||
}
|
||||
|
||||
void initializeSystemProperty(ConfigurableEnvironment environment, ConfigurableApplicationContext applicationContext, String appName, String profile) {
|
||||
// 使用volatile确保多线程环境下的可见性
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
// 检查prometheus是否启用
|
||||
String prometheusEnabled = environment.getProperty("framework.lingniu.prometheus.enabled", "true");
|
||||
if (Boolean.FALSE.toString().equalsIgnoreCase(prometheusEnabled)) {
|
||||
return;
|
||||
}
|
||||
Integer serverPort = environment.getProperty(SERVER_PORT_PROPERTY, Integer.class, 3001);
|
||||
Integer configPort = determineConfigPort(environment, serverPort);
|
||||
// 确保配置端口不与服务器端口冲突
|
||||
if (configPort.equals(serverPort)) {
|
||||
configPort = getAvailableRandomPort(environment, serverPort);
|
||||
}
|
||||
setInitialized(true);
|
||||
setManagementProperties(environment, configPort);
|
||||
}
|
||||
|
||||
private Integer determineConfigPort(ConfigurableEnvironment environment, Integer serverPort) {
|
||||
Integer defaultPort = DEFAULT_ACTUATOR_PORT;
|
||||
Boolean allowAssignPort = environment.acceptsProfiles(Profiles.of("sit", "dev", "uat")) ||
|
||||
environment.getProperty("framework.lingniu.prometheus.allow-assign-port", Boolean.class, false);
|
||||
if (allowAssignPort) {
|
||||
return environment.getProperty("framework.lingniu.prometheus.port", Integer.class, defaultPort);
|
||||
}
|
||||
return defaultPort;
|
||||
}
|
||||
|
||||
private Integer getAvailableRandomPort(ConfigurableEnvironment environment, Integer serverPort) {
|
||||
Integer randomPort;
|
||||
int attempts = 0;
|
||||
int maxAttempts = 10; // 防止无限循环
|
||||
do {
|
||||
randomPort = RandomUtil.randomInt(2000, 5000);
|
||||
attempts++;
|
||||
if (attempts >= maxAttempts) {
|
||||
throw new RuntimeException("Failed to find available port after " + maxAttempts + " attempts");
|
||||
}
|
||||
} while (randomPort.equals(serverPort));
|
||||
|
||||
return randomPort;
|
||||
}
|
||||
|
||||
private void setManagementProperties(ConfigurableEnvironment environment, Integer configPort) {
|
||||
PropertyUtils.setDefaultInitProperty("management.server.port", configPort.toString());
|
||||
PropertyUtils.setDefaultInitProperty("management.endpoint.health.show-details", "always");
|
||||
String resultIncludes = buildEndpointIncludes(environment);
|
||||
PropertyUtils.setDefaultInitProperty("management.endpoints.web.exposure.include", resultIncludes);
|
||||
}
|
||||
|
||||
private String buildEndpointIncludes(ConfigurableEnvironment environment) {
|
||||
String resultIncludes = DEFAULT_ENDPOINT;
|
||||
String configIncludes = environment.getProperty("framework.lingniu.prometheus.exposures");
|
||||
if (!StringUtil.isEmpty(configIncludes)) {
|
||||
resultIncludes = resultIncludes + "," + configIncludes;
|
||||
}
|
||||
return resultIncludes;
|
||||
}
|
||||
|
||||
|
||||
private boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
private void setInitialized(boolean value) {
|
||||
initialized = value;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.lingniu.framework.plugin.prometheus.init.PrometheusConfiguration
|
||||
org.springframework.context.ApplicationContextInitializer=\
|
||||
cn.lingniu.framework.plugin.prometheus.init.PrometheusInit
|
||||
@@ -0,0 +1,34 @@
|
||||
# 【重要】prometheus graf...更多使用请参考官方资料
|
||||
|
||||
## 概述 (Overview)
|
||||
|
||||
1. 定位:集成 prometheus监控框架的应用级监控组件,性能指标统计及异常监控能
|
||||
2. 核心能力
|
||||
* 支持自定义业务埋点,记录关键业务流程的性能与异常信息
|
||||
* jvm指标数据
|
||||
* 与grafana联动,实现监控数据可视化与告警
|
||||
3. 适用场景
|
||||
* 用接口性能监控(响应时间、成功率)
|
||||
* 业务流程异常追踪与问题定位
|
||||
* 系统整体运行状态监控
|
||||
* 业务指标大屏告警
|
||||
|
||||
## 如何配置--参考:PrometheusConfig
|
||||
|
||||
```yaml
|
||||
framework:
|
||||
lingniu:
|
||||
prometheus:
|
||||
enabled: true
|
||||
port: 30290
|
||||
allowAssignPort: false
|
||||
```
|
||||
|
||||
## 如何使用
|
||||
|
||||
参考三个注解、或使用原生方法使用
|
||||
- PrometheusCounter
|
||||
- PrometheusMetrics
|
||||
- PrometheusSummary
|
||||
|
||||
|
||||
Reference in New Issue
Block a user