Skip to content

Commit

Permalink
Introduce a way to completely customize MeterRegistry
Browse files Browse the repository at this point in the history
This is done via the new `MeterRegistryCustomizer` interface
the implementations of which are meant to be registered as
CDI beans.

Closes: #32996
  • Loading branch information
geoand committed Jul 7, 2023
1 parent d9fc2d6 commit fbd6e52
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
import io.quarkus.micrometer.runtime.CompositeRegistryCreator;
import io.quarkus.micrometer.runtime.MeterFilterConstraint;
import io.quarkus.micrometer.runtime.MeterFilterConstraints;
import io.quarkus.micrometer.runtime.MeterRegistryCustomizer;
import io.quarkus.micrometer.runtime.MeterRegistryCustomizerConstraint;
import io.quarkus.micrometer.runtime.MeterRegistryCustomizerConstraints;
import io.quarkus.micrometer.runtime.MicrometerCounted;
import io.quarkus.micrometer.runtime.MicrometerCountedInterceptor;
import io.quarkus.micrometer.runtime.MicrometerRecorder;
Expand All @@ -62,6 +65,7 @@ public class MicrometerProcessor {
private static final DotName METER_REGISTRY = DotName.createSimple(MeterRegistry.class.getName());
private static final DotName METER_BINDER = DotName.createSimple(MeterBinder.class.getName());
private static final DotName METER_FILTER = DotName.createSimple(MeterFilter.class.getName());
private static final DotName METER_REGISTRY_CUSTOMIZER = DotName.createSimple(MeterRegistryCustomizer.class.getName());
private static final DotName NAMING_CONVENTION = DotName.createSimple(NamingConvention.class.getName());

private static final DotName COUNTED_ANNOTATION = DotName.createSimple(Counted.class.getName());
Expand Down Expand Up @@ -105,12 +109,15 @@ UnremovableBeanBuildItem registerAdditionalBeans(CombinedIndexBuildItem indexBui
.setUnremovable()
.addBeanClass(ClockProvider.class)
.addBeanClass(CompositeRegistryCreator.class)
.addBeanClass(MeterRegistryCustomizer.class)
.build());

// Add annotations and associated interceptors
additionalBeans.produce(AdditionalBeanBuildItem.builder()
.addBeanClass(MeterFilterConstraint.class)
.addBeanClass(MeterFilterConstraints.class)
.addBeanClass(MeterRegistryCustomizerConstraint.class)
.addBeanClass(MeterRegistryCustomizerConstraints.class)
.addBeanClass(TIMED_ANNOTATION.toString())
.addBeanClass(TIMED_INTERCEPTOR.toString())
.addBeanClass(COUNTED_ANNOTATION.toString())
Expand All @@ -136,7 +143,8 @@ public List<InterceptorBinding> getAdditionalBindings() {
"org.HdrHistogram.ConcurrentHistogram")
.build());

return UnremovableBeanBuildItem.beanTypes(METER_REGISTRY, METER_BINDER, METER_FILTER, NAMING_CONVENTION);
return UnremovableBeanBuildItem.beanTypes(METER_REGISTRY, METER_BINDER, METER_FILTER, METER_REGISTRY_CUSTOMIZER,
NAMING_CONVENTION);
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.micrometer.runtime;

import io.micrometer.core.instrument.MeterRegistry;

/**
* Meant to be implemented by a CDI bean that provides arbitrary customization for various {@link MeterRegistry} classes
* registered by Quarkus.
* <p>
* Unless an implementation is annotated with {@link MeterRegistryCustomizerConstraint}, it will apply to all
* {@link MeterRegistry} classes.
*/
public interface MeterRegistryCustomizer extends Comparable<MeterRegistryCustomizer> {

int MINIMUM_PRIORITY = Integer.MIN_VALUE;
// we use this priority to give a chance to other customizers to override serializers / deserializers
// that might have been added by the modules that Quarkus registers automatically
// (Jackson will keep the last registered serializer / deserializer for a given type
// if multiple are registered)
int QUARKUS_CUSTOMIZER_PRIORITY = MINIMUM_PRIORITY + 100;
int DEFAULT_PRIORITY = 0;

void customize(MeterRegistry registry);

default int priority() {
return DEFAULT_PRIORITY;
}

default int compareTo(MeterRegistryCustomizer o) {
return Integer.compare(o.priority(), priority());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.micrometer.runtime;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import jakarta.enterprise.util.AnnotationLiteral;
import jakarta.inject.Qualifier;

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD })
@Repeatable(MeterRegistryCustomizerConstraints.class)
public @interface MeterRegistryCustomizerConstraint {
Class<?> applyTo();

final class Literal extends AnnotationLiteral<MeterRegistryCustomizerConstraint> implements
MeterRegistryCustomizerConstraint {
private static final long serialVersionUID = 1L;
private final Class<?> clazz;

public Literal(Class<?> clazz) {
this.clazz = clazz;
}

@Override
public Class<?> applyTo() {
return clazz;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkus.micrometer.runtime;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD })
public @interface MeterRegistryCustomizerConstraints {
MeterRegistryCustomizerConstraint[] value();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.micrometer.runtime;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -66,32 +67,22 @@ public void configureRegistries(MicrometerConfig config,
BeanManager beanManager = Arc.container().beanManager();

Map<Class<? extends MeterRegistry>, List<MeterFilter>> classMeterFilters = new HashMap<>(registryClasses.size());

// Find global/common registry configuration
List<MeterFilter> globalFilters = new ArrayList<>();
Instance<MeterFilter> globalFilterInstance = beanManager.createInstance()
.select(MeterFilter.class, Default.Literal.INSTANCE);
populateListWithMeterFilters(globalFilterInstance, globalFilters);
log.debugf("Discovered global MeterFilters : %s", globalFilters);
populateMeterFilters(registryClasses, beanManager, classMeterFilters, globalFilters);

// Find MeterFilters for specific registry classes, i.e.:
// @MeterFilterConstraint(applyTo = DatadogMeterRegistry.class) Instance<MeterFilter> filters
log.debugf("Configuring Micrometer registries : %s", registryClasses);
for (Class<? extends MeterRegistry> typeClass : registryClasses) {
Instance<MeterFilter> classFilterInstance = beanManager.createInstance()
.select(MeterFilter.class, new MeterFilterConstraint.Literal(typeClass));
List<MeterFilter> classFilters = classMeterFilters.computeIfAbsent(typeClass, k -> new ArrayList<>());

populateListWithMeterFilters(classFilterInstance, classFilters);
log.debugf("Discovered MeterFilters for %s: %s", typeClass, classFilters);
}
Map<Class<? extends MeterRegistry>, List<MeterRegistryCustomizer>> classMeterRegistryCustomizers = new HashMap<>(
registryClasses.size());
List<MeterRegistryCustomizer> globalMeterRegistryCustomizers = new ArrayList<>();
populateMeterRegistryCustomizers(registryClasses, beanManager, classMeterRegistryCustomizers,
globalMeterRegistryCustomizers);

// Find and configure MeterRegistry beans (includes runtime config)
Set<Bean<?>> beans = new HashSet<>(beanManager.getBeans(MeterRegistry.class, Any.Literal.INSTANCE));
beans.removeIf(bean -> bean.getBeanClass().equals(CompositeRegistryCreator.class));

// Apply global filters to the global registry
applyMeterFilters(Metrics.globalRegistry, globalFilters);
applyMeterRegistryCustomizers(Metrics.globalRegistry, globalMeterRegistryCustomizers);

for (Bean<?> i : beans) {
MeterRegistry registry = (MeterRegistry) beanManager
Expand All @@ -100,8 +91,17 @@ public void configureRegistries(MicrometerConfig config,
// Add & configure non-root registries
if (registry != Metrics.globalRegistry && registry != null) {
applyMeterFilters(registry, globalFilters);
applyMeterFilters(registry, classMeterFilters.get(registry.getClass()));
log.debugf("Adding configured registry %s", registry.getClass(), registry);
Class<?> registryClass = registry.getClass();
applyMeterFilters(registry, classMeterFilters.get(registryClass));

var classSpecificCustomizers = classMeterRegistryCustomizers.get(registryClass);
var newList = new ArrayList<MeterRegistryCustomizer>(
globalMeterRegistryCustomizers.size() + classSpecificCustomizers.size());
newList.addAll(globalMeterRegistryCustomizers);
newList.addAll(classSpecificCustomizers);
applyMeterRegistryCustomizers(registry, newList);

log.debugf("Adding configured registry %s", registryClass, registry);
Metrics.globalRegistry.add(registry);
}
}
Expand Down Expand Up @@ -152,9 +152,53 @@ public void run() {
});
}

void populateListWithMeterFilters(Instance<MeterFilter> filterInstance, List<MeterFilter> meterFilters) {
private void populateMeterFilters(Set<Class<? extends MeterRegistry>> registryClasses, BeanManager beanManager,
Map<Class<? extends MeterRegistry>, List<MeterFilter>> classMeterFilters,
List<MeterFilter> globalFilters) {
// Find global/common registry configuration
Instance<MeterFilter> globalFilterInstance = beanManager.createInstance()
.select(MeterFilter.class, Default.Literal.INSTANCE);
populateList(globalFilterInstance, globalFilters);
log.debugf("Discovered global MeterFilters : %s", globalFilters);

// Find MeterFilters for specific registry classes, i.e.:
// @MeterFilterConstraint(applyTo = DatadogMeterRegistry.class) Instance<MeterFilter> filters
for (Class<? extends MeterRegistry> typeClass : registryClasses) {
Instance<MeterFilter> classFilterInstance = beanManager.createInstance()
.select(MeterFilter.class, new MeterFilterConstraint.Literal(typeClass));
List<MeterFilter> classFilters = classMeterFilters.computeIfAbsent(typeClass, k -> new ArrayList<>());

populateList(classFilterInstance, classFilters);
log.debugf("Discovered MeterFilters for %s: %s", typeClass, classFilters);
}
}

private void populateMeterRegistryCustomizers(Set<Class<? extends MeterRegistry>> registryClasses, BeanManager beanManager,
Map<Class<? extends MeterRegistry>, List<MeterRegistryCustomizer>> classMeterRegistryCustomizers,
List<MeterRegistryCustomizer> globalMeterRegistryCustomizers) {
// Find global/common registry configuration
Instance<MeterRegistryCustomizer> globalFilterInstance = beanManager.createInstance()
.select(MeterRegistryCustomizer.class, Default.Literal.INSTANCE);
populateList(globalFilterInstance, globalMeterRegistryCustomizers);
log.debugf("Discovered global MeterRegistryCustomizer : %s", globalMeterRegistryCustomizers);

// Find MeterRegistryCustomizers for specific registry classes, i.e.:
// @MeterRegistryCustomizerConstraint(applyTo = DatadogMeterRegistryCustomizer.class) Instance<MeterRegistryCustomizer> customizers
log.debugf("Configuring Micrometer registries : %s", registryClasses);
for (Class<? extends MeterRegistry> typeClass : registryClasses) {
Instance<MeterRegistryCustomizer> classFilterInstance = beanManager.createInstance()
.select(MeterRegistryCustomizer.class, new MeterRegistryCustomizerConstraint.Literal(typeClass));
List<MeterRegistryCustomizer> classFilters = classMeterRegistryCustomizers.computeIfAbsent(typeClass,
k -> new ArrayList<>());

populateList(classFilterInstance, classFilters);
log.debugf("Discovered MeterRegistryCustomizer for %s: %s", typeClass, classFilters);
}
}

private <T> void populateList(Instance<T> filterInstance, List<T> meterFilters) {
if (!filterInstance.isUnsatisfied()) {
for (MeterFilter filter : filterInstance) {
for (T filter : filterInstance) {
// @Produces methods can return null, and those will turn up here.
if (filter != null) {
meterFilters.add(filter);
Expand All @@ -163,14 +207,23 @@ void populateListWithMeterFilters(Instance<MeterFilter> filterInstance, List<Met
}
}

void applyMeterFilters(MeterRegistry registry, List<MeterFilter> filters) {
private void applyMeterFilters(MeterRegistry registry, List<MeterFilter> filters) {
if (filters != null) {
for (MeterFilter meterFilter : filters) {
registry.config().meterFilter(meterFilter);
}
}
}

private void applyMeterRegistryCustomizers(MeterRegistry registry, List<MeterRegistryCustomizer> customizers) {
if ((customizers != null) && !customizers.isEmpty()) {
Collections.sort(customizers);
for (MeterRegistryCustomizer customizer : customizers) {
customizer.customize(registry);
}
}
}

public void registerMetrics(Consumer<MetricsFactory> consumer) {
consumer.accept(factory);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@

import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.prometheus.client.CollectorRegistry;
import io.quarkus.micrometer.runtime.MeterFilterConstraint;
import io.quarkus.micrometer.runtime.MeterRegistryCustomizer;
import io.quarkus.micrometer.runtime.MeterRegistryCustomizerConstraint;

@Singleton
@Priority(Interceptor.Priority.APPLICATION - 100)
Expand All @@ -34,6 +37,14 @@ public MeterFilter configurePrometheusRegistries() {
Tag.of("registry", "prometheus")));
}

@Produces
@Singleton
@MeterFilterConstraint(applyTo = PrometheusMeterRegistry.class)
public MeterFilter configurePrometheusRegistries2() {
return MeterFilter.commonTags(Arrays.asList(
Tag.of("registry2", "prometheus")));
}

@Produces
@Singleton
@MeterFilterConstraint(applyTo = CustomConfiguration.class)
Expand All @@ -49,6 +60,42 @@ public MeterFilter configureAllRegistries() {
Tag.of("env", deploymentEnv)));
}

@Produces
@Singleton
@MeterRegistryCustomizerConstraint(applyTo = PrometheusMeterRegistry.class)
public MeterRegistryCustomizer customizePrometheusRegistries() {
return new MeterRegistryCustomizer() {
@Override
public void customize(MeterRegistry registry) {
registry.config().meterFilter(MeterFilter.ignoreTags("registry2"));
}
};
}

@Produces
@Singleton
@MeterRegistryCustomizerConstraint(applyTo = CustomConfiguration.class)
public MeterRegistryCustomizer customizeNonexistantRegistries() {
return new MeterRegistryCustomizer() {
@Override
public void customize(MeterRegistry registry) {
registry.config().meterFilter(MeterFilter.ignoreTags("env"));
}
};
}

@Produces
@Singleton
public MeterRegistryCustomizer customizeAllRegistries() {
return new MeterRegistryCustomizer() {
@Override
public void customize(MeterRegistry registry) {
registry.config().meterFilter(MeterFilter.commonTags(Arrays.asList(
Tag.of("env2", deploymentEnv))));
}
};
}

/**
* Produce a custom prometheus configuration that is isolated/separate from
* the application (and won't be connected to the Quarkus configured application
Expand Down
Loading

0 comments on commit fbd6e52

Please sign in to comment.