Skip to content

Commit

Permalink
Merge pull request #34573 from geoand/#32996
Browse files Browse the repository at this point in the history
Introduce a way to completely customize MeterRegistry
  • Loading branch information
geoand authored Jul 7, 2023
2 parents 5e867c9 + d00452f commit 34e133b
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 88 deletions.
15 changes: 14 additions & 1 deletion docs/src/main/asciidoc/telemetry-micrometer.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,12 @@ For example, setting
By default, the metrics are exported using the Prometheus format `application/openmetrics-text`,
you can revert to the former format by specifying the `Accept` request header to `text/plain` (`curl -H "Accept: text/plain" localhost:8080/q/metrics/`).

== Customizing Micrometer

Quarkus provides a variety of way to customize Micrometer.

[[meter-filter]]
== Use `MeterFilter` to customize emitted tags and metrics
=== Use `MeterFilter` to customize emitted tags and metrics

Micrometer uses `MeterFilter` instances to customize the metrics emitted by `MeterRegistry` instances.
The Micrometer extension will detect `MeterFilter` CDI beans and use them when initializing `MeterRegistry`
Expand Down Expand Up @@ -580,6 +584,15 @@ An application configuration property is also injected and used as a tag value.
Additional examples of MeterFilters can be found in the
link:https://micrometer.io/docs/concepts[official documentation].

=== Use `HttpServerMetricsTagsContributor` for server HTTP requests

By providing CDI beans that implement `io.quarkus.micrometer.runtime.HttpServerMetricsTagsContributor`, user code can contribute arbitrary tags based on the details of HTTP request

=== Use `MeterRegistryCustomizer` for arbitrary customizations to meter registries

By providing CDI beans that implement `io.quarkus.micrometer.runtime.MeterRegistryCustomizer` user code has the change to change the configuration of any `MeterRegistry` that has been activated.
Unless an implementation is annotated with `@io.quarkus.micrometer.runtime.MeterRegistryCustomizerConstraint`, the customization applies to all `MeterRegistry` instances.

[[annotations]]
== Does Micrometer support annotations?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.annotations.Consume;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
Expand All @@ -47,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 @@ -61,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 @@ -104,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 @@ -135,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 Expand Up @@ -163,12 +172,11 @@ public void transform(TransformationContext ctx) {
}

@BuildStep
@Consume(BeanContainerBuildItem.class)
@Record(ExecutionTime.STATIC_INIT)
RootMeterRegistryBuildItem createRootRegistry(MicrometerRecorder recorder,
MicrometerConfig config,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
BeanContainerBuildItem beanContainerBuildItem) {
// BeanContainerBuildItem is present to indicate we call this after Arc is initialized
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem) {

RuntimeValue<MeterRegistry> registry = recorder.createRootRegistry(config,
nonApplicationRootPathBuildItem.getNonApplicationRootPath(),
Expand All @@ -177,11 +185,10 @@ RootMeterRegistryBuildItem createRootRegistry(MicrometerRecorder recorder,
}

@BuildStep
@Consume(RootMeterRegistryBuildItem.class)
@Record(ExecutionTime.STATIC_INIT)
void registerExtensionMetrics(MicrometerRecorder recorder,
RootMeterRegistryBuildItem rootMeterRegistryBuildItem,
List<MetricsFactoryConsumerBuildItem> metricsFactoryConsumerBuildItems) {
// RootMeterRegistryBuildItem is present to indicate we call this after the root registry has been initialized

for (MetricsFactoryConsumerBuildItem item : metricsFactoryConsumerBuildItems) {
if (item != null && item.executionTime() == ExecutionTime.STATIC_INIT) {
Expand All @@ -191,15 +198,14 @@ void registerExtensionMetrics(MicrometerRecorder recorder,
}

@BuildStep
@Consume(RootMeterRegistryBuildItem.class)
@Record(ExecutionTime.RUNTIME_INIT)
void configureRegistry(MicrometerRecorder recorder,
MicrometerConfig config,
RootMeterRegistryBuildItem rootMeterRegistryBuildItem,
List<MicrometerRegistryProviderBuildItem> providerClassItems,
List<MetricsFactoryConsumerBuildItem> metricsFactoryConsumerBuildItems,
List<MicrometerRegistryProviderBuildItem> providerClasses,
ShutdownContextBuildItem shutdownContextBuildItem) {
// RootMeterRegistryBuildItem is present to indicate we call this after the root registry has been initialized

Set<Class<? extends MeterRegistry>> typeClasses = new HashSet<>();
for (MicrometerRegistryProviderBuildItem item : providerClassItems) {
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
Loading

0 comments on commit 34e133b

Please sign in to comment.