Micrometer custom metrics

  micrometer

Order

This article mainly studies how to use the metrics of the custom micrometer.

Example

DemoMetrics

public class DemoMetrics implements MeterBinder {
    AtomicInteger count = new AtomicInteger(0);

    @Override
    public void bindTo(MeterRegistry meterRegistry) {
        Gauge.builder("demo.count", count, c -> c.incrementAndGet())
                .tags("host", "localhost")
                .description("demo of custom meter binder")
                .register(meterRegistry);
    }
}

The bindTo method of MeterBinder interface is implemented here, and the indexes to be collected are registered in MeterRegistry.

registered

  • Original mode
new DemoMetrics().bindTo(registry);
  • springboot autoconfigure
@Bean
public DemoMetrics demoMetrics(){
    return new DemoMetrics();
}

After springboot just labels the bean and injects it into the spring container, springboot will automatically register with registry. Springboot has already helped you initialize a series of metrics including UptimeMetrics. See the source code analysis section for details.

Verification

curl -i http://localhost:8080/actuator/metrics/demo.count

Return instance

{
  "name": "demo.count",
  "measurements": [
    {
      "statistic": "VALUE",
      "value": 6
    }
  ],
  "availableTags": [
    {
      "tag": "host",
      "values": [
        "localhost"
      ]
    }
  ]
}

Source code analysis

MetricsAutoConfiguration

spring-boot-actuator-autoconfigure-2.0.0.RELEASE-sources.jar! /org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java

@Configuration
@ConditionalOnClass(Timed.class)
@EnableConfigurationProperties(MetricsProperties.class)
@AutoConfigureBefore(CompositeMeterRegistryAutoConfiguration.class)
public class MetricsAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public Clock micrometerClock() {
        return Clock.SYSTEM;
    }

    @Bean
    public static MeterRegistryPostProcessor meterRegistryPostProcessor(
            ApplicationContext context) {
        return new MeterRegistryPostProcessor(context);
    }

    @Bean
    @Order(0)
    public PropertiesMeterFilter propertiesMeterFilter(MetricsProperties properties) {
        return new PropertiesMeterFilter(properties);
    }

    @Configuration
    @ConditionalOnProperty(value = "management.metrics.binders.jvm.enabled", matchIfMissing = true)
    static class JvmMeterBindersConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public JvmGcMetrics jvmGcMetrics() {
            return new JvmGcMetrics();
        }

        @Bean
        @ConditionalOnMissingBean
        public JvmMemoryMetrics jvmMemoryMetrics() {
            return new JvmMemoryMetrics();
        }

        @Bean
        @ConditionalOnMissingBean
        public JvmThreadMetrics jvmThreadMetrics() {
            return new JvmThreadMetrics();
        }

        @Bean
        @ConditionalOnMissingBean
        public ClassLoaderMetrics classLoaderMetrics() {
            return new ClassLoaderMetrics();
        }

    }

    @Configuration
    static class MeterBindersConfiguration {

        @Bean
        @ConditionalOnClass(name = { "ch.qos.logback.classic.LoggerContext",
                "org.slf4j.LoggerFactory" })
        @Conditional(LogbackLoggingCondition.class)
        @ConditionalOnMissingBean(LogbackMetrics.class)
        @ConditionalOnProperty(value = "management.metrics.binders.logback.enabled", matchIfMissing = true)
        public LogbackMetrics logbackMetrics() {
            return new LogbackMetrics();
        }

        @Bean
        @ConditionalOnProperty(value = "management.metrics.binders.uptime.enabled", matchIfMissing = true)
        @ConditionalOnMissingBean
        public UptimeMetrics uptimeMetrics() {
            return new UptimeMetrics();
        }

        @Bean
        @ConditionalOnProperty(value = "management.metrics.binders.processor.enabled", matchIfMissing = true)
        @ConditionalOnMissingBean
        public ProcessorMetrics processorMetrics() {
            return new ProcessorMetrics();
        }

        @Bean
        @ConditionalOnProperty(name = "management.metrics.binders.files.enabled", matchIfMissing = true)
        @ConditionalOnMissingBean
        public FileDescriptorMetrics fileDescriptorMetrics() {
            return new FileDescriptorMetrics();
        }

    }

    static class LogbackLoggingCondition extends SpringBootCondition {

        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context,
                AnnotatedTypeMetadata metadata) {
            ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
            ConditionMessage.Builder message = ConditionMessage
                    .forCondition("LogbackLoggingCondition");
            if (loggerFactory instanceof LoggerContext) {
                return ConditionOutcome.match(
                        message.because("ILoggerFactory is a Logback LoggerContext"));
            }
            return ConditionOutcome
                    .noMatch(message.because("ILoggerFactory is an instance of "
                            + loggerFactory.getClass().getCanonicalName()));
        }

    }

}

You can see that there are many metrics registered here, such as UptimeMetrics, JvmGcMetrics, ProcessorMetrics, FileDescriptorMetrics, etc.

The emphasis here is on marking MeterRegistryPostProcessor with @Bean

MeterRegistryPostProcessor

spring-boot-actuator-autoconfigure-2.0.0.RELEASE-sources.jar! /org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryPostProcessor.java

class MeterRegistryPostProcessor implements BeanPostProcessor {

    private final ApplicationContext context;

    private volatile MeterRegistryConfigurer configurer;

    MeterRegistryPostProcessor(ApplicationContext context) {
        this.context = context;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        if (bean instanceof MeterRegistry) {
            getConfigurer().configure((MeterRegistry) bean);
        }
        return bean;
    }

    @SuppressWarnings("unchecked")
    private MeterRegistryConfigurer getConfigurer() {
        if (this.configurer == null) {
            this.configurer = new MeterRegistryConfigurer(beansOfType(MeterBinder.class),
                    beansOfType(MeterFilter.class),
                    (Collection<MeterRegistryCustomizer<?>>) (Object) beansOfType(
                            MeterRegistryCustomizer.class),
                    this.context.getBean(MetricsProperties.class).isUseGlobalRegistry());
        }
        return this.configurer;
    }

    private <T> Collection<T> beansOfType(Class<T> type) {
        return this.context.getBeansOfType(type).values();
    }

}

You can see here that new has a MeterRegistryConfigurer, focusing on using the return value of the bean softtype (Meterbind. class) method to its constructor.

MeterRegistryConfigurer

spring-boot-actuator-autoconfigure-2.0.0.RELEASE-sources.jar! /org/springframework/boot/actuate/autoconfigure/metrics/MeterRegistryConfigurer.java

class MeterRegistryConfigurer {

    private final Collection<MeterRegistryCustomizer<?>> customizers;

    private final Collection<MeterFilter> filters;

    private final Collection<MeterBinder> binders;

    private final boolean addToGlobalRegistry;

    MeterRegistryConfigurer(Collection<MeterBinder> binders,
            Collection<MeterFilter> filters,
            Collection<MeterRegistryCustomizer<?>> customizers,
            boolean addToGlobalRegistry) {
        this.binders = (binders != null ? binders : Collections.emptyList());
        this.filters = (filters != null ? filters : Collections.emptyList());
        this.customizers = (customizers != null ? customizers : Collections.emptyList());
        this.addToGlobalRegistry = addToGlobalRegistry;
    }

    void configure(MeterRegistry registry) {
        if (registry instanceof CompositeMeterRegistry) {
            return;
        }
        // Customizers must be applied before binders, as they may add custom
        // tags or alter timer or summary configuration.
        customize(registry);
        addFilters(registry);
        addBinders(registry);
        if (this.addToGlobalRegistry && registry != Metrics.globalRegistry) {
            Metrics.addRegistry(registry);
        }
    }

    @SuppressWarnings("unchecked")
    private void customize(MeterRegistry registry) {
        LambdaSafe.callbacks(MeterRegistryCustomizer.class, this.customizers, registry)
                .withLogger(MeterRegistryConfigurer.class)
                .invoke((customizer) -> customizer.customize(registry));
    }

    private void addFilters(MeterRegistry registry) {
        this.filters.forEach(registry.config()::meterFilter);
    }

    private void addBinders(MeterRegistry registry) {
        this.binders.forEach((binder) -> binder.bindTo(registry));
    }

}

You can see that addBinders are called in the configure method, that is, bindTo the MeterBinder instance managed to the spring container to meterRegistry.

Summary

For the micrometer introduced by springboot2, custom metrics only need to implement the MeterBinder interface and then host it to spring. Spring Boots autoconfigure helps you to register to the meterRegistry automatically.

doc