Merge pull request #46007 from doumdoum
* gh-46007: Polish "Add @MeterTag support to existing @Timed and @Counted support" Add @MeterTag support to existing @Timed and @Counted support Closes gh-46007
This commit is contained in:
commit
d25132185e
|
@ -16,12 +16,15 @@
|
|||
|
||||
package org.springframework.boot.metrics.autoconfigure;
|
||||
|
||||
import io.micrometer.common.annotation.ValueExpressionResolver;
|
||||
import io.micrometer.core.aop.CountedAspect;
|
||||
import io.micrometer.core.aop.CountedMeterTagAnnotationHandler;
|
||||
import io.micrometer.core.aop.MeterTagAnnotationHandler;
|
||||
import io.micrometer.core.aop.TimedAspect;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import org.aspectj.weaver.Advice;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
|
@ -29,6 +32,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.ObservationAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
|
@ -36,9 +40,11 @@ import org.springframework.context.annotation.Bean;
|
|||
* aspects.
|
||||
*
|
||||
* @author Jonatan Ivanov
|
||||
* @author Dominique Villard
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class })
|
||||
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
|
||||
ObservationAutoConfiguration.class })
|
||||
@ConditionalOnClass({ MeterRegistry.class, Advice.class })
|
||||
@ConditionalOnBooleanProperty("management.observations.annotations.enabled")
|
||||
@ConditionalOnBean(MeterRegistry.class)
|
||||
|
@ -46,8 +52,11 @@ public class MetricsAspectsAutoConfiguration {
|
|||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
CountedAspect countedAspect(MeterRegistry registry) {
|
||||
return new CountedAspect(registry);
|
||||
CountedAspect countedAspect(MeterRegistry registry,
|
||||
ObjectProvider<CountedMeterTagAnnotationHandler> countedMeterTagAnnotationHandler) {
|
||||
CountedAspect countedAspect = new CountedAspect(registry);
|
||||
countedMeterTagAnnotationHandler.ifAvailable(countedAspect::setMeterTagAnnotationHandler);
|
||||
return countedAspect;
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -59,4 +68,23 @@ public class MetricsAspectsAutoConfiguration {
|
|||
return timedAspect;
|
||||
}
|
||||
|
||||
@ConditionalOnBean(ValueExpressionResolver.class)
|
||||
static class TagAnnotationHandlersConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler(BeanFactory beanFactory,
|
||||
ValueExpressionResolver valueExpressionResolver) {
|
||||
return new CountedMeterTagAnnotationHandler(beanFactory::getBean, (ignored) -> valueExpressionResolver);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
MeterTagAnnotationHandler meterTagAnnotationHandler(BeanFactory beanFactory,
|
||||
ValueExpressionResolver valueExpressionResolver) {
|
||||
return new MeterTagAnnotationHandler(beanFactory::getBean, (ignored) -> valueExpressionResolver);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
package org.springframework.boot.metrics.autoconfigure;
|
||||
|
||||
import io.micrometer.common.annotation.ValueExpressionResolver;
|
||||
import io.micrometer.core.aop.CountedAspect;
|
||||
import io.micrometer.core.aop.CountedMeterTagAnnotationHandler;
|
||||
import io.micrometer.core.aop.MeterTagAnnotationHandler;
|
||||
import io.micrometer.core.aop.TimedAspect;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
@ -32,6 +34,7 @@ import org.springframework.context.annotation.Configuration;
|
|||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link MetricsAspectsAutoConfiguration}.
|
||||
|
@ -63,14 +66,55 @@ class MetricsAspectsAutoConfigurationTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureMeterTagAnnotationHandler() {
|
||||
this.contextRunner.withUserConfiguration(MeterTagAnnotationHandlerConfiguration.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(CountedAspect.class);
|
||||
assertThat(ReflectionTestUtils.getField(context.getBean(TimedAspect.class), "meterTagAnnotationHandler"))
|
||||
void shouldAutoConfigureMeterTagAnnotationHandlerWhenValueExpressionResolverIsAvailable() {
|
||||
this.contextRunner.withBean(ValueExpressionResolver.class, () -> mock(ValueExpressionResolver.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(TimedAspect.class).hasSingleBean(MeterTagAnnotationHandler.class);
|
||||
assertThat(
|
||||
ReflectionTestUtils.getField(context.getBean(TimedAspect.class), "meterTagAnnotationHandler"))
|
||||
.isSameAs(context.getBean(MeterTagAnnotationHandler.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseUserDefinedMeterTagAnnotationHandler() {
|
||||
this.contextRunner
|
||||
.withBean("customMeterTagAnnotationHandler", MeterTagAnnotationHandler.class,
|
||||
() -> new MeterTagAnnotationHandler(null, null))
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(TimedAspect.class).hasSingleBean(MeterTagAnnotationHandler.class);
|
||||
assertThat(
|
||||
ReflectionTestUtils.getField(context.getBean(TimedAspect.class), "meterTagAnnotationHandler"))
|
||||
.isSameAs(context.getBean("customMeterTagAnnotationHandler"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAutoConfigureCountedMeterTagAnnotationHandlerWhenValueExpressionResolverIsAvailable() {
|
||||
this.contextRunner.withBean(ValueExpressionResolver.class, () -> mock(ValueExpressionResolver.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(CountedAspect.class)
|
||||
.hasSingleBean(CountedMeterTagAnnotationHandler.class);
|
||||
assertThat(
|
||||
ReflectionTestUtils.getField(context.getBean(CountedAspect.class), "meterTagAnnotationHandler"))
|
||||
.isSameAs(context.getBean(CountedMeterTagAnnotationHandler.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseUserDefinedCountedMeterTagAnnotationHandler() {
|
||||
this.contextRunner
|
||||
.withBean("customCountedMeterTagAnnotationHandler", CountedMeterTagAnnotationHandler.class,
|
||||
() -> new CountedMeterTagAnnotationHandler(null, null))
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(CountedAspect.class)
|
||||
.hasSingleBean(CountedMeterTagAnnotationHandler.class);
|
||||
assertThat(
|
||||
ReflectionTestUtils.getField(context.getBean(CountedAspect.class), "meterTagAnnotationHandler"))
|
||||
.isSameAs(context.getBean(CountedMeterTagAnnotationHandler.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotConfigureAspectsIfMicrometerIsMissing() {
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(MeterRegistry.class)).run((context) -> {
|
||||
|
@ -120,14 +164,4 @@ class MetricsAspectsAutoConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class MeterTagAnnotationHandlerConfiguration {
|
||||
|
||||
@Bean
|
||||
MeterTagAnnotationHandler meterTagAnnotationHandler() {
|
||||
return new MeterTagAnnotationHandler(null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.boot.observation.autoconfigure;
|
||||
|
||||
import io.micrometer.common.annotation.ValueExpressionResolver;
|
||||
import io.micrometer.observation.GlobalObservationConvention;
|
||||
import io.micrometer.observation.ObservationFilter;
|
||||
import io.micrometer.observation.ObservationHandler;
|
||||
|
@ -73,6 +74,12 @@ public class ObservationAutoConfiguration {
|
|||
return new PropertiesObservationFilterPredicate(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ValueExpressionResolver.class)
|
||||
SpelValueExpressionResolver spelValueExpressionResolver() {
|
||||
return new SpelValueExpressionResolver();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Advice.class)
|
||||
@ConditionalOnBooleanProperty("management.observations.annotations.enabled")
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 io.micrometer.common.annotation.ValueExpressionResolver;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
|
||||
/**
|
||||
* A {@link Expression SpEL}-based {@link ValueExpressionResolver}.
|
||||
*
|
||||
* @author Dominique Villard
|
||||
*/
|
||||
class SpelValueExpressionResolver implements ValueExpressionResolver {
|
||||
|
||||
@Override
|
||||
public String resolve(String expression, Object parameter) {
|
||||
try {
|
||||
SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
ExpressionParser expressionParser = new SpelExpressionParser();
|
||||
Expression expressionToEvaluate = expressionParser.parseExpression(expression);
|
||||
return expressionToEvaluate.getValue(context, parameter, String.class);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Unable to evaluate SpEL expression '%s'".formatted(expression), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.observation.autoconfigure;
|
|||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
import io.micrometer.common.annotation.ValueExpressionResolver;
|
||||
import io.micrometer.observation.GlobalObservationConvention;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.Observation.Context;
|
||||
|
@ -39,6 +40,7 @@ import org.springframework.context.annotation.Configuration;
|
|||
import org.springframework.core.annotation.Order;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ObservationAutoConfiguration}.
|
||||
|
@ -163,6 +165,18 @@ class ObservationAutoConfigurationTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void autoConfiguresValueExpressionResolver() {
|
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(SpelValueExpressionResolver.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowsUserDefinedValueExpressionResolver() {
|
||||
this.contextRunner.withBean(ValueExpressionResolver.class, () -> mock(ValueExpressionResolver.class))
|
||||
.run((context) -> assertThat(context).hasSingleBean(ValueExpressionResolver.class)
|
||||
.doesNotHaveBean(SpelValueExpressionResolver.class));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ObservationPredicates {
|
||||
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpelValueExpressionResolver}.
|
||||
*
|
||||
* @author Dominique Villard
|
||||
*/
|
||||
class SpelValueExpressionResolverTests {
|
||||
|
||||
final SpelValueExpressionResolver resolver = new SpelValueExpressionResolver();
|
||||
|
||||
@Test
|
||||
void checkValidExpression() {
|
||||
var value = Map.of("foo", Pair.of(1, 2));
|
||||
assertThat(this.resolver.resolve("['foo'].first", value)).isEqualTo("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkInvalidExpression() {
|
||||
var value = Map.of("foo", Pair.of(1, 2));
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve("['bar'].first", value));
|
||||
}
|
||||
|
||||
record Pair(int first, int second) {
|
||||
|
||||
static Pair of(int first, int second) {
|
||||
return new Pair(first, second);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -32,21 +32,19 @@ import io.micrometer.tracing.propagation.Propagator;
|
|||
import org.aspectj.weaver.Advice;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
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.ConditionalOnBooleanProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.observation.autoconfigure.ObservationAutoConfiguration;
|
||||
import org.springframework.boot.observation.autoconfigure.ObservationHandlerGroup;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -56,7 +54,7 @@ import org.springframework.util.ClassUtils;
|
|||
* @author Jonatan Ivanov
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@AutoConfiguration(after = ObservationAutoConfiguration.class)
|
||||
@ConditionalOnBean(Tracer.class)
|
||||
public class MicrometerTracingAutoConfiguration {
|
||||
|
||||
|
@ -122,16 +120,18 @@ public class MicrometerTracingAutoConfiguration {
|
|||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
SpanTagAnnotationHandler spanTagAnnotationHandler(BeanFactory beanFactory) {
|
||||
ValueExpressionResolver valueExpressionResolver = new SpelTagValueExpressionResolver();
|
||||
@ConditionalOnBean(ValueExpressionResolver.class)
|
||||
SpanTagAnnotationHandler spanTagAnnotationHandler(BeanFactory beanFactory,
|
||||
ValueExpressionResolver valueExpressionResolver) {
|
||||
return new SpanTagAnnotationHandler(beanFactory::getBean, (ignored) -> valueExpressionResolver);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(MethodInvocationProcessor.class)
|
||||
ImperativeMethodInvocationProcessor imperativeMethodInvocationProcessor(NewSpanParser newSpanParser,
|
||||
Tracer tracer, SpanTagAnnotationHandler spanTagAnnotationHandler) {
|
||||
return new ImperativeMethodInvocationProcessor(newSpanParser, tracer, spanTagAnnotationHandler);
|
||||
Tracer tracer, ObjectProvider<SpanTagAnnotationHandler> spanTagAnnotationHandler) {
|
||||
return new ImperativeMethodInvocationProcessor(newSpanParser, tracer,
|
||||
spanTagAnnotationHandler.getIfAvailable());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -142,21 +142,4 @@ public class MicrometerTracingAutoConfiguration {
|
|||
|
||||
}
|
||||
|
||||
private static final class SpelTagValueExpressionResolver implements ValueExpressionResolver {
|
||||
|
||||
@Override
|
||||
public String resolve(String expression, Object parameter) {
|
||||
try {
|
||||
SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
|
||||
ExpressionParser expressionParser = new SpelExpressionParser();
|
||||
Expression expressionToEvaluate = expressionParser.parseExpression(expression);
|
||||
return expressionToEvaluate.getValue(context, parameter, String.class);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Unable to evaluate SpEL expression '%s'".formatted(expression), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ class MicrometerTracingAutoConfigurationTests {
|
|||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("management.observations.annotations.enabled=true")
|
||||
.withBean(ValueExpressionResolver.class, () -> mock(ValueExpressionResolver.class))
|
||||
.withConfiguration(AutoConfigurations.of(MicrometerTracingAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue