准备工作
Pom.xml依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
springboot配置文件
management.server.port=8081
management.metrics.tags.application=${spring.application.name}
management.metrics.export.prometheus.enabled=true
management.endpoints.web.exposure.include=prometheus
启动程序,访问:http://127.0.0.1:8081/actuator/prometheus 可以看到指标都是按照Prometheus的格式输出的,默认的监控指标有很多jvm、http、tomcat、logback等;以上就是准备工作,如果有问题,请从头排查是否配置错误。
自定义Prometheus监控指标
利用拦截器实现所有HTTP接口的监控
利用HTTP的拦截器添加Prometheus的监控指标,首先创建一个拦截器CustomInterceptor 实现HandlerInterceptor接口,然后重写里面的 前置处理、后置处理;
import io.micrometer.core.instrument.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class CustomInterceptor implements HandlerInterceptor {
private static final String CUSTOM_KPI_NAME_TIMER = "custom.kpi.timer"; //耗时
private static final String CUSTOM_KPI_NAME_COUNTER = "custom.kpi.counter"; //api调用次数。
private static final String CUSTOM_KPI_NAME_SUMMARY = "custom.kpi.summary"; //汇总率
private static MeterRegistry registry;
private long startTime;
private final GaugeNumber gaugeNumber = new GaugeNumber();
void getRegistry(){
if(registry == null){
//这里使用的时SpringUtil获取Bean,没有用@Autowired注解,Autowired会因为加载时机问题导致拿不到;
registry = SpringUtil.getBean(MeterRegistry.class);
}
}
@Override
public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {
getRegistry();
//记录接口开始调用的时间
startTime = System.currentTimeMillis();
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, @NotNull Object handler, Exception ex) throws Exception {
//统计调用次数
registry.counter(CUSTOM_KPI_NAME_COUNTER,"uri", request.getRequestURI(), "method", request.getMethod(),
"status", String.valueOf(response.getStatus()), "exception", ex == null ? "" : ex.getMessage(), "outcome", response.getStatus() == 200 ? "SUCCESS" : "CLIENT_ERROR").increment();
//统计单次耗时
registry.timer(CUSTOM_KPI_NAME_TIMER,"uri", request.getRequestURI(), "method", request.getMethod(),
"status", String.valueOf(response.getStatus()), "exception", ex == null ? "" : ex.getMessage(), "outcome", response.getStatus() == 200 ? "SUCCESS" : "CLIENT_ERROR").record(System.currentTimeMillis() - startTime, TimeUnit.MILLISECONDS);
//统计调用成功率,根据过滤Counter对象,获取计数
Collection meters = registry.get(CUSTOM_KPI_NAME_COUNTER).tag("uri", request.getRequestURI()).tag("method", request.getMethod()).meters();
double total = 0;
double success = 0;
for (Meter meter : meters) {
Counter counter = (Counter) meter;
total += counter.count();
String status = meter.getId().getTag("status");
if (status != null && status.equals("200")) {
success += counter.count();
}
}
//保存对应的成功率到Map中
String key = request.getMethod() + request.getRequestURI();
gaugeNumber.setPercent(key, success / total * 100L);
registry.gauge(CUSTOM_KPI_NAME_SUMMARY, Tags.of("uri", request.getRequestURI(), "method", request.getMethod()), gaugeNumber, value -> value.getPercent(key));
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
// gauge监控某个对象,所以用内部类替代,然后根据tag标签区分对应的成功率;key 为 method + uri
static class GaugeNumber {
Map map = new HashMap<>();
public Double getPercent(String key) {
return map.get(key);
}
public void setPercent(String key, Double percent) {
map.put(key, percent);
}
}
}
注册自定义拦截器给Spring
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CustomWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/**");
}
}
SpringUtil.getBean
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContextParam) throws BeansException {
applicationContext=applicationContextParam;
}
public static Object getObject(String id) {
Object object = null;
object = applicationContext.getBean(id);
return object;
}
public static T getObject(Class tClass) {
return applicationContext.getBean(tClass);
}
public static Object getBean(String tClass) {
return applicationContext.getBean(tClass);
}
public static T getBean(String str,Class tClass) {
return (T)applicationContext.getBean(str);
}
public static T getBean(Class tClass) {
return applicationContext.getBean(tClass);
}
}