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

有問題及時聯繫

相關文章
相關標籤/搜索