diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml index 9e458ae80b5..d7dd3b602d7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml @@ -137,6 +137,11 @@ micrometer-registry-statsd true + + io.micrometer + micrometer-registry-wavefront + true + io.projectreactor.ipc reactor-netty 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 new file mode 100644 index 00000000000..e10cedd6251 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.metrics.export.wavefront; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.wavefront.WavefrontConfig; +import io.micrometer.wavefront.WavefrontMeterRegistry; + +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +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.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Wavefront. + * + * @author Jon Schneider + * @since 2.0.0 + */ +@Configuration +@AutoConfigureBefore(SimpleMetricsExportAutoConfiguration.class) +@AutoConfigureAfter(MetricsAutoConfiguration.class) +@ConditionalOnBean(Clock.class) +@ConditionalOnClass(WavefrontMeterRegistry.class) +@ConditionalOnProperty(prefix = "management.metrics.export.wavefront", name = "enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(WavefrontProperties.class) +public class WavefrontMetricsExportAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(WavefrontConfig.class) + public WavefrontConfig wavefrontConfig(WavefrontProperties props) { + return new WavefrontPropertiesConfigAdapter(props); + } + + @Bean(destroyMethod = "stop") + @ConditionalOnMissingBean + public WavefrontMeterRegistry wavefrontMeterRegistry(WavefrontConfig config, + Clock clock) { + return new WavefrontMeterRegistry(config, clock); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontProperties.java new file mode 100644 index 00000000000..bb9fa170ae9 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontProperties.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.metrics.export.wavefront; + +import java.net.URI; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * {@link ConfigurationProperties} for configuring Wavefront metrics export. + * + * @author Jon Schneider + * @since 2.0.0 + */ +@ConfigurationProperties("management.metrics.export.wavefront") +public class WavefrontProperties extends StepRegistryProperties { + + /** + * URI to which metrics are published. May represent a Wavefront sidecar or the + * Wavefront API host. This host could also represent an internal proxy set up in your + * environment that forwards metrics data to the Wavefront API host. + * + * If publishing metrics to a Wavefront proxy (as described in + * https://docs.wavefront.com/proxies_installing.html), the host must be in the + * proxy://HOST:PORT format. + */ + private URI uri; + + /** + * Unique identifier for the app instance that is the source of metrics being + * published to Wavefront. Defaults to the local host name. + */ + private String source; + + /** + * API token used when publishing metrics directly to the Wavefront API host. + */ + private String apiToken; + + /** + * Global prefix to separate metrics originating from this app's white box + * instrumentation from those originating from other Wavefront integrations when + * viewed in the Wavefront UI. + */ + private String globalPrefix; + + public URI getUri() { + return this.uri; + } + + public void setUri(URI uri) { + this.uri = uri; + } + + public String getSource() { + return this.source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getApiToken() { + return this.apiToken; + } + + public void setApiToken(String apiToken) { + this.apiToken = apiToken; + } + + public String getGlobalPrefix() { + return this.globalPrefix; + } + + public void setGlobalPrefix(String globalPrefix) { + this.globalPrefix = globalPrefix; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapter.java new file mode 100644 index 00000000000..d2c6b1ca81d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontPropertiesConfigAdapter.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.metrics.export.wavefront; + +import io.micrometer.wavefront.WavefrontConfig; + +import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PropertiesConfigAdapter; + +/** + * Adapter to convert {@link WavefrontProperties} to a {@link WavefrontConfig}. + * + * @author Jon Schneider + * @since 2.0.0 + */ +public class WavefrontPropertiesConfigAdapter + extends PropertiesConfigAdapter implements WavefrontConfig { + + public WavefrontPropertiesConfigAdapter(WavefrontProperties properties) { + super(properties); + } + + @Override + public String get(String k) { + return null; + } + + @Override + public String uri() { + return get(this::getUriAsString, WavefrontConfig.DEFAULT_DIRECT::uri); + } + + @Override + public String source() { + return get(WavefrontProperties::getSource, WavefrontConfig.super::source); + } + + @Override + public String apiToken() { + return get(WavefrontProperties::getApiToken, WavefrontConfig.super::apiToken); + } + + @Override + public String globalPrefix() { + return get(WavefrontProperties::getGlobalPrefix, + WavefrontConfig.super::globalPrefix); + } + + private String getUriAsString(WavefrontProperties properties) { + return properties.getUri() == null ? null : properties.getUri().toString(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/package-info.java new file mode 100644 index 00000000000..ca4ad6e6bc9 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2018 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 + * + * http://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. + */ + +/** + * Support for exporting actuator metrics to Wavefront. + */ +package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories index a93c06a0285..5d7f8b61137 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories @@ -47,6 +47,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.Prometh org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.export.signalfx.SignalFxMetricsExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.export.statsd.StatsdMetricsExportAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.WavefrontMetricsExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.integration.MetricsIntegrationAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.web.client.RestTemplateMetricsAutoConfiguration,\ 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 new file mode 100644 index 00000000000..c954701f55e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/wavefront/WavefrontMetricsExportAutoConfigurationTests.java @@ -0,0 +1,157 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.metrics.export.wavefront; + +import java.util.Map; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.wavefront.WavefrontConfig; +import io.micrometer.wavefront.WavefrontMeterRegistry; +import org.junit.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +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.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link WavefrontMetricsExportAutoConfiguration}. + * + * @author Jon Schneider + */ +public class WavefrontMetricsExportAutoConfigurationTests { + + private final ApplicationContextRunner runner = new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(WavefrontMetricsExportAutoConfiguration.class)); + + @Test + public void backsOffWithoutAClock() { + this.runner.run((context) -> assertThat(context) + .doesNotHaveBean(WavefrontMeterRegistry.class)); + } + + @Test + public void failsWithoutAnApiTokenWhenPublishingDirectly() { + this.runner.withUserConfiguration(BaseConfiguration.class) + .run((context) -> assertThat(context).hasFailed()); + } + + @Test + public void autoConfigurationCanBeDisabled() { + this.runner.withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.metrics.export.wavefront.enabled=false") + .run((context) -> assertThat(context) + .doesNotHaveBean(WavefrontMeterRegistry.class) + .doesNotHaveBean(WavefrontConfig.class)); + } + + @Test + public void allowsConfigToBeCustomized() { + this.runner.withUserConfiguration(CustomConfigConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(Clock.class) + .hasSingleBean(WavefrontMeterRegistry.class) + .hasSingleBean(WavefrontConfig.class).hasBean("customConfig")); + } + + @Test + public void allowsRegistryToBeCustomized() { + this.runner.withUserConfiguration(CustomRegistryConfiguration.class) + .withPropertyValues("management.metrics.export.wavefront.api-token=abcde") + .run((context) -> assertThat(context).hasSingleBean(Clock.class) + .hasSingleBean(WavefrontConfig.class) + .hasSingleBean(WavefrontMeterRegistry.class) + .hasBean("customRegistry")); + } + + @Test + public void stopsMeterRegistryWhenContextIsClosed() { + this.runner.withUserConfiguration(BaseConfiguration.class) + .withPropertyValues("management.metrics.export.wavefront.api-token=abcde") + .run((context) -> { + WavefrontMeterRegistry registry = spyOnDisposableBean( + WavefrontMeterRegistry.class, context); + context.close(); + verify(registry).stop(); + }); + } + + @SuppressWarnings("unchecked") + private T spyOnDisposableBean(Class type, + AssertableApplicationContext context) { + String[] names = context.getBeanNamesForType(type); + assertThat(names).hasSize(1); + String registryBeanName = names[0]; + Map disposableBeans = (Map) ReflectionTestUtils + .getField(context.getAutowireCapableBeanFactory(), "disposableBeans"); + Object registryAdapter = disposableBeans.get(registryBeanName); + T registry = (T) spy(ReflectionTestUtils.getField(registryAdapter, "bean")); + ReflectionTestUtils.setField(registryAdapter, "bean", registry); + return registry; + } + + @Configuration + static class BaseConfiguration { + + @Bean + public Clock clock() { + return Clock.SYSTEM; + } + + } + + @Configuration + @Import(BaseConfiguration.class) + static class CustomConfigConfiguration { + + @Bean + public WavefrontConfig customConfig() { + return new WavefrontConfig() { + @Override + public String get(String key) { + return null; + } + + @Override + public String uri() { + return WavefrontConfig.DEFAULT_PROXY.uri(); + } + }; + } + + } + + @Configuration + @Import(BaseConfiguration.class) + static class CustomRegistryConfiguration { + + @Bean(destroyMethod = "stop") + public WavefrontMeterRegistry customRegistry(WavefrontConfig config, + Clock clock) { + return new WavefrontMeterRegistry(config, clock); + } + + } + +} diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index 4e5411b96b9..c07c4908aa8 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -902,6 +902,11 @@ micrometer-registry-statsd ${micrometer.version} + + io.micrometer + micrometer-registry-wavefront + ${micrometer.version} + io.netty netty-bom diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 893faa39b20..051ee33b317 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -1391,6 +1391,11 @@ content into your application. Rather, pick only the properties that you need. management.metrics.export.statsd.polling-frequency=10s # How often gauges will be polled. When a gauge is polled, its value is recalculated and if the value has changed, it is sent to the StatsD server. management.metrics.export.statsd.port=8125 # Port of the StatsD server to receive exported metrics. management.metrics.export.statsd.queue-size=2147483647 # Maximum size of the queue of items waiting to be sent to the StatsD server. + management.metrics.export.wavefront.api-token= # API token used when publishing metrics directly to the Wavefront API host. + management.metrics.export.wavefront.enabled= # Whether exporting of metrics to this backend is enabled. + management.metrics.export.wavefront.global-prefix= # Global prefix to separate metrics originating from this app's white box instrumentation from those originating from other Wavefront integrations when viewed in the Wavefront UI. + management.metrics.export.wavefront.source= # Unique identifier for the app instance that is the source of metrics being published to Wavefront. Defaults to the local host name. + management.metrics.export.wavefront.uri= # URI to which metrics are published. May represent a Wavefront sidecar or the Wavefront API host. This host could also represent an internal proxy set up in your environment that forwards metrics data to the Wavefront API host. management.metrics.use-global-registry=true # Whether auto-configured MeterRegistry implementations should be bound to the global static registry on Metrics. management.metrics.web.client.max-uri-tags=100 # Maximum number of unique URI tag values allowed. After the max number of tag values is reached, metrics with additional tag values are denied by filter. management.metrics.web.client.record-request-percentiles=false # Whether instrumented requests record percentiles histogram buckets by default.