From 68b58f3fc24a663f234509be28cd9275a2e0ca7b Mon Sep 17 00:00:00 2001 From: Jonatan Ivanov Date: Tue, 14 Oct 2025 15:48:01 -0700 Subject: [PATCH] Add support for @ObservationKeyValue See https://github.com/micrometer-metrics/micrometer/issues/4030 --- .../ObservationAutoConfiguration.java | 16 +++++++-- .../ObservationAutoConfigurationTests.java | 36 ++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/module/spring-boot-micrometer-observation/src/main/java/org/springframework/boot/micrometer/observation/autoconfigure/ObservationAutoConfiguration.java b/module/spring-boot-micrometer-observation/src/main/java/org/springframework/boot/micrometer/observation/autoconfigure/ObservationAutoConfiguration.java index 630ec7df5e05..f52a594c5d81 100644 --- a/module/spring-boot-micrometer-observation/src/main/java/org/springframework/boot/micrometer/observation/autoconfigure/ObservationAutoConfiguration.java +++ b/module/spring-boot-micrometer-observation/src/main/java/org/springframework/boot/micrometer/observation/autoconfigure/ObservationAutoConfiguration.java @@ -22,9 +22,11 @@ import io.micrometer.observation.ObservationHandler; import io.micrometer.observation.ObservationPredicate; import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservationKeyValueAnnotationHandler; import io.micrometer.observation.aop.ObservedAspect; 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; @@ -87,8 +89,18 @@ static class ObservedAspectConfiguration { @Bean @ConditionalOnMissingBean - ObservedAspect observedAspect(ObservationRegistry observationRegistry) { - return new ObservedAspect(observationRegistry); + ObservedAspect observedAspect(ObservationRegistry observationRegistry, + ObjectProvider observationKeyValueAnnotationHandler) { + ObservedAspect observedAspect = new ObservedAspect(observationRegistry); + observationKeyValueAnnotationHandler.ifAvailable(observedAspect::setObservationKeyValueAnnotationHandler); + return observedAspect; + } + + @Bean + @ConditionalOnMissingBean + ObservationKeyValueAnnotationHandler observationKeyValueAnnotationHandler(BeanFactory beanFactory, + ValueExpressionResolver valueExpressionResolver) { + return new ObservationKeyValueAnnotationHandler(beanFactory::getBean, (ignored) -> valueExpressionResolver); } } diff --git a/module/spring-boot-micrometer-observation/src/test/java/org/springframework/boot/micrometer/observation/autoconfigure/ObservationAutoConfigurationTests.java b/module/spring-boot-micrometer-observation/src/test/java/org/springframework/boot/micrometer/observation/autoconfigure/ObservationAutoConfigurationTests.java index 37d67abccd03..6a5e0371997c 100644 --- a/module/spring-boot-micrometer-observation/src/test/java/org/springframework/boot/micrometer/observation/autoconfigure/ObservationAutoConfigurationTests.java +++ b/module/spring-boot-micrometer-observation/src/test/java/org/springframework/boot/micrometer/observation/autoconfigure/ObservationAutoConfigurationTests.java @@ -28,10 +28,12 @@ import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler; import io.micrometer.observation.ObservationPredicate; import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservationKeyValueAnnotationHandler; import io.micrometer.observation.aop.ObservedAspect; import org.aspectj.weaver.Advice; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -61,36 +63,48 @@ void beansShouldNotBeSuppliedWhenMicrometerObservationIsNotOnClassPath() { this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.observation")).run((context) -> { assertThat(context).doesNotHaveBean(ObservationRegistry.class); assertThat(context).doesNotHaveBean(ObservedAspect.class); + assertThat(context).doesNotHaveBean(ObservationKeyValueAnnotationHandler.class); }); } @Test - void supplyObservationRegistry() { + void supplyObservationRegistryAndAspect() { this.contextRunner.run((context) -> { ObservationRegistry observationRegistry = context.getBean(ObservationRegistry.class); Observation.start("test-observation", observationRegistry).stop(); assertThat(context).hasSingleBean(ObservedAspect.class); + assertThat(context).hasSingleBean(ObservationKeyValueAnnotationHandler.class); + assertThat(context.getBean(ObservedAspect.class)).extracting("observationKeyValueAnnotationHandler") + .isSameAs(context.getBean(ObservationKeyValueAnnotationHandler.class)); }); } @Test void allowsObservedAspectToBeDisabled() { - this.contextRunner.withClassLoader(new FilteredClassLoader(Advice.class)) - .run((context) -> assertThat(context).doesNotHaveBean(ObservedAspect.class)); + this.contextRunner.withClassLoader(new FilteredClassLoader(Advice.class)).run((context) -> { + assertThat(context).doesNotHaveBean(ObservedAspect.class); + assertThat(context).doesNotHaveBean(ObservationKeyValueAnnotationHandler.class); + }); } @Test void allowsObservedAspectToBeDisabledWithProperty() { - this.contextRunner.withPropertyValues("management.observations.annotations.enabled=false") - .run((context) -> assertThat(context).doesNotHaveBean(ObservedAspect.class)); + this.contextRunner.withPropertyValues("management.observations.annotations.enabled=false").run((context) -> { + assertThat(context).doesNotHaveBean(ObservedAspect.class); + assertThat(context).doesNotHaveBean(ObservationKeyValueAnnotationHandler.class); + }); } @Test void allowsObservedAspectToBeCustomized() { - this.contextRunner.withUserConfiguration(CustomObservedAspectConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(ObservedAspect.class) + this.contextRunner.withUserConfiguration(CustomObservedAspectConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(ObservedAspect.class) .getBean(ObservedAspect.class) - .isSameAs(context.getBean("customObservedAspect"))); + .isSameAs(context.getBean("customObservedAspect")); + assertThat(context).hasSingleBean(ObservationKeyValueAnnotationHandler.class) + .getBean(ObservationKeyValueAnnotationHandler.class) + .isSameAs(context.getBean("customObservationKeyValueAnnotationHandler")); + }); } @Test @@ -216,6 +230,12 @@ ObservedAspect customObservedAspect(ObservationRegistry observationRegistry) { return new ObservedAspect(observationRegistry); } + @Bean + ObservationKeyValueAnnotationHandler customObservationKeyValueAnnotationHandler(BeanFactory beanFactory, + ValueExpressionResolver valueExpressionResolver) { + return new ObservationKeyValueAnnotationHandler(beanFactory::getBean, (ignored) -> valueExpressionResolver); + } + } @Configuration(proxyBeanMethods = false)