Skip to content

Commit 67efe81

Browse files
committed
Add auto-configuration for Micrometer 2.0.0 Observation API
- Enables timer creation for observations by calling withTimerObservationHandler on the MeterRegistry - Registers ObservationPredicate, GlobalTagsProvider and ObservationHandler on the MeterRegistry - Applies grouping to the ObservationHandler: MeterObservationHandler are added to a FirstMatchingCompositeObservationHandler See spring-projectsgh-29666
1 parent 8f898ba commit 67efe81

File tree

3 files changed

+319
-1
lines changed

3 files changed

+319
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.metrics;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
22+
import io.micrometer.core.instrument.MeterRegistry;
23+
import io.micrometer.core.instrument.observation.MeterObservationHandler;
24+
import io.micrometer.core.instrument.observation.Observation.GlobalTagsProvider;
25+
import io.micrometer.core.instrument.observation.ObservationHandler;
26+
import io.micrometer.core.instrument.observation.ObservationHandler.FirstMatchingCompositeObservationHandler;
27+
import io.micrometer.core.instrument.observation.ObservationPredicate;
28+
import io.micrometer.core.instrument.observation.TimerObservationHandler;
29+
30+
import org.springframework.boot.autoconfigure.AutoConfiguration;
31+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
32+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
33+
import org.springframework.context.annotation.Bean;
34+
35+
/**
36+
* {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Observation API.
37+
*
38+
* @author Moritz Halbritter
39+
* @since 3.0.0
40+
*/
41+
@AutoConfiguration(after = CompositeMeterRegistryAutoConfiguration.class)
42+
@ConditionalOnBean(MeterRegistry.class)
43+
public class ObservationAutoConfiguration {
44+
45+
/**
46+
* Enables {@link TimerObservationHandler} on the {@link MeterRegistry}.
47+
* @return meter registry customizer
48+
*/
49+
@Bean
50+
public MeterRegistryCustomizer<MeterRegistry> enableTimerObservationHandler() {
51+
return MeterRegistry::withTimerObservationHandler;
52+
}
53+
54+
/**
55+
* Registers {@link ObservationPredicate ObservationPredicates} into the
56+
* {@link MeterRegistry}.
57+
* @param predicates list of predicates
58+
* @return meter registry customizer
59+
*/
60+
@Bean
61+
public MeterRegistryCustomizer<MeterRegistry> registerObservationPredicates(List<ObservationPredicate> predicates) {
62+
return (registry) -> predicates
63+
.forEach((predicate) -> registry.observationConfig().observationPredicate(predicate));
64+
}
65+
66+
/**
67+
* Registers {@link GlobalTagsProvider GlobalTagsProviders} into the
68+
* {@link MeterRegistry}.
69+
* @param tagProviders list of tags providers
70+
* @return meter registry customizer
71+
*/
72+
@Bean
73+
public MeterRegistryCustomizer<MeterRegistry> registerGlobalTagsProvider(List<GlobalTagsProvider<?>> tagProviders) {
74+
return (registry) -> tagProviders
75+
.forEach((tagProvider) -> registry.observationConfig().tagsProvider(tagProvider));
76+
}
77+
78+
/**
79+
* Registers {@link ObservationHandler ObservationHandlers} into the
80+
* {@link MeterRegistry}.
81+
* @param handlers list of handlers
82+
* @return meter registry customizer
83+
*/
84+
@Bean
85+
public MeterRegistryCustomizer<MeterRegistry> registerObservationHandler(List<ObservationHandler<?>> handlers) {
86+
return (registry) -> registerObservationHandler(registry, handlers);
87+
}
88+
89+
@SuppressWarnings("rawtypes")
90+
private void registerObservationHandler(MeterRegistry registry, List<ObservationHandler<?>> handlers) {
91+
List<ObservationHandler> meterHandlers = new ArrayList<>();
92+
for (ObservationHandler<?> observationHandler : handlers) {
93+
if (observationHandler instanceof MeterObservationHandler) {
94+
meterHandlers.add(observationHandler);
95+
}
96+
else {
97+
registry.observationConfig().observationHandler(observationHandler);
98+
}
99+
}
100+
if (!meterHandlers.isEmpty()) {
101+
registry.observationConfig()
102+
.observationHandler(new FirstMatchingCompositeObservationHandler(meterHandlers));
103+
}
104+
}
105+
106+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.Log4J2MetricsAutoConfigur
4444
org.springframework.boot.actuate.autoconfigure.metrics.LogbackMetricsAutoConfiguration
4545
org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration
4646
org.springframework.boot.actuate.autoconfigure.metrics.MetricsEndpointAutoConfiguration
47+
org.springframework.boot.actuate.autoconfigure.metrics.ObservationAutoConfiguration
4748
org.springframework.boot.actuate.autoconfigure.metrics.SystemMetricsAutoConfiguration
4849
org.springframework.boot.actuate.autoconfigure.metrics.amqp.RabbitMetricsAutoConfiguration
4950
org.springframework.boot.actuate.autoconfigure.metrics.cache.CacheMetricsAutoConfiguration
@@ -97,4 +98,4 @@ org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceEndpointAutoC
9798
org.springframework.boot.actuate.autoconfigure.web.mappings.MappingsEndpointAutoConfiguration
9899
org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementContextAutoConfiguration
99100
org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration
100-
org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration
101+
org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.metrics;
18+
19+
import java.lang.reflect.Field;
20+
import java.lang.reflect.Method;
21+
import java.util.Collection;
22+
import java.util.List;
23+
24+
import io.micrometer.core.instrument.Tags;
25+
import io.micrometer.core.instrument.observation.MeterObservationHandler;
26+
import io.micrometer.core.instrument.observation.Observation.Context;
27+
import io.micrometer.core.instrument.observation.Observation.GlobalTagsProvider;
28+
import io.micrometer.core.instrument.observation.ObservationHandler;
29+
import io.micrometer.core.instrument.observation.ObservationHandler.AllMatchingCompositeObservationHandler;
30+
import io.micrometer.core.instrument.observation.ObservationHandler.CompositeObservationHandler;
31+
import io.micrometer.core.instrument.observation.ObservationHandler.FirstMatchingCompositeObservationHandler;
32+
import io.micrometer.core.instrument.observation.ObservationPredicate;
33+
import io.micrometer.core.instrument.observation.ObservationRegistry;
34+
import io.micrometer.core.instrument.observation.ObservationRegistry.ObservationConfig;
35+
import io.micrometer.core.instrument.observation.TimerObservationHandler;
36+
import org.junit.jupiter.api.Test;
37+
38+
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
39+
import org.springframework.boot.autoconfigure.AutoConfigurations;
40+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
41+
import org.springframework.context.annotation.Bean;
42+
import org.springframework.context.annotation.Configuration;
43+
import org.springframework.core.annotation.Order;
44+
import org.springframework.util.ReflectionUtils;
45+
46+
import static org.assertj.core.api.Assertions.assertThat;
47+
48+
/**
49+
* Tests for {@link ObservationAutoConfiguration}.
50+
*
51+
* @author Moritz Halbritter
52+
*/
53+
class ObservationAutoConfigurationTests {
54+
55+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().with(MetricsRun.simple())
56+
.withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class));
57+
58+
@Test
59+
void autoConfiguresTimerTimerObservationHandler() {
60+
this.contextRunner.run((context) -> {
61+
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
62+
List<ObservationHandler<?>> handlers = getObservationHandlers(observationRegistry);
63+
assertThat(handlers).hasSize(1);
64+
assertThat(handlers.get(0)).isInstanceOf(TimerObservationHandler.class);
65+
66+
});
67+
}
68+
69+
@Test
70+
void autoConfiguresObservationPredicates() {
71+
this.contextRunner.withUserConfiguration(ObservationPredicates.class).run((context) -> {
72+
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
73+
assertThat(getObservationPredicates(observationRegistry)).hasSize(1);
74+
});
75+
}
76+
77+
@Test
78+
void autoConfiguresGlobalTagsProvider() {
79+
this.contextRunner.withUserConfiguration(GlobalTagsProviders.class).run((context) -> {
80+
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
81+
assertThat(getTagsProviders(observationRegistry)).hasSize(1);
82+
});
83+
}
84+
85+
@Test
86+
@SuppressWarnings("rawtypes")
87+
void autoConfiguresObservationHandler() {
88+
this.contextRunner.withUserConfiguration(ObservationHandlers.class).run((context) -> {
89+
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
90+
List<ObservationHandler<?>> handlers = getObservationHandlers(observationRegistry);
91+
assertThat(handlers).hasSize(5);
92+
assertThat(handlers.get(0)).isInstanceOf(TimerObservationHandler.class);
93+
assertThat(handlers.get(1)).isInstanceOf(CustomObservationHandler.class);
94+
assertThat(handlers.get(2)).isInstanceOf(FirstMatchingCompositeObservationHandler.class);
95+
assertThat(handlers.get(3)).isInstanceOf(AllMatchingCompositeObservationHandler.class);
96+
// CustomMeterObservationHandlers gets wrapped into a
97+
// FirstMatchingCompositeObservationHandler
98+
assertThat(handlers.get(4)).isInstanceOf(FirstMatchingCompositeObservationHandler.class);
99+
List<ObservationHandler> containedHandlers = ((CompositeObservationHandler) handlers.get(4)).getHandlers();
100+
assertThat(containedHandlers).hasSize(2);
101+
assertThat(containedHandlers.get(0)).isInstanceOf(CustomMeterObservationHandler.class);
102+
assertThat(containedHandlers.get(1)).isInstanceOf(CustomMeterObservationHandler.class);
103+
});
104+
}
105+
106+
@SuppressWarnings("unchecked")
107+
private List<ObservationPredicate> getObservationPredicates(ObservationRegistry observationRegistry) {
108+
Field field = ReflectionUtils.findField(ObservationConfig.class, "observationPredicates");
109+
ReflectionUtils.makeAccessible(field);
110+
return (List<ObservationPredicate>) ReflectionUtils.getField(field, observationRegistry.observationConfig());
111+
}
112+
113+
@SuppressWarnings("unchecked")
114+
private Collection<GlobalTagsProvider<?>> getTagsProviders(ObservationRegistry registry) {
115+
Method method = ReflectionUtils.findMethod(ObservationConfig.class, "getTagsProviders");
116+
ReflectionUtils.makeAccessible(method);
117+
return (Collection<GlobalTagsProvider<?>>) ReflectionUtils.invokeMethod(method, registry.observationConfig());
118+
}
119+
120+
@SuppressWarnings("unchecked")
121+
private List<ObservationHandler<?>> getObservationHandlers(ObservationRegistry registry) {
122+
Method method = ReflectionUtils.findMethod(ObservationConfig.class, "getObservationHandlers");
123+
ReflectionUtils.makeAccessible(method);
124+
return List.copyOf(
125+
(Collection<ObservationHandler<?>>) ReflectionUtils.invokeMethod(method, registry.observationConfig()));
126+
}
127+
128+
@Configuration(proxyBeanMethods = false)
129+
static class ObservationPredicates {
130+
131+
@Bean
132+
ObservationPredicate customPredicate() {
133+
return (s, context) -> s.equals("observation1");
134+
}
135+
136+
}
137+
138+
@Configuration(proxyBeanMethods = false)
139+
static class GlobalTagsProviders {
140+
141+
@Bean
142+
GlobalTagsProvider<?> customTagsProvider() {
143+
return new GlobalTagsProvider<>() {
144+
@Override
145+
public boolean supportsContext(Context context) {
146+
return true;
147+
}
148+
149+
@Override
150+
public Tags getLowCardinalityTags(Context context) {
151+
return Tags.of("tag1", "value1");
152+
}
153+
};
154+
}
155+
156+
}
157+
158+
@Configuration(proxyBeanMethods = false)
159+
static class ObservationHandlers {
160+
161+
@Bean
162+
@Order(4)
163+
AllMatchingCompositeObservationHandler customAllMatchingCompositeObservationHandler() {
164+
return new AllMatchingCompositeObservationHandler();
165+
}
166+
167+
@Bean
168+
@Order(3)
169+
FirstMatchingCompositeObservationHandler customFirstMatchingCompositeObservationHandler() {
170+
return new FirstMatchingCompositeObservationHandler();
171+
}
172+
173+
@Bean
174+
@Order(2)
175+
ObservationHandler<Context> customObservationHandler() {
176+
return new CustomObservationHandler();
177+
}
178+
179+
@Bean
180+
@Order(1)
181+
MeterObservationHandler<Context> customMeterObservationHandler2() {
182+
return new CustomMeterObservationHandler();
183+
}
184+
185+
@Bean
186+
@Order(0)
187+
MeterObservationHandler<Context> customMeterObservationHandler1() {
188+
return new CustomMeterObservationHandler();
189+
}
190+
191+
}
192+
193+
static class CustomObservationHandler implements ObservationHandler<Context> {
194+
195+
@Override
196+
public boolean supportsContext(Context context) {
197+
return true;
198+
}
199+
200+
}
201+
202+
static class CustomMeterObservationHandler implements MeterObservationHandler<Context> {
203+
204+
@Override
205+
public boolean supportsContext(Context context) {
206+
return true;
207+
}
208+
209+
}
210+
211+
}

0 commit comments

Comments
 (0)