Prometheus监控之Micrometer支持多端点URL

一、背景java

使用prometheus作监控系统时(java),通常的作法就是系统暴露端点URL给 prometheus,诸如 /metrics ,而后prometheus拉取这个url中的指标数据,
主要用到的东西有(spring-boot-starter-actuator, micrometer-registry-prometheus)
可是 问题是,默认暴露的端点是 /prometheus,全路径为 /actuator/prometheus,只有一个URL,那么若是有这样的场景该如何:
  • prometheus 限制单个URL中的指标数据不能超过 1W
  • 若用来作业务指标监控,每一个业务方都想用不一样的URL暴露指标(应该不会想在一个URL里放全部业务的指标)

那么就须要添加多个URL,该如何作呢?git

二、实践github

查看micrometer源码(主要是PrometheusMetricsExportAutoConfiguration此类),能够知道默认端点prometheus的初始化流程,
也没查到其余公开的API能够方便的添加暴露多个端点URL,那么就能够仿照他的流程本身再写一套这个配置,经实践本身写的配置不会太多,
例如 我想暴露一个 apple的端点,即 /actuator/apple的URL,那么代码以下:web

首先是定义端点spring

@WebEndpoint(id = "apple")
public class AppleScrapeEndPoint {

    private final CollectorRegistry collectorRegistry;

    public AppleScrapeEndPoint(CollectorRegistry collectorRegistry) {
        this.collectorRegistry = collectorRegistry;
    }

    @ReadOperation(produces = TextFormat.CONTENT_TYPE_004)
    public String scrape() {
        try {
            Writer writer = new StringWriter();
            TextFormat.write004(writer, this.collectorRegistry.metricFamilySamples());
            return writer.toString();
        } catch (IOException ex) {
            // This actually never happens since StringWriter::write() doesn't throw any
            // IOException
            throw new RuntimeException("Writing metrics failed", ex);
        }
    }
}

而后是定义ApplePropertiesConfigAdapterapp

public class ApplePropertiesConfigAdapter extends PropertiesConfigAdapter<PrometheusProperties>
        implements PrometheusConfig {

    ApplePropertiesConfigAdapter(PrometheusProperties properties) {
        super(properties);
    }

    @Override
    public String get(String key) {
        return null;
    }

    @Override
    public boolean descriptions() {
        return get(PrometheusProperties::isDescriptions, PrometheusConfig.super::descriptions);
    }

    @Override
    public Duration step() {
        return get(PrometheusProperties::getStep, PrometheusConfig.super::step);
    }

}

最后是配置初始化ide

@Configuration
@AutoConfigureAfter(value = {PrometheusMetricsExportAutoConfiguration.class})
@ConditionalOnClass(value = {PrometheusMeterRegistry.class})
@ConditionalOnProperty(prefix = "management.metrics.export.apple", name = "enabled", havingValue = "true",
        matchIfMissing = true)
public class ApplePrometheusAutoConfiguration {

    @Bean(name = "applePrometheusProperties")
    @ConfigurationProperties(prefix = "management.metrics.export.apple")
    public PrometheusProperties applePrometheusProperties() {
        return new PrometheusProperties();
    }

    @Bean(name = "applePrometheusConfig")
    public PrometheusConfig applePrometheusConfig() {
        return new ApplePropertiesConfigAdapter(applePrometheusProperties());
    }

    @Bean(name = "appleMeterRegistry")
    public PrometheusMeterRegistry appleMeterRegistry(Clock clock) {
        return new PrometheusMeterRegistry(applePrometheusConfig(), appleCollectorRegistry(), clock);
    }

    @Bean(name = "appleCollectorRegistry")
    public CollectorRegistry appleCollectorRegistry() {
        System.out.println("=======appleCollectorRegistry");
        return new CollectorRegistry(true);
    }

    @Configuration
    @ConditionalOnEnabledEndpoint(endpoint = AppleScrapeEndPoint.class)
    public static class TicketScrapeEndpointConfiguration {

        @Resource
        private CollectorRegistry appleCollectorRegistry;

        @Bean(name = "appleEndpoint")
        @ConditionalOnMissingBean
        public AppleScrapeEndPoint appleEndpoint() {
            return new AppleScrapeEndPoint(appleCollectorRegistry);
        }

    }

}

而后再配置文件中配置新添加的端点spring-boot

management:
  endpoint:
    prometheus:
      # 关闭默认的prometheus端点,新建本身的
      enabled: false
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: ['health', 'apple']

这样就完成了,就能够在 /actuator这个URL中看到本身新添加的URL。
若是想添加多个,那么照着上述copy一份代码,改更名称啥的就能够了,别忘了在配置文件中include里添加。post

这样基本能解决问题了,可是看着不太舒服,我有多个URL就须要COPY多份这样的代码,并且基本还差很少同样,因此咱们能够考虑 主动配置,
减小重复代码的建立,具体以下:ui

例如我想再添加一个a的端点,即 /actuator/a

首先是定义端点:

@Component
@DatagridEndpoint
@WebEndpoint(id = "a")
public class AEndpoint {

    private CollectorRegistry collectorRegistry;
    public AEndpoint(){
    }

    @ReadOperation(produces = TextFormat.CONTENT_TYPE_004)
    public String scrape() {
        try {
            Writer writer = new StringWriter();
            TextFormat.write004(writer, this.collectorRegistry.metricFamilySamples());
            return writer.toString();
        } catch (IOException ex) {
            // This actually never happens since StringWriter::write() doesn't throw any
            // IOException
            throw new RuntimeException("Writing metrics failed", ex);
        }
    }
}

而后是配置流程

@Slf4j
@Component
@AutoConfigureAfter(value = {PrometheusMetricsExportAutoConfiguration.class})
@ConditionalOnClass(value = {PrometheusMeterRegistry.class})
public class MetricsExportAutoConfiguration implements BeanDefinitionRegistryPostProcessor,
        ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public class AutoPropertiesConfigAdapter extends PropertiesConfigAdapter<PrometheusProperties>
            implements io.micrometer.prometheus.PrometheusConfig {

        AutoPropertiesConfigAdapter(PrometheusProperties properties) {
            super(properties);
        }

        @Override
        public String get(String key) {
            return null;
        }

        @Override
        public boolean descriptions() {
            return get(PrometheusProperties::isDescriptions, io.micrometer.prometheus.PrometheusConfig.super::descriptions);
        }

        @Override
        public Duration step() {
            return get(PrometheusProperties::getStep, PrometheusConfig.super::step);
        }

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {

        Map<String, Object> beansMap = applicationContext.getBeansWithAnnotation(DatagridEndpoint.class);
        if (CollectionUtils.isEmpty(beansMap)) {
            return;
        }

        Clock clock = applicationContext.getBean(Clock.class);
        Preconditions.checkNotNull(clock);

        for (Map.Entry<String, Object> entry : beansMap.entrySet()) {
            Object bean = entry.getValue();
            WebEndpoint webEndpoint = bean.getClass().getAnnotation(WebEndpoint.class);
            if (null == webEndpoint) {
                continue;
            }
            String endPointName = webEndpoint.id();
            if (Strings.isNullOrEmpty(endPointName)) {
                continue;
            }
            // prometheus properties bean
            BeanDefinitionBuilder prometheusPropertiesBeanDefinitionBuilder = BeanDefinitionBuilder
                    .genericBeanDefinition(PrometheusProperties.class);
            BeanDefinition prometheusPropertiesBeanDefinition = prometheusPropertiesBeanDefinitionBuilder.getRawBeanDefinition();
            ((DefaultListableBeanFactory) factory).registerBeanDefinition(endPointName + "PrometheusProperties", prometheusPropertiesBeanDefinition);

            PrometheusProperties prometheusProperties = applicationContext.getBean(endPointName + "PrometheusProperties", PrometheusProperties.class);

            // prometheus config bean
            BeanDefinitionBuilder prometheusConfigBeanDefinitionBuilder = BeanDefinitionBuilder
                    .genericBeanDefinition(AutoPropertiesConfigAdapter.class, () -> new AutoPropertiesConfigAdapter(prometheusProperties));
            BeanDefinition prometheusConfigBeanDefinition = prometheusConfigBeanDefinitionBuilder.getRawBeanDefinition();
            ((DefaultListableBeanFactory) factory).registerBeanDefinition(endPointName + "PrometheusConfig", prometheusConfigBeanDefinition);

            // collector registry bean
            BeanDefinitionBuilder collectorRegistryBeanDefinitionBuilder = BeanDefinitionBuilder
                    .genericBeanDefinition(CollectorRegistry.class, () -> new CollectorRegistry(true));
            BeanDefinition collectorRegistryBeanDefinition = collectorRegistryBeanDefinitionBuilder.getRawBeanDefinition();
            ((DefaultListableBeanFactory) factory).registerBeanDefinition(endPointName + "CollectorRegistry", collectorRegistryBeanDefinition);

            PrometheusConfig prometheusConfig = applicationContext.getBean(endPointName + "PrometheusConfig", AutoPropertiesConfigAdapter.class);
            CollectorRegistry collectorRegistry = applicationContext.getBean(endPointName + "CollectorRegistry", CollectorRegistry.class);

            // prometheus meter registry bean
            BeanDefinitionBuilder meterRegistryBeanDefinitionBuilder = BeanDefinitionBuilder
                    .genericBeanDefinition(PrometheusMeterRegistry.class, () -> new PrometheusMeterRegistry(prometheusConfig, collectorRegistry, clock));
            BeanDefinition meterRegistryBeanDefinition = meterRegistryBeanDefinitionBuilder.getRawBeanDefinition();
            ((DefaultListableBeanFactory) factory).registerBeanDefinition(endPointName + "MeterRegistry", meterRegistryBeanDefinition);

            Reflect.on(bean).set("collectorRegistry", collectorRegistry);

        }
    }
}

最后也是在配置文件里include添加暴露的端点

management:
  endpoint:
    prometheus:
      # 关闭默认的prometheus端点,新建本身的
      enabled: false
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: ['health', 'apple', 'a']

ok,下次若是想添加额外的,那么只须要建立和端点a同样的类,改下id值,而后再配置文件里include里暴露下就能够了,
MetricsExportAutoConfiguration这个类会自动建立其余的配置,就不须要重复代码了

哦,对,关于DatagridEndpoint这个注解,就只是个简单的注解而已,以下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DatagridEndpoint {
}

这样就完成了。
固然,上述只是很潦草的代码,各位能够看着本身改改,更适合本身的项目!

源码见:https://github.com/kute/prome...

有问题及时联系

相关文章
相关标签/搜索