Initial commit

This commit is contained in:
Eric
2026-01-16 18:51:16 +08:00
commit 98c057de11
280 changed files with 16665 additions and 0 deletions

View File

@@ -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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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/._-]", "_");
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();
}
}

View File

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

View File

@@ -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

View File

@@ -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