Merge pull request #32399 from jonatan-ivanov

* gh-32399:
  Polish "Use TracingAwareMeterObservationHandler if tracing is configured"
  Use TracingAwareMeterObservationHandler if tracing is configured

Closes gh-32399
pull/32740/head
Andy Wilkinson 2 years ago
commit 58b5daec55

@ -16,17 +16,23 @@
package org.springframework.boot.actuate.autoconfigure.observation;
import java.util.List;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
import io.micrometer.core.instrument.observation.MeterObservationHandler;
import io.micrometer.observation.GlobalObservationConvention;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationPredicate;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.handler.TracingAwareMeterObservationHandler;
import io.micrometer.tracing.handler.TracingObservationHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -42,9 +48,10 @@ import org.springframework.context.annotation.Configuration;
*
* @author Moritz Halbritter
* @author Brian Clozel
* @author Jonatan Ivanov
* @since 3.0.0
*/
@AutoConfiguration(after = CompositeMeterRegistryAutoConfiguration.class)
@AutoConfiguration(after = { CompositeMeterRegistryAutoConfiguration.class, MicrometerTracingAutoConfiguration.class })
@ConditionalOnClass(ObservationRegistry.class)
@EnableConfigurationProperties(ObservationProperties.class)
public class ObservationAutoConfiguration {
@ -67,35 +74,68 @@ public class ObservationAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(MeterRegistry.class)
static class MetricsConfiguration {
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnMissingClass("io.micrometer.tracing.Tracer")
static class OnlyMetricsConfiguration {
@Bean
@ConditionalOnMissingBean(MeterObservationHandler.class)
DefaultMeterObservationHandler defaultMeterObservationHandler(MeterRegistry meterRegistry) {
return new DefaultMeterObservationHandler(meterRegistry);
ObservationHandlerGrouping metricsObservationHandlerGrouping() {
return new ObservationHandlerGrouping(MeterObservationHandler.class);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("io.micrometer.tracing.handler.TracingObservationHandler")
static class OnlyMetricsConfiguration {
@ConditionalOnClass(Tracer.class)
@ConditionalOnMissingClass("io.micrometer.core.instrument.MeterRegistry")
static class OnlyTracingConfiguration {
@Bean
OnlyMetricsObservationHandlerGrouping onlyMetricsObservationHandlerGrouping() {
return new OnlyMetricsObservationHandlerGrouping();
ObservationHandlerGrouping tracingObservationHandlerGrouping() {
return new ObservationHandlerGrouping(TracingObservationHandler.class);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(TracingObservationHandler.class)
static class TracingConfiguration {
@ConditionalOnClass({ MeterRegistry.class, Tracer.class })
static class MetricsWithTracingConfiguration {
@Bean
TracingObservationHandlerGrouping tracingObservationHandlerGrouping() {
return new TracingObservationHandlerGrouping();
ObservationHandlerGrouping metricsAndTracingObservationHandlerGrouping() {
return new ObservationHandlerGrouping(
List.of(TracingObservationHandler.class, MeterObservationHandler.class));
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(MeterRegistry.class)
@ConditionalOnMissingBean(MeterObservationHandler.class)
static class MeterObservationHandlerConfiguration {
@ConditionalOnMissingBean(Tracer.class)
@Configuration(proxyBeanMethods = false)
static class OnlyMetricsMeterObservationHandlerConfiguration {
@Bean
DefaultMeterObservationHandler defaultMeterObservationHandler(MeterRegistry meterRegistry) {
return new DefaultMeterObservationHandler(meterRegistry);
}
}
@ConditionalOnBean(Tracer.class)
@Configuration(proxyBeanMethods = false)
static class TracingAndMetricsObservationHandlerConfiguration {
@Bean
TracingAwareMeterObservationHandler<Observation.Context> tracingAwareMeterObservationHandler(
MeterRegistry meterRegistry, Tracer tracer) {
return new TracingAwareMeterObservationHandler<>(new DefaultMeterObservationHandler(meterRegistry),
tracer);
}
}
}

@ -16,24 +16,61 @@
package org.springframework.boot.actuate.autoconfigure.observation;
import java.util.Collection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler;
import io.micrometer.observation.ObservationRegistry.ObservationConfig;
import org.springframework.util.CollectionUtils;
/**
* Strategy to apply {@link ObservationHandler ObservationHandlers} to an
* {@link ObservationConfig}.
* Groups {@link ObservationHandler ObservationHandlers} by type.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
*/
interface ObservationHandlerGrouping {
/**
* Applies the given list of {@code handlers} to the given {@code config}.
* @param handlers the list of observation handlers
* @param config the config to apply the handlers to
*/
void apply(Collection<ObservationHandler<?>> handlers, ObservationConfig config);
@SuppressWarnings("rawtypes")
class ObservationHandlerGrouping {
private final List<Class<? extends ObservationHandler>> categories;
ObservationHandlerGrouping(Class<? extends ObservationHandler> category) {
this(List.of(category));
}
ObservationHandlerGrouping(List<Class<? extends ObservationHandler>> categories) {
this.categories = categories;
}
void apply(List<ObservationHandler<?>> handlers, ObservationConfig config) {
Map<Class<? extends ObservationHandler>, List<ObservationHandler<?>>> groupings = new HashMap<>();
for (ObservationHandler<?> handler : handlers) {
Class<? extends ObservationHandler> category = findCategory(handler);
if (category != null) {
groupings.computeIfAbsent(category, (c) -> new ArrayList<>()).add(handler);
}
else {
config.observationHandler(handler);
}
}
for (Class<? extends ObservationHandler> category : this.categories) {
List<ObservationHandler<?>> handlerGroup = groupings.get(category);
if (!CollectionUtils.isEmpty(handlerGroup)) {
config.observationHandler(new FirstMatchingCompositeObservationHandler(handlerGroup));
}
}
}
private Class<? extends ObservationHandler> findCategory(ObservationHandler<?> handler) {
for (Class<? extends ObservationHandler> category : this.categories) {
if (category.isInstance(handler)) {
return category;
}
}
return null;
}
}

@ -68,8 +68,8 @@ class ObservationRegistryConfigurer {
}
private void registerHandlers(ObservationRegistry registry) {
this.observationHandlerGrouping.getObject().apply(asOrderedList(this.observationHandlers),
registry.observationConfig());
this.observationHandlerGrouping.ifAvailable(
(grouping) -> grouping.apply(asOrderedList(this.observationHandlers), registry.observationConfig()));
}
private void registerObservationPredicates(ObservationRegistry registry) {

@ -1,56 +0,0 @@
/*
* 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.Collection;
import java.util.List;
import io.micrometer.core.instrument.observation.MeterObservationHandler;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler;
import io.micrometer.observation.ObservationRegistry.ObservationConfig;
/**
* {@link ObservationHandlerGrouping} used by {@link ObservationAutoConfiguration} if
* micrometer-tracing is not on the classpath.
*
* Groups all {@link MeterObservationHandler} into a
* {@link FirstMatchingCompositeObservationHandler}. All other handlers are added to the
* {@link ObservationConfig} directly.
*
* @author Moritz Halbritter
*/
class OnlyMetricsObservationHandlerGrouping implements ObservationHandlerGrouping {
@Override
public void apply(Collection<ObservationHandler<?>> handlers, ObservationConfig config) {
List<ObservationHandler<?>> meterObservationHandlers = new ArrayList<>();
for (ObservationHandler<?> handler : handlers) {
if (handler instanceof MeterObservationHandler<?>) {
meterObservationHandlers.add(handler);
}
else {
config.observationHandler(handler);
}
}
if (!meterObservationHandlers.isEmpty()) {
config.observationHandler(new FirstMatchingCompositeObservationHandler(meterObservationHandlers));
}
}
}

@ -1,65 +0,0 @@
/*
* 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.Collection;
import java.util.List;
import io.micrometer.core.instrument.observation.MeterObservationHandler;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler;
import io.micrometer.observation.ObservationRegistry.ObservationConfig;
import io.micrometer.tracing.handler.TracingObservationHandler;
/**
* {@link ObservationHandlerGrouping} used by {@link ObservationAutoConfiguration} if
* micrometer-tracing is on the classpath.
*
* Groups all {@link TracingObservationHandler} into a
* {@link FirstMatchingCompositeObservationHandler}, and {@link MeterObservationHandler}
* into a {@link FirstMatchingCompositeObservationHandler}. All other handlers are added
* to the {@link ObservationConfig} directly.
*
* @author Moritz Halbritter
*/
class TracingObservationHandlerGrouping implements ObservationHandlerGrouping {
@Override
public void apply(Collection<ObservationHandler<?>> handlers, ObservationConfig config) {
List<ObservationHandler<?>> meterObservationHandlers = new ArrayList<>();
List<ObservationHandler<?>> tracingObservationHandlers = new ArrayList<>();
for (ObservationHandler<?> handler : handlers) {
if (handler instanceof TracingObservationHandler<?>) {
tracingObservationHandlers.add(handler);
}
else if (handler instanceof MeterObservationHandler<?>) {
meterObservationHandlers.add(handler);
}
else {
config.observationHandler(handler);
}
}
if (!tracingObservationHandlers.isEmpty()) {
config.observationHandler(new FirstMatchingCompositeObservationHandler(tracingObservationHandlers));
}
if (!meterObservationHandlers.isEmpty()) {
config.observationHandler(new FirstMatchingCompositeObservationHandler(meterObservationHandlers));
}
}
}

@ -34,6 +34,7 @@ import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObserv
import io.micrometer.observation.ObservationPredicate;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.handler.TracingAwareMeterObservationHandler;
import io.micrometer.tracing.handler.TracingObservationHandler;
import org.junit.jupiter.api.Test;
import org.mockito.Answers;
@ -55,6 +56,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link ObservationAutoConfiguration}.
*
* @author Moritz Halbritter
* @author Jonatan Ivanov
*/
class ObservationAutoConfigurationTests {
@ -63,7 +65,80 @@ class ObservationAutoConfigurationTests {
.withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class));
private final ApplicationContextRunner tracingContextRunner = new ApplicationContextRunner()
.with(MetricsRun.simple()).withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class));
.with(MetricsRun.simple()).withUserConfiguration(TracerConfiguration.class)
.withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class));
@Test
void beansShouldNotBeSuppliedWhenMicrometerObservationIsNotOnClassPath() {
this.tracingContextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.observation"))
.run((context) -> {
assertThat(context).hasSingleBean(MeterRegistry.class);
assertThat(context).doesNotHaveBean(ObservationRegistry.class);
assertThat(context).doesNotHaveBean(ObservationHandler.class);
assertThat(context).doesNotHaveBean(ObservationHandlerGrouping.class);
});
}
@Test
void supplyObservationRegistryWhenMicrometerCoreAndTracingAreNotOnClassPath() {
this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.core", "io.micrometer.tracing"))
.run((context) -> {
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
Observation.start("test-observation", observationRegistry).stop();
assertThat(context).doesNotHaveBean(ObservationHandler.class);
assertThat(context).doesNotHaveBean(ObservationHandlerGrouping.class);
});
}
@Test
void supplyMeterHandlerAndGroupingWhenMicrometerCoreIsOnClassPathButTracingIsNot() {
this.contextRunner.run((context) -> {
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
Observation.start("test-observation", observationRegistry).stop();
assertThat(context).hasSingleBean(ObservationHandler.class);
assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class);
assertThat(context).hasSingleBean(ObservationHandlerGrouping.class);
assertThat(context).hasBean("metricsObservationHandlerGrouping");
});
}
@Test
void supplyOnlyTracingObservationHandlerGroupingWhenMicrometerCoreIsNotOnClassPathButTracingIs() {
this.tracingContextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.core")).run((context) -> {
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
Observation.start("test-observation", observationRegistry).stop();
assertThat(context).doesNotHaveBean(ObservationHandler.class);
assertThat(context).hasSingleBean(ObservationHandlerGrouping.class);
assertThat(context).hasBean("tracingObservationHandlerGrouping");
});
}
@Test
void supplyMeterHandlerAndGroupingWhenMicrometerCoreAndTracingAreOnClassPath() {
this.tracingContextRunner.run((context) -> {
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
// Intentionally not stopped since that will trigger additional logic in
// TracingAwareMeterObservationHandler that we don't test here
Observation.start("test-observation", observationRegistry);
assertThat(context).hasSingleBean(ObservationHandler.class);
assertThat(context).hasSingleBean(TracingAwareMeterObservationHandler.class);
assertThat(context).hasSingleBean(ObservationHandlerGrouping.class);
assertThat(context).hasBean("metricsAndTracingObservationHandlerGrouping");
});
}
@Test
void supplyMeterHandlerAndGroupingWhenMicrometerCoreAndTracingAreOnClassPathButThereIsNoTracer() {
new ApplicationContextRunner().with(MetricsRun.simple())
.withConfiguration(AutoConfigurations.of(ObservationAutoConfiguration.class)).run((context) -> {
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
Observation.start("test-observation", observationRegistry).stop();
assertThat(context).hasSingleBean(ObservationHandler.class);
assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class);
assertThat(context).hasSingleBean(ObservationHandlerGrouping.class);
assertThat(context).hasBean("metricsAndTracingObservationHandlerGrouping");
});
}
@Test
void autoConfiguresDefaultMeterObservationHandler() {
@ -75,9 +150,17 @@ class ObservationAutoConfigurationTests {
// Observation leads to a timer
MeterRegistry meterRegistry = context.getBean(MeterRegistry.class);
assertThat(meterRegistry.get("test-observation").timer().count()).isEqualTo(1);
assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class);
assertThat(context).hasSingleBean(ObservationHandler.class);
});
}
@Test
void allowsDefaultMeterObservationHandlerToBeDisabled() {
this.contextRunner.withClassLoader(new FilteredClassLoader(MeterRegistry.class))
.run((context) -> assertThat(context).doesNotHaveBean(ObservationHandler.class));
}
@Test
void autoConfiguresObservationPredicates() {
this.contextRunner.withUserConfiguration(ObservationPredicates.class).run((context) -> {
@ -119,6 +202,8 @@ class ObservationAutoConfigurationTests {
assertThat(handlers.get(1)).isInstanceOf(CustomMeterObservationHandler.class);
assertThat(((CustomMeterObservationHandler) handlers.get(1)).getName())
.isEqualTo("customMeterObservationHandler1");
assertThat(context).doesNotHaveBean(DefaultMeterObservationHandler.class);
assertThat(context).doesNotHaveBean(TracingAwareMeterObservationHandler.class);
});
}
@ -132,9 +217,26 @@ class ObservationAutoConfigurationTests {
Observation.start("test-observation", () -> customContext, observationRegistry).stop();
assertThat(handlers).hasSize(1);
assertThat(handlers.get(0)).isInstanceOf(ObservationHandlerWithCustomContext.class);
assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class);
assertThat(context).doesNotHaveBean(TracingAwareMeterObservationHandler.class);
});
}
@Test
void autoConfiguresTracingAwareMeterObservationHandler() {
this.tracingContextRunner.withUserConfiguration(CustomTracingObservationHandlers.class).run((context) -> {
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
List<ObservationHandler<?>> handlers = context.getBean(CalledHandlers.class).getCalledHandlers();
// Intentionally not stopped since that will trigger additional logic in
// TracingAwareMeterObservationHandler that we don't test here
Observation.start("test-observation", observationRegistry);
assertThat(handlers).hasSize(1);
assertThat(handlers.get(0)).isInstanceOf(CustomTracingObservationHandler.class);
assertThat(context).hasSingleBean(TracingAwareMeterObservationHandler.class);
assertThat(context.getBeansOfType(ObservationHandler.class)).hasSize(2);
});
}
@Test
void autoConfiguresObservationHandlerWhenTracingIsActive() {
this.tracingContextRunner.withUserConfiguration(ObservationHandlersTracing.class).run((context) -> {
@ -156,6 +258,8 @@ class ObservationAutoConfigurationTests {
assertThat(handlers.get(2)).isInstanceOf(CustomMeterObservationHandler.class);
assertThat(((CustomMeterObservationHandler) handlers.get(2)).getName())
.isEqualTo("customMeterObservationHandler1");
assertThat(context).doesNotHaveBean(TracingAwareMeterObservationHandler.class);
assertThat(context).doesNotHaveBean(DefaultMeterObservationHandler.class);
});
}
@ -236,6 +340,27 @@ class ObservationAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class TracerConfiguration {
@Bean
Tracer tracer() {
return mock(Tracer.class); // simulating tracer configuration
}
}
@Configuration(proxyBeanMethods = false)
@Import(CalledHandlersConfiguration.class)
static class CustomTracingObservationHandlers {
@Bean
CustomTracingObservationHandler customTracingHandler1(CalledHandlers calledHandlers) {
return new CustomTracingObservationHandler("customTracingHandler1", calledHandlers);
}
}
@Configuration(proxyBeanMethods = false)
@Import(CalledHandlersConfiguration.class)
static class ObservationHandlersTracing {

Loading…
Cancel
Save