Skip to content

Commit

Permalink
Add auto-configuration for Micrometer 2.0.0 Observation API
Browse files Browse the repository at this point in the history
- Adds a ObservationRegistry bean
- Add support for ObservationRegistryCustomizers
- Enables timer creation for observations if micrometer-core is on
  the classpath
- Registers ObservationPredicate, GlobalTagsProvider and
  ObservationHandler on the MeterRegistry
- Applies grouping to the ObservationHandlers: MeterObservationHandler
  are added to a FirstMatchingCompositeObservationHandler
- If micrometer-tracing is on the classpath, the
  TracingObservationHandler are added to a
  FirstMatchingCompositeObservationHandler

See spring-projectsgh-29666
  • Loading branch information
mhalbritter committed Mar 31, 2022
1 parent 47eb630 commit aeab402
Show file tree
Hide file tree
Showing 17 changed files with 1,170 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ dependencies {
optional("com.zaxxer:HikariCP")
optional("io.dropwizard.metrics:metrics-jmx")
optional("io.lettuce:lettuce-core")
optional("io.micrometer:micrometer-observation")
optional("io.micrometer:micrometer-core")
optional("io.micrometer:micrometer-tracing-api")
optional("io.micrometer:micrometer-binders")
optional("io.micrometer:micrometer-registry-appoptics")
optional("io.micrometer:micrometer-registry-atlas") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.observation;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler;
import io.micrometer.observation.ObservationRegistry;

/**
* Installs {@link ObservationHandler} into an {@link ObservationRegistry}.
*
* Uses an {@link ObservationHandlerGrouping} strategy to group the handlers. All handlers
* which belong to a group are added with their group members into a
* {@link FirstMatchingCompositeObservationHandler}. All handlers without a group are
* added directly to the {@link ObservationRegistry}. The group numbers are ordered
* ascending, meaning lower numbers are added first to the {@link ObservationRegistry}.
*
* @author Moritz Halbritter
*/
class HandlersObservationRegistryCustomizer implements ObservationRegistryCustomizer<ObservationRegistry> {

private final List<ObservationHandler<?>> observationHandlers;

private final ObservationHandlerGrouping observationHandlerGrouping;

HandlersObservationRegistryCustomizer(List<ObservationHandler<?>> observationHandlers,
ObservationHandlerGrouping observationHandlerGrouping) {
this.observationHandlers = observationHandlers;
this.observationHandlerGrouping = observationHandlerGrouping;
}

@Override
public void customize(ObservationRegistry registry) {
Map<Integer, List<ObservationHandler<?>>> groupedHandlers = new TreeMap<>();
for (ObservationHandler<?> observationHandler : this.observationHandlers) {
Integer group = this.observationHandlerGrouping.getGroup(observationHandler);
if (group == null) {
registry.observationConfig().observationHandler(observationHandler);
}
else {
groupedHandlers.computeIfAbsent(group, (ignored) -> new ArrayList<>()).add(observationHandler);
}
}
for (List<ObservationHandler<?>> handlers : groupedHandlers.values()) {
registry.observationConfig()
.observationHandler(new FirstMatchingCompositeObservationHandler(castToRawType(handlers)));
}
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private List<ObservationHandler> castToRawType(List<ObservationHandler<?>> handlers) {
// See https://github.com/micrometer-metrics/micrometer/issues/3064
return (List) handlers;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.observation;

import io.micrometer.core.instrument.observation.MeterObservationHandler;
import io.micrometer.observation.ObservationHandler;

/**
* {@link ObservationHandler} grouping used by {@link ObservationAutoConfiguration} if
* micrometer-tracing is not on the classpath.
*
* @author Moritz Halbritter
*/
class NoTracingObservationHandlerGrouping implements ObservationHandlerGrouping {

private static final int METER_OBSERVATION_HANDLER_GROUP = 1;

@Override
public Integer getGroup(ObservationHandler<?> observationHandler) {
if (observationHandler instanceof MeterObservationHandler) {
return METER_OBSERVATION_HANDLER_GROUP;
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.observation;

import java.util.List;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.observation.Observation.GlobalTagsProvider;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationPredicate;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.handler.TracingObservationHandler;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Observation API.
*
* @author Moritz Halbritter
* @since 3.0.0
*/
@AutoConfiguration(after = CompositeMeterRegistryAutoConfiguration.class)
@ConditionalOnClass(ObservationRegistry.class)
public class ObservationAutoConfiguration {

@Bean
static ObservationRegistryPostProcessor observationRegistryPostProcessor(
ObjectProvider<ObservationRegistryCustomizer<?>> observationRegistryCustomizers,
ObjectProvider<ObservationPredicate> observationPredicates,
ObjectProvider<GlobalTagsProvider<?>> tagProviders) {
return new ObservationRegistryPostProcessor(observationRegistryCustomizers, observationPredicates,
tagProviders);
}

@Bean
@ConditionalOnMissingBean
ObservationRegistry observationRegistry() {
return ObservationRegistry.create();
}

@Bean
@ConditionalOnMissingBean
HandlersObservationRegistryCustomizer handlersObservationRegistryCustomizer(
List<ObservationHandler<?>> observationHandlers, ObservationHandlerGrouping observationHandlerGrouping) {
return new HandlersObservationRegistryCustomizer(observationHandlers, observationHandlerGrouping);
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(MeterRegistry.class)
static class MetricsConfiguration {

@Bean
TimerObservationHandlerObservationRegistryCustomizer enableTimerObservationHandler(
MeterRegistry meterRegistry) {
return new TimerObservationHandlerObservationRegistryCustomizer(meterRegistry);
}

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("io.micrometer.tracing.handler.TracingObservationHandler")
static class NoTracingConfiguration {

@Bean
ObservationHandlerGrouping noTracingObservationHandlerGrouping() {
return new NoTracingObservationHandlerGrouping();
}

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(TracingObservationHandler.class)
static class TracingConfiguration {

@Bean
ObservationHandlerGrouping tracingObservationHandlerGrouping() {
return new TracingObservationHandlerGrouping();
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.observation;

import io.micrometer.observation.ObservationHandler;

/**
* Strategy to get the group for an {@link ObservationHandler}.
*
* @author Moritz Halbritter
* @since 3.0.0
*/
public interface ObservationHandlerGrouping {

/**
* Returns the group number of the given {@code observationHandler}. If {@code null}
* is returned, the {@link ObservationHandler} doesn't belong to any group.
* @param observationHandler the observation handler to get the group for
* @return group number or {@code null} if the handler doesn't belong to any group
*/
Integer getGroup(ObservationHandler<?> observationHandler);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.observation;

import java.util.List;

import io.micrometer.observation.Observation.GlobalTagsProvider;
import io.micrometer.observation.ObservationPredicate;
import io.micrometer.observation.ObservationRegistry;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.util.LambdaSafe;

/**
* Configurer to apply {@link ObservationRegistryCustomizer customizers} to
* {@link ObservationRegistry observation registries}. Installs
* {@link ObservationPredicate observation predicates} and {@link GlobalTagsProvider
* global tag providers} into the {@link ObservationRegistry}.
*
* @author Moritz Halbritter
*/
class ObservationRegistryConfigurer {

private final ObjectProvider<ObservationRegistryCustomizer<?>> customizers;

private final ObjectProvider<ObservationPredicate> observationPredicates;

private final ObjectProvider<GlobalTagsProvider<?>> tagProviders;

ObservationRegistryConfigurer(ObjectProvider<ObservationRegistryCustomizer<?>> customizers,
ObjectProvider<ObservationPredicate> observationPredicates,
ObjectProvider<GlobalTagsProvider<?>> tagProviders) {
this.customizers = customizers;
this.observationPredicates = observationPredicates;
this.tagProviders = tagProviders;
}

void configure(ObservationRegistry registry) {
registerObservationPredicates(registry);
registerGlobalTagsProvider(registry);
customize(registry);
}

private void registerObservationPredicates(ObservationRegistry registry) {
this.observationPredicates.orderedStream().forEach(
(observationPredicate) -> registry.observationConfig().observationPredicate(observationPredicate));
}

private void registerGlobalTagsProvider(ObservationRegistry registry) {
this.tagProviders.orderedStream()
.forEach((tagProvider) -> registry.observationConfig().tagsProvider(tagProvider));
}

@SuppressWarnings("unchecked")
private void customize(ObservationRegistry registry) {
LambdaSafe.callbacks(ObservationRegistryCustomizer.class, asOrderedList(this.customizers), registry)
.withLogger(ObservationRegistryConfigurer.class).invoke((customizer) -> customizer.customize(registry));
}

private <T> List<T> asOrderedList(ObjectProvider<T> provider) {
return provider.orderedStream().toList();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.observation;

import io.micrometer.observation.ObservationRegistry;

/**
* Callback interface that can be used to customize auto-configured
* {@link ObservationRegistry ObservationRegistries}.
*
* @param <T> the registry type to customize
* @author Moritz Halbritter
* @since 3.0.0
*/
@FunctionalInterface
public interface ObservationRegistryCustomizer<T extends ObservationRegistry> {

/**
* Customize the given {@code registry}.
* @param registry the registry to customize
*/
void customize(T registry);

}
Loading

0 comments on commit aeab402

Please sign in to comment.