Browse Source

Introduce ObservationHandlerGroup interface

Add a new `ObservationHandlerGroup` interface that allows grouping
logic to be implemented by the metrics and tracing modules. This
update removes the need for `ObservabilityAutoConfiguration`.

Closes gh-45746
pull/46230/head
Phillip Webb 7 months ago committed by Andy Wilkinson
parent
commit
98506c666d
  1. 131
      spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/observability/ObservabilityAutoConfiguration.java
  2. 20
      spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/observability/package-info.java
  3. 11
      spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java
  4. 92
      spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingAndMeterObservationHandlerGroup.java
  5. 1
      spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  6. 514
      spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/observability/ObservabilityAutoConfigurationTests.java
  7. 28
      spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java
  8. 118
      spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingAndMeterObservationHandlerGroupTests.java
  9. 19
      spring-boot-project/spring-boot-metrics/src/main/java/org/springframework/boot/metrics/autoconfigure/MetricsAutoConfiguration.java
  10. 27
      spring-boot-project/spring-boot-metrics/src/main/java/org/springframework/boot/metrics/autoconfigure/MetricsProperties.java
  11. 33
      spring-boot-project/spring-boot-metrics/src/test/java/org/springframework/boot/metrics/autoconfigure/MetricsAutoConfigurationTests.java
  12. 4
      spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationAutoConfiguration.java
  13. 79
      spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationHandlerGroup.java
  14. 82
      spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationHandlerGrouping.java
  15. 81
      spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationHandlerGroups.java
  16. 25
      spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationRegistryConfigurer.java
  17. 8
      spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationRegistryPostProcessor.java
  18. 10
      spring-boot-project/spring-boot-observation/src/main/resources/META-INF/additional-spring-configuration-metadata.json
  19. 1
      spring-boot-project/spring-boot-observation/src/test/java/org/springframework/boot/observation/autoconfigure/ObservationAutoConfigurationTests.java
  20. 96
      spring-boot-project/spring-boot-observation/src/test/java/org/springframework/boot/observation/autoconfigure/ObservationHandlerGroupTests.java
  21. 17
      spring-boot-project/spring-boot-observation/src/test/java/org/springframework/boot/observation/autoconfigure/ObservationHandlerGroupsTests.java
  22. 3
      spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability.imports

131
spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/observability/ObservabilityAutoConfiguration.java

@ -1,131 +0,0 @@ @@ -1,131 +0,0 @@
/*
* Copyright 2012-present 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.observability;
import java.util.List;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler.IgnoredMeters;
import io.micrometer.core.instrument.observation.MeterObservationHandler;
import io.micrometer.observation.Observation;
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.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration;
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.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.observation.autoconfigure.ObservationHandlerGrouping;
import org.springframework.boot.observation.autoconfigure.ObservationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Observation API.
*
* @author Moritz Halbritter
* @author Brian Clozel
* @author Jonatan Ivanov
* @author Vedran Pavic
* @since 3.0.0
*/
@AutoConfiguration(beforeName = "org.springframework.boot.observation.autoconfigure.ObservationAutoConfiguration",
afterName = "org.springframework.boot.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration",
after = MicrometerTracingAutoConfiguration.class)
@ConditionalOnClass({ ObservationRegistry.class, ObservationHandlerGrouping.class })
public class ObservabilityAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnMissingClass("io.micrometer.tracing.Tracer")
static class OnlyMetricsConfiguration {
@Bean
ObservationHandlerGrouping metricsObservationHandlerGrouping() {
return new ObservationHandlerGrouping(MeterObservationHandler.class);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Tracer.class)
@ConditionalOnMissingClass("io.micrometer.core.instrument.MeterRegistry")
static class OnlyTracingConfiguration {
@Bean
ObservationHandlerGrouping tracingObservationHandlerGrouping() {
return new ObservationHandlerGrouping(TracingObservationHandler.class);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ MeterRegistry.class, Tracer.class })
static class MetricsWithTracingConfiguration {
@Bean
ObservationHandlerGrouping metricsAndTracingObservationHandlerGrouping() {
return new ObservationHandlerGrouping(
List.of(TracingObservationHandler.class, MeterObservationHandler.class));
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(MeterRegistry.class)
@ConditionalOnMissingBean(MeterObservationHandler.class)
@EnableConfigurationProperties(ObservationProperties.class)
static class MeterObservationHandlerConfiguration {
@ConditionalOnMissingBean(type = "io.micrometer.tracing.Tracer")
@Configuration(proxyBeanMethods = false)
static class OnlyMetricsMeterObservationHandlerConfiguration {
@Bean
DefaultMeterObservationHandler defaultMeterObservationHandler(MeterRegistry meterRegistry,
ObservationProperties properties) {
return properties.getLongTaskTimer().isEnabled() ? new DefaultMeterObservationHandler(meterRegistry)
: new DefaultMeterObservationHandler(meterRegistry, IgnoredMeters.LONG_TASK_TIMER);
}
}
@ConditionalOnBean(Tracer.class)
@Configuration(proxyBeanMethods = false)
static class TracingAndMetricsObservationHandlerConfiguration {
@Bean
TracingAwareMeterObservationHandler<Observation.Context> tracingAwareMeterObservationHandler(
MeterRegistry meterRegistry, Tracer tracer, ObservationProperties properties) {
DefaultMeterObservationHandler delegate = properties.getLongTaskTimer().isEnabled()
? new DefaultMeterObservationHandler(meterRegistry)
: new DefaultMeterObservationHandler(meterRegistry, IgnoredMeters.LONG_TASK_TIMER);
return new TracingAwareMeterObservationHandler<>(delegate, tracer);
}
}
}
}

20
spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/observability/package-info.java

@ -1,20 +0,0 @@ @@ -1,20 +0,0 @@
/*
* Copyright 2012-present 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.
*/
/**
* Auto-configuration for observability (metrics and tracing).
*/
package org.springframework.boot.actuate.autoconfigure.observability;

11
spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java

@ -27,6 +27,7 @@ import io.micrometer.tracing.annotation.SpanTagAnnotationHandler; @@ -27,6 +27,7 @@ import io.micrometer.tracing.annotation.SpanTagAnnotationHandler;
import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
import io.micrometer.tracing.handler.PropagatingReceiverTracingObservationHandler;
import io.micrometer.tracing.handler.PropagatingSenderTracingObservationHandler;
import io.micrometer.tracing.handler.TracingObservationHandler;
import io.micrometer.tracing.propagation.Propagator;
import org.aspectj.weaver.Advice;
@ -37,6 +38,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -37,6 +38,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.observation.autoconfigure.ObservationHandlerGroup;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@ -45,6 +47,7 @@ import org.springframework.expression.Expression; @@ -45,6 +47,7 @@ import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.util.ClassUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Tracing API.
@ -54,7 +57,6 @@ import org.springframework.expression.spel.support.SimpleEvaluationContext; @@ -54,7 +57,6 @@ import org.springframework.expression.spel.support.SimpleEvaluationContext;
* @since 3.0.0
*/
@AutoConfiguration
@ConditionalOnClass(Tracer.class)
@ConditionalOnBean(Tracer.class)
public class MicrometerTracingAutoConfiguration {
@ -75,6 +77,13 @@ public class MicrometerTracingAutoConfiguration { @@ -75,6 +77,13 @@ public class MicrometerTracingAutoConfiguration {
*/
public static final int SENDER_TRACING_OBSERVATION_HANDLER_ORDER = 2000;
@Bean
public ObservationHandlerGroup tracingObservationHandlerGroup(Tracer tracer) {
return ClassUtils.isPresent("io.micrometer.core.instrument.MeterRegistry", null)
? new TracingAndMeterObservationHandlerGroup(tracer)
: ObservationHandlerGroup.of(TracingObservationHandler.class);
}
@Bean
@ConditionalOnMissingBean
@Order(DEFAULT_TRACING_OBSERVATION_HANDLER_ORDER)

92
spring-boot-project/spring-boot-actuator-autoconfigure-all/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingAndMeterObservationHandlerGroup.java

@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
/*
* Copyright 2012-present 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.tracing;
import java.util.ArrayList;
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.Tracer;
import io.micrometer.tracing.handler.TracingAwareMeterObservationHandler;
import io.micrometer.tracing.handler.TracingObservationHandler;
import org.springframework.boot.observation.autoconfigure.ObservationHandlerGroup;
/**
* {@link ObservationHandlerGroup} that considers both {@link TracingObservationHandler}
* and {@link MeterObservationHandler} types as members. This group takes precedence over
* any regular {@link MeterObservationHandler} group in order to use ensure
* {@link TracingAwareMeterObservationHandler} wrapping is applied during registration.
*
* @author Phillip Webb
*/
class TracingAndMeterObservationHandlerGroup implements ObservationHandlerGroup {
private final Tracer tracer;
TracingAndMeterObservationHandlerGroup(Tracer tracer) {
this.tracer = tracer;
}
@Override
public boolean isMember(ObservationHandler<?> handler) {
return MeterObservationHandler.class.isInstance(handler) || TracingObservationHandler.class.isInstance(handler);
}
@Override
public int compareTo(ObservationHandlerGroup other) {
if (other instanceof TracingAndMeterObservationHandlerGroup) {
return 0;
}
return MeterObservationHandler.class.isAssignableFrom(other.handlerType()) ? -1 : 1;
}
@Override
public void registerMembers(ObservationConfig config, List<ObservationHandler<?>> members) {
List<ObservationHandler<?>> tracingHandlers = new ArrayList<>(members.size());
List<ObservationHandler<?>> metricsHandlers = new ArrayList<>(members.size());
for (ObservationHandler<?> member : members) {
if (member instanceof MeterObservationHandler<?> meterObservationHandler
&& !(member instanceof TracingAwareMeterObservationHandler<?>)) {
metricsHandlers.add(new TracingAwareMeterObservationHandler<>(meterObservationHandler, this.tracer));
}
else {
tracingHandlers.add(member);
}
}
registerHandlers(config, tracingHandlers);
registerHandlers(config, metricsHandlers);
}
private void registerHandlers(ObservationConfig config, List<ObservationHandler<?>> handlers) {
if (handlers.size() == 1) {
config.observationHandler(handlers.get(0));
}
else if (!handlers.isEmpty()) {
config.observationHandler(new FirstMatchingCompositeObservationHandler(handlers));
}
}
@Override
public Class<?> handlerType() {
return TracingObservationHandler.class;
}
}

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

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.ReactiveCloudFoundryActuatorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryActuatorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.observability.ObservabilityAutoConfiguration
org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration
org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration
org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration

514
spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/observability/ObservabilityAutoConfigurationTests.java

@ -1,514 +0,0 @@ @@ -1,514 +0,0 @@
/*
* Copyright 2012-present 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.observability;
import java.util.ArrayList;
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.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.Observation.Context;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationHandler.AllMatchingCompositeObservationHandler;
import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.aop.ObservedAspect;
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;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.observation.autoconfigure.ObservationAutoConfiguration;
import org.springframework.boot.observation.autoconfigure.ObservationHandlerGrouping;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ObservabilityAutoConfiguration}.
*
* @author Moritz Halbritter
* @author Jonatan Ivanov
* @author Vedran Pavic
*/
class ObservabilityAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withBean(SimpleMeterRegistry.class)
.withPropertyValues("management.observations.annotations.enabled=true")
.withClassLoader(new FilteredClassLoader("io.micrometer.tracing"))
.withConfiguration(
AutoConfigurations.of(ObservationAutoConfiguration.class, ObservabilityAutoConfiguration.class));
private final ApplicationContextRunner tracingContextRunner = new ApplicationContextRunner()
.withBean(SimpleMeterRegistry.class)
.withPropertyValues("management.observations.annotations.enabled=true")
.withUserConfiguration(TracerConfiguration.class)
.withConfiguration(
AutoConfigurations.of(ObservationAutoConfiguration.class, ObservabilityAutoConfiguration.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(ObservedAspect.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(ObservedAspect.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(ObservedAspect.class);
assertThat(context).hasSingleBean(TracingAwareMeterObservationHandler.class);
assertThat(context).hasSingleBean(ObservationHandlerGrouping.class);
assertThat(context).hasBean("metricsAndTracingObservationHandlerGrouping");
});
}
@Test
void supplyMeterHandlerAndGroupingWhenMicrometerCoreAndTracingAreOnClassPathButThereIsNoTracer() {
new ApplicationContextRunner().withBean(SimpleMeterRegistry.class)
.withPropertyValues("management.observations.annotations.enabled=true")
.withConfiguration(
AutoConfigurations.of(ObservationAutoConfiguration.class, ObservabilityAutoConfiguration.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(ObservedAspect.class);
assertThat(context).hasSingleBean(ObservationHandlerGrouping.class);
assertThat(context).hasBean("metricsAndTracingObservationHandlerGrouping");
});
}
@Test
void autoConfiguresDefaultMeterObservationHandler() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class);
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
Observation.start("test-observation", observationRegistry).stop();
// When a DefaultMeterObservationHandler is registered, every stopped
// Observation leads to a timer
MeterRegistry meterRegistry = context.getBean(MeterRegistry.class);
assertThat(meterRegistry.get("test-observation").timer().count()).isOne();
assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class);
assertThat(context).hasSingleBean(ObservationHandler.class);
assertThat(context).hasSingleBean(ObservedAspect.class);
});
}
@Test
void allowsDefaultMeterObservationHandlerToBeDisabled() {
this.contextRunner.withClassLoader(new FilteredClassLoader(MeterRegistry.class))
.run((context) -> assertThat(context).doesNotHaveBean(ObservationHandler.class));
}
@Test
void autoConfiguresObservationHandlers() {
this.contextRunner.withUserConfiguration(ObservationHandlers.class).run((context) -> {
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
List<ObservationHandler<?>> handlers = context.getBean(CalledHandlers.class).getCalledHandlers();
Observation.start("test-observation", observationRegistry).stop();
assertThat(context).doesNotHaveBean(DefaultMeterObservationHandler.class);
assertThat(handlers).hasSize(2);
// Multiple MeterObservationHandler are wrapped in
// FirstMatchingCompositeObservationHandler, which calls only the first one
assertThat(handlers.get(0)).isInstanceOf(CustomMeterObservationHandler.class);
assertThat(((CustomMeterObservationHandler) handlers.get(0)).getName())
.isEqualTo("customMeterObservationHandler1");
// Regular handlers are registered last
assertThat(handlers.get(1)).isInstanceOf(CustomObservationHandler.class);
assertThat(context).doesNotHaveBean(DefaultMeterObservationHandler.class);
assertThat(context).doesNotHaveBean(TracingAwareMeterObservationHandler.class);
});
}
@Test
void autoConfiguresObservationHandlerWithCustomContext() {
this.contextRunner.withUserConfiguration(ObservationHandlerWithCustomContextConfiguration.class)
.run((context) -> {
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
List<ObservationHandler<?>> handlers = context.getBean(CalledHandlers.class).getCalledHandlers();
CustomContext customContext = new CustomContext();
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) -> {
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
List<ObservationHandler<?>> handlers = context.getBean(CalledHandlers.class).getCalledHandlers();
Observation.start("test-observation", observationRegistry).stop();
assertThat(handlers).hasSize(3);
// Multiple TracingObservationHandler are wrapped in
// FirstMatchingCompositeObservationHandler, which calls only the first one
assertThat(handlers.get(0)).isInstanceOf(CustomTracingObservationHandler.class);
assertThat(((CustomTracingObservationHandler) handlers.get(0)).getName())
.isEqualTo("customTracingHandler1");
// Multiple MeterObservationHandler are wrapped in
// FirstMatchingCompositeObservationHandler, which calls only the first one
assertThat(handlers.get(1)).isInstanceOf(CustomMeterObservationHandler.class);
assertThat(((CustomMeterObservationHandler) handlers.get(1)).getName())
.isEqualTo("customMeterObservationHandler1");
// Regular handlers are registered last
assertThat(handlers.get(2)).isInstanceOf(CustomObservationHandler.class);
assertThat(context).doesNotHaveBean(TracingAwareMeterObservationHandler.class);
assertThat(context).doesNotHaveBean(DefaultMeterObservationHandler.class);
});
}
@Test
void shouldEnableLongTaskTimersByDefault() {
this.contextRunner.run((context) -> {
DefaultMeterObservationHandler handler = context.getBean(DefaultMeterObservationHandler.class);
assertThat(handler).hasFieldOrPropertyWithValue("shouldCreateLongTaskTimer", true);
});
}
@Test
void shouldDisableLongTaskTimerIfPropertyIsSet() {
this.contextRunner.withPropertyValues("management.observations.long-task-timer.enabled=false")
.run((context) -> {
DefaultMeterObservationHandler handler = context.getBean(DefaultMeterObservationHandler.class);
assertThat(handler).hasFieldOrPropertyWithValue("shouldCreateLongTaskTimer", false);
});
}
@Test
@SuppressWarnings("unchecked")
void shouldEnableLongTaskTimersForTracingByDefault() {
this.tracingContextRunner.run((context) -> {
TracingAwareMeterObservationHandler<Observation.Context> tracingHandler = context
.getBean(TracingAwareMeterObservationHandler.class);
Object delegate = ReflectionTestUtils.getField(tracingHandler, "delegate");
assertThat(delegate).hasFieldOrPropertyWithValue("shouldCreateLongTaskTimer", true);
});
}
@Test
@SuppressWarnings("unchecked")
void shouldDisableLongTaskTimerForTracingIfPropertyIsSet() {
this.tracingContextRunner.withPropertyValues("management.observations.long-task-timer.enabled=false")
.run((context) -> {
TracingAwareMeterObservationHandler<Observation.Context> tracingHandler = context
.getBean(TracingAwareMeterObservationHandler.class);
Object delegate = ReflectionTestUtils.getField(tracingHandler, "delegate");
assertThat(delegate).hasFieldOrPropertyWithValue("shouldCreateLongTaskTimer", false);
});
}
@Configuration(proxyBeanMethods = false)
@Import(CalledHandlersConfiguration.class)
static class ObservationHandlers {
@Bean
@Order(4)
AllMatchingCompositeObservationHandler customAllMatchingCompositeObservationHandler() {
return new AllMatchingCompositeObservationHandler();
}
@Bean
@Order(3)
FirstMatchingCompositeObservationHandler customFirstMatchingCompositeObservationHandler() {
return new FirstMatchingCompositeObservationHandler();
}
@Bean
@Order(2)
ObservationHandler<Context> customObservationHandler(CalledHandlers calledHandlers) {
return new CustomObservationHandler(calledHandlers);
}
@Bean
@Order(1)
MeterObservationHandler<Context> customMeterObservationHandler2(CalledHandlers calledHandlers) {
return new CustomMeterObservationHandler("customMeterObservationHandler2", calledHandlers);
}
@Bean
@Order(0)
MeterObservationHandler<Context> customMeterObservationHandler1(CalledHandlers calledHandlers) {
return new CustomMeterObservationHandler("customMeterObservationHandler1", calledHandlers);
}
}
@Configuration(proxyBeanMethods = false)
@Import(CalledHandlersConfiguration.class)
static class ObservationHandlerWithCustomContextConfiguration {
@Bean
ObservationHandlerWithCustomContext observationHandlerWithCustomContext(CalledHandlers calledHandlers) {
return new ObservationHandlerWithCustomContext(calledHandlers);
}
}
@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 {
@Bean
@Order(6)
CustomTracingObservationHandler customTracingHandler2(CalledHandlers calledHandlers) {
return new CustomTracingObservationHandler("customTracingHandler2", calledHandlers);
}
@Bean
@Order(5)
CustomTracingObservationHandler customTracingHandler1(CalledHandlers calledHandlers) {
return new CustomTracingObservationHandler("customTracingHandler1", calledHandlers);
}
@Bean
@Order(4)
AllMatchingCompositeObservationHandler customAllMatchingCompositeObservationHandler() {
return new AllMatchingCompositeObservationHandler();
}
@Bean
@Order(3)
FirstMatchingCompositeObservationHandler customFirstMatchingCompositeObservationHandler() {
return new FirstMatchingCompositeObservationHandler();
}
@Bean
@Order(2)
ObservationHandler<Context> customObservationHandler(CalledHandlers calledHandlers) {
return new CustomObservationHandler(calledHandlers);
}
@Bean
@Order(1)
MeterObservationHandler<Context> customMeterObservationHandler2(CalledHandlers calledHandlers) {
return new CustomMeterObservationHandler("customMeterObservationHandler2", calledHandlers);
}
@Bean
@Order(0)
MeterObservationHandler<Context> customMeterObservationHandler1(CalledHandlers calledHandlers) {
return new CustomMeterObservationHandler("customMeterObservationHandler1", calledHandlers);
}
}
private static class CustomTracingObservationHandler implements TracingObservationHandler<Context> {
private final Tracer tracer = mock(Tracer.class, Answers.RETURNS_MOCKS);
private final String name;
private final CalledHandlers calledHandlers;
CustomTracingObservationHandler(String name, CalledHandlers calledHandlers) {
this.name = name;
this.calledHandlers = calledHandlers;
}
String getName() {
return this.name;
}
@Override
public Tracer getTracer() {
return this.tracer;
}
@Override
public void onStart(Context context) {
this.calledHandlers.onCalled(this);
}
@Override
public boolean supportsContext(Context context) {
return true;
}
}
private static class ObservationHandlerWithCustomContext implements ObservationHandler<CustomContext> {
private final CalledHandlers calledHandlers;
ObservationHandlerWithCustomContext(CalledHandlers calledHandlers) {
this.calledHandlers = calledHandlers;
}
@Override
public void onStart(CustomContext context) {
this.calledHandlers.onCalled(this);
}
@Override
public boolean supportsContext(Context context) {
return context instanceof CustomContext;
}
}
private static final class CustomContext extends Context {
}
private static final class CalledHandlers {
private final List<ObservationHandler<?>> calledHandlers = new ArrayList<>();
void onCalled(ObservationHandler<?> handler) {
this.calledHandlers.add(handler);
}
List<ObservationHandler<?>> getCalledHandlers() {
return this.calledHandlers;
}
}
@Configuration(proxyBeanMethods = false)
static class CalledHandlersConfiguration {
@Bean
CalledHandlers calledHandlers() {
return new CalledHandlers();
}
}
private static class CustomObservationHandler implements ObservationHandler<Context> {
private final CalledHandlers calledHandlers;
CustomObservationHandler(CalledHandlers calledHandlers) {
this.calledHandlers = calledHandlers;
}
@Override
public void onStart(Context context) {
this.calledHandlers.onCalled(this);
}
@Override
public boolean supportsContext(Context context) {
return true;
}
}
private static class CustomMeterObservationHandler implements MeterObservationHandler<Context> {
private final CalledHandlers calledHandlers;
private final String name;
CustomMeterObservationHandler(String name, CalledHandlers calledHandlers) {
this.name = name;
this.calledHandlers = calledHandlers;
}
String getName() {
return this.name;
}
@Override
public void onStart(Context context) {
this.calledHandlers.onCalled(this);
}
@Override
public boolean supportsContext(Context context) {
return true;
}
}
}

28
spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java

@ -20,6 +20,8 @@ import java.util.List; @@ -20,6 +20,8 @@ import java.util.List;
import io.micrometer.common.annotation.ValueExpressionResolver;
import io.micrometer.common.annotation.ValueResolver;
import io.micrometer.core.instrument.observation.MeterObservationHandler;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.annotation.DefaultNewSpanParser;
import io.micrometer.tracing.annotation.ImperativeMethodInvocationProcessor;
@ -36,6 +38,7 @@ import org.aspectj.weaver.Advice; @@ -36,6 +38,7 @@ import org.aspectj.weaver.Advice;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.observation.autoconfigure.ObservationHandlerGroup;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@ -179,6 +182,31 @@ class MicrometerTracingAutoConfigurationTests { @@ -179,6 +182,31 @@ class MicrometerTracingAutoConfigurationTests {
});
}
@Test
void shouldCreateTracingAndMeterObservationHandlerGroupWhenHasTracing() {
this.contextRunner.withUserConfiguration(TracerConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(ObservationHandlerGroup.class);
ObservationHandlerGroup group = context.getBean(ObservationHandlerGroup.class);
assertThat(group).isInstanceOf(TracingAndMeterObservationHandlerGroup.class);
assertThat(group.isMember(mock(ObservationHandler.class))).isFalse();
assertThat(group.isMember(mock(TracingObservationHandler.class))).isTrue();
assertThat(group.isMember(mock(MeterObservationHandler.class))).isTrue();
});
}
@Test
void shouldCreateTracingObservationHandlerGroupWhenMetricsIsNotOnClassPath() {
this.contextRunner.withUserConfiguration(TracerConfiguration.class)
.withClassLoader(new FilteredClassLoader("io.micrometer.core"))
.run((context) -> {
assertThat(context).hasSingleBean(ObservationHandlerGroup.class);
ObservationHandlerGroup group = context.getBean(ObservationHandlerGroup.class);
assertThat(group).isNotInstanceOf(TracingAndMeterObservationHandlerGroup.class);
assertThat(group.isMember(mock(ObservationHandler.class))).isFalse();
assertThat(group.isMember(mock(TracingObservationHandler.class))).isTrue();
});
}
@Configuration(proxyBeanMethods = false)
private static final class TracerConfiguration {

118
spring-boot-project/spring-boot-actuator-autoconfigure-all/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingAndMeterObservationHandlerGroupTests.java

@ -0,0 +1,118 @@ @@ -0,0 +1,118 @@
/*
* Copyright 2012-present 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.tracing;
import java.util.ArrayList;
import java.util.Collections;
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.Tracer;
import io.micrometer.tracing.handler.TracingAwareMeterObservationHandler;
import io.micrometer.tracing.handler.TracingObservationHandler;
import org.assertj.core.extractor.Extractors;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.boot.observation.autoconfigure.ObservationHandlerGroup;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
/**
* Tests for {@link TracingAndMeterObservationHandlerGroup}.
*
* @author Phillip Webb
*/
class TracingAndMeterObservationHandlerGroupTests {
@Test
void compareToSortsBeforeMeterObservationHandlerGroup() {
ObservationHandlerGroup meterGroup = ObservationHandlerGroup.of(MeterObservationHandler.class);
TracingAndMeterObservationHandlerGroup tracingAndMeterGroup = new TracingAndMeterObservationHandlerGroup(
mock(Tracer.class));
assertThat(sort(meterGroup, tracingAndMeterGroup)).containsExactly(tracingAndMeterGroup, meterGroup);
assertThat(sort(tracingAndMeterGroup, meterGroup)).containsExactly(tracingAndMeterGroup, meterGroup);
}
@Test
void isMemberAcceptsMeterObservationHandlerOrTracingObservationHandler() {
TracingAndMeterObservationHandlerGroup group = new TracingAndMeterObservationHandlerGroup(mock(Tracer.class));
assertThat(group.isMember(mock(ObservationHandler.class))).isFalse();
assertThat(group.isMember(mock(MeterObservationHandler.class))).isTrue();
assertThat(group.isMember(mock(TracingObservationHandler.class))).isTrue();
}
@Test
@SuppressWarnings("unchecked")
void registerMembersWrapsMeterObservationHandlersAndRegistersDistinctGroups() {
Tracer tracer = mock(Tracer.class);
TracingAndMeterObservationHandlerGroup group = new TracingAndMeterObservationHandlerGroup(tracer);
TracingObservationHandler<?> tracingHandler1 = mock(TracingObservationHandler.class);
TracingObservationHandler<?> tracingHandler2 = mock(TracingObservationHandler.class);
MeterObservationHandler<?> meterHandler1 = mock(MeterObservationHandler.class);
MeterObservationHandler<?> meterHandler2 = mock(MeterObservationHandler.class);
ObservationConfig config = mock(ObservationConfig.class);
List<ObservationHandler<?>> members = List.of(tracingHandler1, meterHandler1, tracingHandler2, meterHandler2);
group.registerMembers(config, members);
ArgumentCaptor<ObservationHandler<?>> handlerCaptor = ArgumentCaptor.captor();
then(config).should(times(2)).observationHandler(handlerCaptor.capture());
List<ObservationHandler<?>> actualComposites = handlerCaptor.getAllValues();
assertThat(actualComposites).hasSize(2);
ObservationHandler<?> tracingComposite = actualComposites.get(0);
assertThat(tracingComposite).isInstanceOf(FirstMatchingCompositeObservationHandler.class);
List<ObservationHandler<?>> tracingHandlers = (List<ObservationHandler<?>>) Extractors.byName("handlers")
.apply(tracingComposite);
assertThat(tracingHandlers).containsExactly(tracingHandler1, tracingHandler2);
ObservationHandler<?> metricsComposite = actualComposites.get(1);
assertThat(metricsComposite).isInstanceOf(FirstMatchingCompositeObservationHandler.class);
List<ObservationHandler<?>> metricsHandlers = (List<ObservationHandler<?>>) Extractors.byName("handlers")
.apply(metricsComposite);
assertThat(metricsHandlers).hasSize(2);
assertThat(metricsHandlers).extracting("delegate").containsExactly(meterHandler1, meterHandler2);
}
@Test
void registerMembersOnlyUsesCompositeWhenMoreThanOneHandler() {
Tracer tracer = mock(Tracer.class);
TracingAndMeterObservationHandlerGroup group = new TracingAndMeterObservationHandlerGroup(tracer);
TracingObservationHandler<?> tracingHandler1 = mock(TracingObservationHandler.class);
TracingObservationHandler<?> tracingHandler2 = mock(TracingObservationHandler.class);
MeterObservationHandler<?> meterHandler = mock(MeterObservationHandler.class);
ObservationConfig config = mock(ObservationConfig.class);
List<ObservationHandler<?>> members = List.of(tracingHandler1, meterHandler, tracingHandler2);
group.registerMembers(config, members);
ArgumentCaptor<ObservationHandler<?>> handlerCaptor = ArgumentCaptor.captor();
then(config).should(times(2)).observationHandler(handlerCaptor.capture());
List<ObservationHandler<?>> actualComposites = handlerCaptor.getAllValues();
assertThat(actualComposites).hasSize(2);
assertThat(actualComposites.get(0)).isInstanceOf(FirstMatchingCompositeObservationHandler.class);
assertThat(actualComposites.get(1)).isInstanceOf(TracingAwareMeterObservationHandler.class);
}
private List<ObservationHandlerGroup> sort(ObservationHandlerGroup... groups) {
List<ObservationHandlerGroup> list = new ArrayList<>(List.of(groups));
Collections.sort(list);
return list;
}
}

19
spring-boot-project/spring-boot-metrics/src/main/java/org/springframework/boot/metrics/autoconfigure/MetricsAutoConfiguration.java

@ -22,7 +22,11 @@ import io.micrometer.core.annotation.Timed; @@ -22,7 +22,11 @@ import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler.IgnoredMeters;
import io.micrometer.core.instrument.observation.MeterObservationHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@ -30,6 +34,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -30,6 +34,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.observation.autoconfigure.ObservationHandlerGroup;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
@ -42,6 +47,7 @@ import org.springframework.core.annotation.Order; @@ -42,6 +47,7 @@ import org.springframework.core.annotation.Order;
* @author Jon Schneider
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Phillip Webb
* @since 4.0.0
*/
@AutoConfiguration(before = CompositeMeterRegistryAutoConfiguration.class)
@ -75,6 +81,19 @@ public class MetricsAutoConfiguration { @@ -75,6 +81,19 @@ public class MetricsAutoConfiguration {
return new MeterRegistryCloser(meterRegistries.orderedStream().toList());
}
@Bean
ObservationHandlerGroup metricsObservationHandlerGroup() {
return ObservationHandlerGroup.of(MeterObservationHandler.class);
}
@Bean
DefaultMeterObservationHandler defaultMeterObservationHandler(ObjectProvider<MeterRegistry> meterRegistryProvider,
Clock clock, MetricsProperties properties) {
MeterRegistry meterRegistry = meterRegistryProvider.getIfAvailable(() -> new CompositeMeterRegistry(clock));
return new DefaultMeterObservationHandler(meterRegistry,
properties.getObservations().getIgnoredMeters().toArray(IgnoredMeters[]::new));
}
/**
* Ensures that {@link MeterRegistry meter registries} are closed early in the
* shutdown process.

27
spring-boot-project/spring-boot-metrics/src/main/java/org/springframework/boot/metrics/autoconfigure/MetricsProperties.java

@ -21,8 +21,12 @@ import java.time.Duration; @@ -21,8 +21,12 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler.IgnoredMeters;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -63,6 +67,8 @@ public class MetricsProperties { @@ -63,6 +67,8 @@ public class MetricsProperties {
private final Distribution distribution = new Distribution();
private final Observations observations = new Observations();
public boolean isUseGlobalRegistry() {
return this.useGlobalRegistry;
}
@ -91,6 +97,10 @@ public class MetricsProperties { @@ -91,6 +97,10 @@ public class MetricsProperties {
return this.distribution;
}
public Observations getObservations() {
return this.observations;
}
public static class Web {
private final Client client = new Client();
@ -257,4 +267,21 @@ public class MetricsProperties { @@ -257,4 +267,21 @@ public class MetricsProperties {
}
public static class Observations {
/**
* Meters that should be ignored when recoding observations.
*/
private Set<IgnoredMeters> ignoredMeters = new LinkedHashSet<>();
public Set<IgnoredMeters> getIgnoredMeters() {
return this.ignoredMeters;
}
public void setIgnoredMeters(Set<IgnoredMeters> ignoredMeters) {
this.ignoredMeters = ignoredMeters;
}
}
}

33
spring-boot-project/spring-boot-metrics/src/test/java/org/springframework/boot/metrics/autoconfigure/MetricsAutoConfigurationTests.java

@ -22,11 +22,15 @@ import io.micrometer.core.instrument.MeterRegistry; @@ -22,11 +22,15 @@ import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.config.MeterFilterReply;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
import io.micrometer.core.instrument.observation.MeterObservationHandler;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.ObservationHandler;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.metrics.autoconfigure.MetricsAutoConfiguration.MeterRegistryCloser;
import org.springframework.boot.observation.autoconfigure.ObservationHandlerGroup;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -42,6 +46,7 @@ import static org.mockito.Mockito.mock; @@ -42,6 +46,7 @@ import static org.mockito.Mockito.mock;
*
* @author Andy Wilkinson
* @author Moritz Halbritter
* @author Phillip Webb
*/
class MetricsAutoConfigurationTests {
@ -89,6 +94,34 @@ class MetricsAutoConfigurationTests { @@ -89,6 +94,34 @@ class MetricsAutoConfigurationTests {
});
}
@Test
void supplyHandlerAndGroup() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(ObservationHandlerGroup.class);
assertThat(context).hasSingleBean(DefaultMeterObservationHandler.class);
ObservationHandlerGroup group = context.getBean(ObservationHandlerGroup.class);
assertThat(group.isMember(mock(ObservationHandler.class))).isFalse();
assertThat(group.isMember(mock(MeterObservationHandler.class))).isTrue();
});
}
@Test
void shouldEnableLongTaskTimerByDefault() {
this.contextRunner.run((context) -> {
DefaultMeterObservationHandler handler = context.getBean(DefaultMeterObservationHandler.class);
assertThat(handler).hasFieldOrPropertyWithValue("shouldCreateLongTaskTimer", true);
});
}
@Test
void shouldDisableLongTaskTimerIfPropertyIsSet() {
this.contextRunner.withPropertyValues("management.metrics.observations.ignored-meters=long-task-timer")
.run((context) -> {
DefaultMeterObservationHandler handler = context.getBean(DefaultMeterObservationHandler.class);
assertThat(handler).hasFieldOrPropertyWithValue("shouldCreateLongTaskTimer", false);
});
}
@Configuration(proxyBeanMethods = false)
static class CustomClockConfiguration {

4
spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationAutoConfiguration.java

@ -55,10 +55,10 @@ public class ObservationAutoConfiguration { @@ -55,10 +55,10 @@ public class ObservationAutoConfiguration {
ObjectProvider<ObservationPredicate> observationPredicates,
ObjectProvider<GlobalObservationConvention<?>> observationConventions,
ObjectProvider<ObservationHandler<?>> observationHandlers,
ObjectProvider<ObservationHandlerGrouping> observationHandlerGrouping,
ObjectProvider<ObservationHandlerGroup> observationHandlerGroups,
ObjectProvider<ObservationFilter> observationFilters) {
return new ObservationRegistryPostProcessor(observationRegistryCustomizers, observationPredicates,
observationConventions, observationHandlers, observationHandlerGrouping, observationFilters);
observationConventions, observationHandlers, observationHandlerGroups, observationFilters);
}
@Bean

79
spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationHandlerGroup.java

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
/*
* Copyright 2012-present 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.observation.autoconfigure;
import java.util.List;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler;
import io.micrometer.observation.ObservationRegistry.ObservationConfig;
import org.springframework.util.Assert;
/**
* Group of {@link ObservationHandler ObservationHandlers} that can be registered
* together. The first group claiming membership of a handler is responsible for
* registering it. Groups are {@link Comparable} so that they can be ordered against each
* other.
*
* @author Phillip Webb
* @since 4.0.0
*/
public interface ObservationHandlerGroup extends Comparable<ObservationHandlerGroup> {
/**
* Return if the given handler is a member of this group.
* @param handler the handler to check
* @return if the handler is a member
*/
default boolean isMember(ObservationHandler<?> handler) {
return handlerType().isInstance(handler);
}
/**
* Register group members against the given {@link ObservationConfig}.
* @param config the config used to register members
* @param members the group members to register
*/
default void registerMembers(ObservationConfig config, List<ObservationHandler<?>> members) {
config.observationHandler(new FirstMatchingCompositeObservationHandler(members));
}
@Override
default int compareTo(ObservationHandlerGroup other) {
return 0;
}
/**
* Return the primary type of handler that this group accepts.
* @return the accepted handler type
*/
Class<?> handlerType();
/**
* Static factory method to create a {@link ObservationHandlerGroup} with members of
* the given handler type.
* @param <H> the handler type
* @param handlerType the handler type
* @return a new {@link ObservationHandlerGroup}
*/
static <H extends ObservationHandler<?>> ObservationHandlerGroup of(Class<H> handlerType) {
Assert.notNull(handlerType, "'handlerType' must not be null");
return () -> handlerType;
}
}

82
spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationHandlerGrouping.java

@ -1,82 +0,0 @@ @@ -1,82 +0,0 @@
/*
* Copyright 2012-present 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.observation.autoconfigure;
import java.util.ArrayList;
import java.util.List;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler;
import io.micrometer.observation.ObservationRegistry.ObservationConfig;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Groups {@link ObservationHandler ObservationHandlers} by type.
*
* @author Andy Wilkinson
* @author Moritz Halbritter
* @since 4.0.0
*/
@SuppressWarnings("rawtypes")
public class ObservationHandlerGrouping {
private final List<Class<? extends ObservationHandler>> categories;
public ObservationHandlerGrouping(Class<? extends ObservationHandler> category) {
this(List.of(category));
}
public ObservationHandlerGrouping(List<Class<? extends ObservationHandler>> categories) {
this.categories = categories;
}
void apply(List<ObservationHandler<?>> handlers, ObservationConfig config) {
MultiValueMap<Class<? extends ObservationHandler>, ObservationHandler<?>> groupings = new LinkedMultiValueMap<>();
List<ObservationHandler<?>> handlersWithoutCategory = new ArrayList<>();
for (ObservationHandler<?> handler : handlers) {
Class<? extends ObservationHandler> category = findCategory(handler);
if (category != null) {
groupings.add(category, handler);
}
else {
handlersWithoutCategory.add(handler);
}
}
for (Class<? extends ObservationHandler> category : this.categories) {
List<ObservationHandler<?>> handlerGroup = groupings.get(category);
if (!CollectionUtils.isEmpty(handlerGroup)) {
config.observationHandler(new FirstMatchingCompositeObservationHandler(handlerGroup));
}
}
for (ObservationHandler<?> observationHandler : handlersWithoutCategory) {
config.observationHandler(observationHandler);
}
}
private Class<? extends ObservationHandler> findCategory(ObservationHandler<?> handler) {
for (Class<? extends ObservationHandler> category : this.categories) {
if (category.isInstance(handler)) {
return category;
}
}
return null;
}
}

81
spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationHandlerGroups.java

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
/*
* Copyright 2012-present 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.observation.autoconfigure;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry.ObservationConfig;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* A collection {@link ObservationHandlerGroup} instance and supporting registration
* logic.
*
* @author Andy Wilkinson
* @author Moritz Halbritter
* @author Phillip Webb
*/
class ObservationHandlerGroups {
private final List<ObservationHandlerGroup> groups;
ObservationHandlerGroups(Collection<? extends ObservationHandlerGroup> groups) {
this.groups = Collections.unmodifiableList(sort(groups));
}
private static List<ObservationHandlerGroup> sort(Collection<? extends ObservationHandlerGroup> groups) {
ArrayList<ObservationHandlerGroup> sortedGroups = new ArrayList<>(groups);
Collections.sort(sortedGroups);
return sortedGroups;
}
void register(ObservationConfig config, List<ObservationHandler<?>> handlers) {
MultiValueMap<ObservationHandlerGroup, ObservationHandler<?>> grouped = new LinkedMultiValueMap<>();
for (ObservationHandler<?> handler : handlers) {
grouped.add(findGroup(handler), handler);
}
for (ObservationHandlerGroup group : this.groups) {
List<ObservationHandler<?>> members = grouped.get(group);
if (!CollectionUtils.isEmpty(members)) {
group.registerMembers(config, members);
}
}
List<ObservationHandler<?>> unclaimed = grouped.get(null);
if (!CollectionUtils.isEmpty(unclaimed)) {
for (ObservationHandler<?> handler : unclaimed) {
config.observationHandler(handler);
}
}
}
private ObservationHandlerGroup findGroup(ObservationHandler<?> handler) {
for (ObservationHandlerGroup group : this.groups) {
if (group.isMember(handler)) {
return group;
}
}
return null;
}
}

25
spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationRegistryConfigurer.java

@ -32,7 +32,7 @@ import org.springframework.boot.util.LambdaSafe; @@ -32,7 +32,7 @@ import org.springframework.boot.util.LambdaSafe;
* {@link ObservationRegistry observation registries}. Installs
* {@link ObservationPredicate observation predicates} and
* {@link GlobalObservationConvention global observation conventions} into the
* {@link ObservationRegistry}. Also uses a {@link ObservationHandlerGrouping} to group
* {@link ObservationRegistry}. Also uses a {@link ObservationHandlerGroups} to group
* handlers, which are then added to the {@link ObservationRegistry}.
*
* @author Moritz Halbritter
@ -47,7 +47,7 @@ class ObservationRegistryConfigurer { @@ -47,7 +47,7 @@ class ObservationRegistryConfigurer {
private final ObjectProvider<ObservationHandler<?>> observationHandlers;
private final ObjectProvider<ObservationHandlerGrouping> observationHandlerGrouping;
private final ObjectProvider<ObservationHandlerGroup> observationHandlerGroups;
private final ObjectProvider<ObservationFilter> observationFilters;
@ -55,13 +55,13 @@ class ObservationRegistryConfigurer { @@ -55,13 +55,13 @@ class ObservationRegistryConfigurer {
ObjectProvider<ObservationPredicate> observationPredicates,
ObjectProvider<GlobalObservationConvention<?>> observationConventions,
ObjectProvider<ObservationHandler<?>> observationHandlers,
ObjectProvider<ObservationHandlerGrouping> observationHandlerGrouping,
ObjectProvider<ObservationHandlerGroup> observationHandlerGroups,
ObjectProvider<ObservationFilter> observationFilters) {
this.customizers = customizers;
this.observationPredicates = observationPredicates;
this.observationConventions = observationConventions;
this.observationHandlers = observationHandlers;
this.observationHandlerGrouping = observationHandlerGrouping;
this.observationHandlerGroups = observationHandlerGroups;
this.observationFilters = observationFilters;
}
@ -74,14 +74,9 @@ class ObservationRegistryConfigurer { @@ -74,14 +74,9 @@ class ObservationRegistryConfigurer {
}
private void registerHandlers(ObservationRegistry registry) {
ObservationHandlerGrouping grouping = this.observationHandlerGrouping.getIfAvailable();
List<ObservationHandler<?>> orderedHandlers = asOrderedList(this.observationHandlers);
if (grouping != null) {
grouping.apply(orderedHandlers, registry.observationConfig());
}
else {
orderedHandlers.forEach((handler) -> registry.observationConfig().observationHandler(handler));
}
ObservationHandlerGroups groups = new ObservationHandlerGroups(this.observationHandlerGroups.stream().toList());
List<ObservationHandler<?>> orderedHandlers = this.observationHandlers.orderedStream().toList();
groups.register(registry.observationConfig(), orderedHandlers);
}
private void registerObservationPredicates(ObservationRegistry registry) {
@ -98,13 +93,9 @@ class ObservationRegistryConfigurer { @@ -98,13 +93,9 @@ class ObservationRegistryConfigurer {
@SuppressWarnings("unchecked")
private void customize(ObservationRegistry registry) {
LambdaSafe.callbacks(ObservationRegistryCustomizer.class, asOrderedList(this.customizers), registry)
LambdaSafe.callbacks(ObservationRegistryCustomizer.class, this.customizers.orderedStream().toList(), registry)
.withLogger(ObservationRegistryConfigurer.class)
.invoke((customizer) -> customizer.customize(registry));
}
private <T> List<T> asOrderedList(ObjectProvider<T> provider) {
return provider.orderedStream().toList();
}
}

8
spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationRegistryPostProcessor.java

@ -43,7 +43,7 @@ class ObservationRegistryPostProcessor implements BeanPostProcessor { @@ -43,7 +43,7 @@ class ObservationRegistryPostProcessor implements BeanPostProcessor {
private final ObjectProvider<ObservationHandler<?>> observationHandlers;
private final ObjectProvider<ObservationHandlerGrouping> observationHandlerGrouping;
private final ObjectProvider<ObservationHandlerGroup> observationHandlerGroups;
private final ObjectProvider<ObservationFilter> observationFilters;
@ -53,13 +53,13 @@ class ObservationRegistryPostProcessor implements BeanPostProcessor { @@ -53,13 +53,13 @@ class ObservationRegistryPostProcessor implements BeanPostProcessor {
ObjectProvider<ObservationPredicate> observationPredicates,
ObjectProvider<GlobalObservationConvention<?>> observationConventions,
ObjectProvider<ObservationHandler<?>> observationHandlers,
ObjectProvider<ObservationHandlerGrouping> observationHandlerGrouping,
ObjectProvider<ObservationHandlerGroup> observationHandlerGroups,
ObjectProvider<ObservationFilter> observationFilters) {
this.observationRegistryCustomizers = observationRegistryCustomizers;
this.observationPredicates = observationPredicates;
this.observationConventions = observationConventions;
this.observationHandlers = observationHandlers;
this.observationHandlerGrouping = observationHandlerGrouping;
this.observationHandlerGroups = observationHandlerGroups;
this.observationFilters = observationFilters;
}
@ -75,7 +75,7 @@ class ObservationRegistryPostProcessor implements BeanPostProcessor { @@ -75,7 +75,7 @@ class ObservationRegistryPostProcessor implements BeanPostProcessor {
if (this.configurer == null) {
this.configurer = new ObservationRegistryConfigurer(this.observationRegistryCustomizers,
this.observationPredicates, this.observationConventions, this.observationHandlers,
this.observationHandlerGrouping, this.observationFilters);
this.observationHandlerGroups, this.observationFilters);
}
return this.configurer;
}

10
spring-boot-project/spring-boot-observation/src/main/resources/META-INF/additional-spring-configuration-metadata.json

@ -6,6 +6,16 @@ @@ -6,6 +6,16 @@
"type": "java.lang.Boolean",
"description": "Whether auto-configuration of Micrometer annotations is enabled.",
"defaultValue": false
},
{
"name": "management.observations.annotations.long-lask-timer.enabled",
"type": "java.lang.boolean",
"description": "Whether to create a LongTaskTimer for every observation.",
"defaultValue": true,
"deprecation": {
"level": "error",
"replacement": "management.metrics.observations.ignored-meters"
}
}
]
}

1
spring-boot-project/spring-boot-observation/src/test/java/org/springframework/boot/observation/autoconfigure/ObservationAutoConfigurationTests.java

@ -68,7 +68,6 @@ class ObservationAutoConfigurationTests { @@ -68,7 +68,6 @@ class ObservationAutoConfigurationTests {
ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class);
Observation.start("test-observation", observationRegistry).stop();
assertThat(context).hasSingleBean(ObservedAspect.class);
assertThat(context).doesNotHaveBean(ObservationHandlerGrouping.class);
});
}

96
spring-boot-project/spring-boot-observation/src/test/java/org/springframework/boot/observation/autoconfigure/ObservationHandlerGroupTests.java

@ -0,0 +1,96 @@ @@ -0,0 +1,96 @@
/*
* Copyright 2012-present 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.observation.autoconfigure;
import java.util.List;
import io.micrometer.observation.Observation.Context;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler;
import io.micrometer.observation.ObservationRegistry.ObservationConfig;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ObservationHandlerGroup}.
*
* @author Phillip Webb
*/
class ObservationHandlerGroupTests {
@Test
void isMemberWhenHandlerIsInstanceOfHandlerTypeReturnsTrue() {
ObservationHandlerGroup group = ObservationHandlerGroup.of(TestObservationHandler.class);
assertThat(group.isMember(mock(TestObservationHandler.class))).isTrue();
assertThat(group.isMember(mock(TestObservationHandlerSubclass.class))).isTrue();
}
@Test
void isMemberWhenHandlerIsNotInstanceOfHandlerTypeReturnsFalse() {
ObservationHandlerGroup group = ObservationHandlerGroup.of(TestObservationHandler.class);
assertThat(group.isMember(mock(ObservationHandler.class))).isFalse();
assertThat(group.isMember(mock(OtherObservationHandler.class))).isFalse();
}
@Test
void registerMembersRegistersUsingFirstMatchingCompositeObservationHandler() {
ObservationHandlerGroup group = ObservationHandlerGroup.of(TestObservationHandler.class);
TestObservationHandler handler1 = mock(TestObservationHandler.class);
TestObservationHandler handler2 = mock(TestObservationHandler.class);
ObservationConfig config = mock(ObservationConfig.class);
group.registerMembers(config, List.of(handler1, handler2));
ArgumentCaptor<ObservationHandler<?>> registeredHandler = ArgumentCaptor.captor();
then(config).should().observationHandler(registeredHandler.capture());
assertThat(registeredHandler.getValue()).isInstanceOf(FirstMatchingCompositeObservationHandler.class)
.extracting("handlers")
.asInstanceOf(InstanceOfAssertFactories.LIST)
.containsExactly(handler1, handler2);
}
@Test
void compareToReturnsZero() {
ObservationHandlerGroup group1 = ObservationHandlerGroup.of(TestObservationHandler.class);
ObservationHandlerGroup group2 = ObservationHandlerGroup.of(TestObservationHandler.class);
assertThat(group1.compareTo(group1)).isZero();
assertThat(group1.compareTo(group2)).isZero();
assertThat(group2.compareTo(group1)).isZero();
}
@Test
void ofCreatesHandlerGroup() {
ObservationHandlerGroup group = ObservationHandlerGroup.of(TestObservationHandler.class);
assertThat(group.handlerType()).isEqualTo(TestObservationHandler.class);
}
interface TestObservationHandler extends ObservationHandler<Context> {
}
interface TestObservationHandlerSubclass extends TestObservationHandler {
}
interface OtherObservationHandler extends ObservationHandler<Context> {
}
}

17
spring-boot-project/spring-boot-observation/src/test/java/org/springframework/boot/observation/autoconfigure/ObservationHandlerGroupingTests.java → spring-boot-project/spring-boot-observation/src/test/java/org/springframework/boot/observation/autoconfigure/ObservationHandlerGroupsTests.java

@ -31,22 +31,25 @@ import org.springframework.util.ReflectionUtils; @@ -31,22 +31,25 @@ import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ObservationHandlerGrouping}.
* Tests for {@link ObservationHandlerGroups}.
*
* @author Moritz Halbritter
*/
class ObservationHandlerGroupingTests {
class ObservationHandlerGroupsTests {
private static final ObservationHandlerGroup GROUP_A = ObservationHandlerGroup.of(ObservationHandlerA.class);
private static final ObservationHandlerGroup GROUP_B = ObservationHandlerGroup.of(ObservationHandlerB.class);
@Test
void shouldGroupCategoriesIntoFirstMatchingHandlerAndRespectCategoryOrder() {
ObservationHandlerGrouping grouping = new ObservationHandlerGrouping(
List.of(ObservationHandlerA.class, ObservationHandlerB.class));
ObservationHandlerGroups grouping = new ObservationHandlerGroups(List.of(GROUP_A, GROUP_B));
ObservationConfig config = new ObservationConfig();
ObservationHandlerA handlerA1 = new ObservationHandlerA("a1");
ObservationHandlerA handlerA2 = new ObservationHandlerA("a2");
ObservationHandlerB handlerB1 = new ObservationHandlerB("b1");
ObservationHandlerB handlerB2 = new ObservationHandlerB("b2");
grouping.apply(List.of(handlerB1, handlerB2, handlerA1, handlerA2), config);
grouping.register(config, List.of(handlerB1, handlerB2, handlerA1, handlerA2));
List<ObservationHandler<?>> handlers = getObservationHandlers(config);
assertThat(handlers).hasSize(2);
// Category A is first
@ -63,12 +66,12 @@ class ObservationHandlerGroupingTests { @@ -63,12 +66,12 @@ class ObservationHandlerGroupingTests {
@Test
void uncategorizedHandlersShouldBeOrderedAfterCategories() {
ObservationHandlerGrouping grouping = new ObservationHandlerGrouping(ObservationHandlerA.class);
ObservationHandlerGroups grouping = new ObservationHandlerGroups(List.of(GROUP_A));
ObservationConfig config = new ObservationConfig();
ObservationHandlerA handlerA1 = new ObservationHandlerA("a1");
ObservationHandlerA handlerA2 = new ObservationHandlerA("a2");
ObservationHandlerB handlerB1 = new ObservationHandlerB("b1");
grouping.apply(List.of(handlerB1, handlerA1, handlerA2), config);
grouping.register(config, List.of(handlerB1, handlerA1, handlerA2));
List<ObservationHandler<?>> handlers = getObservationHandlers(config);
assertThat(handlers).hasSize(2);
// Category A is first

3
spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability.imports

@ -11,6 +11,3 @@ org.springframework.boot.metrics.autoconfigure.MetricsAutoConfiguration @@ -11,6 +11,3 @@ org.springframework.boot.metrics.autoconfigure.MetricsAutoConfiguration
# Tracing
org.springframework.boot.actuate.autoconfigure.tracing.NoopTracerAutoConfiguration
org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration
# Observability
org.springframework.boot.actuate.autoconfigure.observability.ObservabilityAutoConfiguration

Loading…
Cancel
Save