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`. Issue: 45746
This commit is contained in:
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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;
|
||||
@@ -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;
|
||||
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;
|
||||
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;
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(Tracer.class)
|
||||
@ConditionalOnBean(Tracer.class)
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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,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
|
||||
|
||||
@@ -1,514 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
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 {
|
||||
});
|
||||
}
|
||||
|
||||
@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 {
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
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;
|
||||
* @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 {
|
||||
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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Moritz Halbritter
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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> {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user