Browse Source
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-45746pull/46230/head
22 changed files with 617 additions and 783 deletions
@ -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); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -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; |
||||
@ -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,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; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -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; |
||||
} |
||||
|
||||
} |
||||
@ -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; |
||||
} |
||||
|
||||
} |
||||
@ -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; |
||||
} |
||||
|
||||
} |
||||
@ -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; |
||||
} |
||||
|
||||
} |
||||
@ -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> { |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue