diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfiguration.java index 470d4f919a..4204950b72 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfiguration.java @@ -16,12 +16,18 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; +import java.util.Map; + import com.wavefront.sdk.common.WavefrontSender; +import com.wavefront.sdk.common.application.ApplicationTags; import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; import io.micrometer.wavefront.WavefrontConfig; import io.micrometer.wavefront.WavefrontMeterRegistry; import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; @@ -42,6 +48,7 @@ import org.springframework.context.annotation.Import; * @author Jon Schneider * @author Artsiom Yudovin * @author Stephane Nicoll + * @author Glenn Oppegard * @since 2.0.0 */ @AutoConfiguration( @@ -68,4 +75,16 @@ public class WavefrontMetricsExportAutoConfiguration { return WavefrontMeterRegistry.builder(wavefrontConfig).clock(clock).wavefrontSender(wavefrontSender).build(); } + @Bean + @ConditionalOnBean(ApplicationTags.class) + MeterRegistryCustomizer wavefrontApplicationTagsCustomizer( + ApplicationTags wavefrontApplicationTags) { + Tags commonTags = Tags.of(wavefrontApplicationTags.toPointTags().entrySet().stream().map(this::asTag).toList()); + return (registry) -> registry.config().commonTags(commonTags); + } + + private Tag asTag(Map.Entry entry) { + return Tag.of(entry.getKey(), entry.getValue()); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java index 4b86760af2..a07fb4217d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.tracing.wavefront; import java.util.Collections; +import java.util.function.Supplier; import brave.handler.SpanHandler; import com.wavefront.sdk.common.WavefrontSender; @@ -40,15 +41,18 @@ 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.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for Wavefront tracing. * * @author Moritz Halbritter + * @author Glenn Oppegard * @since 3.0.0 */ @AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class }) @@ -59,19 +63,38 @@ import org.springframework.core.env.Environment; public class WavefrontTracingAutoConfiguration { /** - * Default value for application name if {@code spring.application.name} is not set. + * Default value for the Wavefront Application name. + * @see Wavefront + * Application Tags */ - private static final String DEFAULT_APPLICATION_NAME = "application"; + private static final String DEFAULT_APPLICATION_NAME = "unnamed_application"; + + /** + * Default value for the Wavefront Service name if {@code spring.application.name} is + * not set. + * @see Wavefront + * Application Tags + */ + private static final String DEFAULT_SERVICE_NAME = "unnamed_service"; @Bean @ConditionalOnMissingBean - public ApplicationTags applicationTags(Environment environment, WavefrontProperties properties) { - String springApplicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME); + public ApplicationTags wavefrontApplicationTags(Environment environment, WavefrontProperties properties) { Tracing tracing = properties.getTracing(); - String applicationName = (tracing.getApplicationName() != null) ? tracing.getApplicationName() - : springApplicationName; - String serviceName = (tracing.getServiceName() != null) ? tracing.getServiceName() : springApplicationName; - return new ApplicationTags.Builder(applicationName, serviceName).build(); + String wavefrontServiceName = getName(tracing.getServiceName(), + () -> environment.getProperty("spring.application.name", DEFAULT_SERVICE_NAME)); + String wavefrontApplicationName = getName(tracing.getApplicationName(), () -> DEFAULT_APPLICATION_NAME); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + ApplicationTags.Builder builder = new ApplicationTags.Builder(wavefrontApplicationName, wavefrontServiceName); + map.from(tracing::getClusterName).to(builder::cluster); + map.from(tracing::getShardName).to(builder::shard); + return builder.build(); + } + + private String getName(String value, Supplier fallback) { + return (StringUtils.hasText(value)) ? value : fallback.get(); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java index 37a963d624..4d8fac75fa 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/wavefront/WavefrontProperties.java @@ -30,6 +30,7 @@ import org.springframework.util.unit.DataSize; * Configuration properties to configure Wavefront. * * @author Moritz Halbritter + * @author Glenn Oppegard * @since 3.0.0 */ @ConfigurationProperties(prefix = "management.wavefront") @@ -261,15 +262,27 @@ public class WavefrontProperties { public static class Tracing { /** - * Application name. Defaults to 'spring.application.name'. + * Wavefront Application name used in ApplicationTags. Defaults to + * 'unnamed_application'. */ private String applicationName; /** - * Service name. Defaults to 'spring.application.name'. + * Wavefront Service name used in ApplicationTags, falling back to + * 'spring.application.name'. If both are unset it defaults to 'unnamed_service'. */ private String serviceName; + /** + * Optional Wavefront Cluster name used in ApplicationTags. + */ + private String clusterName; + + /** + * Optional Wavefront Shard name used in ApplicationTags. + */ + private String shardName; + public String getServiceName() { return this.serviceName; } @@ -286,6 +299,22 @@ public class WavefrontProperties { this.applicationName = applicationName; } + public String getClusterName() { + return this.clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + public String getShardName() { + return this.shardName; + } + + public void setShardName(String shardName) { + this.shardName = shardName; + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfigurationTests.java index d8901202f0..28efcc5463 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfigurationTests.java @@ -16,12 +16,16 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; +import java.util.Map; + import com.wavefront.sdk.common.WavefrontSender; +import com.wavefront.sdk.common.application.ApplicationTags; import io.micrometer.core.instrument.Clock; import io.micrometer.wavefront.WavefrontConfig; import io.micrometer.wavefront.WavefrontMeterRegistry; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -36,6 +40,7 @@ import static org.mockito.Mockito.mock; * * @author Jon Schneider * @author Stephane Nicoll + * @author Glenn Oppegard */ class WavefrontMetricsExportAutoConfigurationTests { @@ -81,6 +86,23 @@ class WavefrontMetricsExportAutoConfigurationTests { .hasSingleBean(WavefrontMeterRegistry.class).hasBean("customRegistry")); } + @Test + void exportsApplicationTagsInWavefrontRegistry() { + ApplicationTags.Builder builder = new ApplicationTags.Builder("super-application", "super-service"); + builder.cluster("super-cluster"); + builder.shard("super-shard"); + builder.customTags(Map.of("custom-key", "custom-val")); + this.contextRunner.withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) + .withUserConfiguration(BaseConfiguration.class).withBean(ApplicationTags.class, builder::build) + .run((context) -> { + WavefrontMeterRegistry registry = context.getBean(WavefrontMeterRegistry.class); + registry.counter("my.counter", "env", "qa"); + assertThat(registry.find("my.counter").tags("env", "qa").tags("application", "super-application") + .tags("service", "super-service").tags("cluster", "super-cluster") + .tags("shard", "super-shard").tags("custom-key", "custom-val").counter()).isNotNull(); + }); + } + @Test void stopsMeterRegistryWhenContextIsClosed() { this.contextRunner.withUserConfiguration(BaseConfiguration.class) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java index 2999861b24..c4926b7e95 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/wavefront/WavefrontTracingAutoConfigurationTests.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link WavefrontTracingAutoConfiguration}. * * @author Moritz Halbritter + * @author Glenn Oppegard */ class WavefrontTracingAutoConfigurationTests { @@ -114,22 +115,40 @@ class WavefrontTracingAutoConfigurationTests { } @Test - void shouldHaveADefaultApplicationName() { + void shouldHaveADefaultApplicationNameAndServiceName() { this.contextRunner.withUserConfiguration(WavefrontSenderConfiguration.class).run((context) -> { ApplicationTags applicationTags = context.getBean(ApplicationTags.class); - assertThat(applicationTags.getApplication()).isEqualTo("application"); + assertThat(applicationTags.getApplication()).isEqualTo("unnamed_application"); + assertThat(applicationTags.getService()).isEqualTo("unnamed_service"); + assertThat(applicationTags.getCluster()).isNull(); + assertThat(applicationTags.getShard()).isNull(); }); } + @Test + void shouldUseSpringApplicationNameForServiceName() { + this.contextRunner.withUserConfiguration(WavefrontSenderConfiguration.class) + .withPropertyValues("spring.application.name=super-service").run((context) -> { + ApplicationTags applicationTags = context.getBean(ApplicationTags.class); + assertThat(applicationTags.getApplication()).isEqualTo("unnamed_application"); + assertThat(applicationTags.getService()).isEqualTo("super-service"); + }); + } + @Test void shouldHonorConfigProperties() { this.contextRunner.withUserConfiguration(WavefrontSenderConfiguration.class) - .withPropertyValues("spring.application.name=super-application", - "management.wavefront.tracing.service-name=super-service") + .withPropertyValues("spring.application.name=ignored", + "management.wavefront.tracing.application-name=super-application", + "management.wavefront.tracing.service-name=super-service", + "management.wavefront.tracing.cluster-name=super-cluster", + "management.wavefront.tracing.shard-name=super-shard") .run((context) -> { ApplicationTags applicationTags = context.getBean(ApplicationTags.class); assertThat(applicationTags.getApplication()).isEqualTo("super-application"); assertThat(applicationTags.getService()).isEqualTo("super-service"); + assertThat(applicationTags.getCluster()).isEqualTo("super-cluster"); + assertThat(applicationTags.getShard()).isEqualTo("super-shard"); }); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java index b0b7154a64..69ec3bb95e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java @@ -16,9 +16,6 @@ package org.springframework.boot.context.properties.bind; -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -34,14 +31,14 @@ import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.beans.BeanInfoFactory; -import org.springframework.beans.ExtendedBeanInfoFactory; +import org.springframework.beans.BeanUtils; import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ResolvableType; import org.springframework.core.StandardReflectionParameterNameDiscoverer; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; /** @@ -59,8 +56,6 @@ import org.springframework.util.ReflectionUtils; */ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { - private static final BeanInfoFactory beanInfoFactory = new ExtendedBeanInfoFactory(); - private final Class[] types; /** @@ -120,7 +115,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { private final Constructor bindConstructor; - private final BeanInfo beanInfo; + private final PropertyDescriptor[] propertyDescriptors; private final Set> seen; @@ -134,24 +129,11 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { Set> compiledWithoutParameters) { this.type = type; this.bindConstructor = BindConstructorProvider.DEFAULT.getBindConstructor(Bindable.of(type), nestedType); - this.beanInfo = getBeanInfo(type); + this.propertyDescriptors = BeanUtils.getPropertyDescriptors(type); this.seen = seen; this.compiledWithoutParameters = compiledWithoutParameters; } - private static BeanInfo getBeanInfo(Class beanType) { - try { - BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanType); - if (beanInfo != null) { - return beanInfo; - } - return Introspector.getBeanInfo(beanType, Introspector.IGNORE_ALL_BEANINFO); - } - catch (IntrospectionException ex) { - return null; - } - } - void process(ReflectionHints hints) { if (this.seen.contains(this.type)) { return; @@ -161,7 +143,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { if (this.bindConstructor != null) { handleValueObjectProperties(hints); } - else if (this.beanInfo != null) { + else if (!ObjectUtils.isEmpty(this.propertyDescriptors)) { handleJavaBeanProperties(hints); } } @@ -196,7 +178,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { } private void handleJavaBeanProperties(ReflectionHints hints) { - for (PropertyDescriptor propertyDescriptor : this.beanInfo.getPropertyDescriptors()) { + for (PropertyDescriptor propertyDescriptor : this.propertyDescriptors) { Method writeMethod = propertyDescriptor.getWriteMethod(); if (writeMethod != null) { hints.registerMethod(writeMethod, ExecutableMode.INVOKE);