Commit 5f2517f8 authored by Stephane Nicoll's avatar Stephane Nicoll

Auto-configure AppOptics metrics

Closes gh-14819
parent 3dc74fea
......@@ -97,6 +97,11 @@
<artifactId>micrometer-jersey2</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-appoptics</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-atlas</artifactId>
......
/*
* 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.appoptics;
import io.micrometer.appoptics.AppOpticsConfig;
import io.micrometer.appoptics.AppOpticsMeterRegistry;
import io.micrometer.core.instrument.Clock;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
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 AppOptics.
*
* @author Stephane Nicoll
* @since 2.1.0
*/
@Configuration
@AutoConfigureBefore({ CompositeMeterRegistryAutoConfiguration.class,
SimpleMetricsExportAutoConfiguration.class })
@AutoConfigureAfter(MetricsAutoConfiguration.class)
@ConditionalOnBean(Clock.class)
@ConditionalOnClass(AppOpticsMeterRegistry.class)
@ConditionalOnProperty(prefix = "management.metrics.export.appoptics", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(AppOpticsProperties.class)
public class AppOpticsMetricsExportAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AppOpticsConfig appOpticsConfig(AppOpticsProperties appOpticsProperties) {
return new AppOpticsPropertiesConfigAdapter(appOpticsProperties);
}
@Bean
@ConditionalOnMissingBean
public AppOpticsMeterRegistry appOpticsMeterRegistry(AppOpticsConfig config,
Clock clock) {
return new AppOpticsMeterRegistry(config, clock);
}
}
/*
* 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.appoptics;
import java.time.Duration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* {@link ConfigurationProperties} for configuring AppOptics metrics export.
*
* @author Stephane Nicoll
* @since 2.1.0
*/
@ConfigurationProperties(prefix = "management.metrics.export.appoptics")
public class AppOpticsProperties extends StepRegistryProperties {
/**
* URI to ship metrics to.
*/
public String uri = "https://api.appoptics.com/v1/measurements";
/**
* AppOptics API token.
*/
public String apiToken;
/**
* Tag that will be mapped to "@host" when shipping metrics to AppOptics.
*/
public String hostTag = "instance";
/**
* Number of measurements per request to use for this backend. If more measurements
* are found, then multiple requests will be made.
*/
private Integer batchSize = 500;
/**
* Connection timeout for requests to this backend.
*/
private Duration connectTimeout = Duration.ofSeconds(5);
public String getUri() {
return this.uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getApiToken() {
return this.apiToken;
}
public void setApiToken(String apiToken) {
this.apiToken = apiToken;
}
public String getHostTag() {
return this.hostTag;
}
public void setHostTag(String hostTag) {
this.hostTag = hostTag;
}
@Override
public Integer getBatchSize() {
return this.batchSize;
}
@Override
public void setBatchSize(Integer batchSize) {
this.batchSize = batchSize;
}
@Override
public Duration getConnectTimeout() {
return this.connectTimeout;
}
@Override
public void setConnectTimeout(Duration connectTimeout) {
this.connectTimeout = connectTimeout;
}
}
/*
* 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.appoptics;
import io.micrometer.appoptics.AppOpticsConfig;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter;
/**
* Adapter to convert {@link AppOpticsProperties} to an {@link AppOpticsConfig}.
*
* @author Stephane Nicoll
*/
class AppOpticsPropertiesConfigAdapter
extends StepRegistryPropertiesConfigAdapter<AppOpticsProperties>
implements AppOpticsConfig {
AppOpticsPropertiesConfigAdapter(AppOpticsProperties properties) {
super(properties);
}
public String uri() {
return get(AppOpticsProperties::getUri, AppOpticsConfig.super::uri);
}
public String token() {
return get(AppOpticsProperties::getApiToken, AppOpticsConfig.super::token);
}
public String hostTag() {
return get(AppOpticsProperties::getHostTag, AppOpticsConfig.super::hostTag);
}
}
/*
* 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 AppOptics.
*/
package org.springframework.boot.actuate.autoconfigure.metrics.export.appoptics;
......@@ -46,6 +46,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.MetricsEndpointAutoConfig
org.springframework.boot.actuate.autoconfigure.metrics.SystemMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.amqp.RabbitMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.cache.CacheMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.export.appoptics.AppOpticsMetricsExportAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.export.atlas.AtlasMetricsExportAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.export.datadog.DatadogMetricsExportAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.export.dynatrace.DynatraceMetricsExportAutoConfiguration,\
......
/*
* 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.appoptics;
import java.util.Map;
import io.micrometer.appoptics.AppOpticsConfig;
import io.micrometer.appoptics.AppOpticsMeterRegistry;
import io.micrometer.core.instrument.Clock;
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 AppOpticsMetricsExportAutoConfiguration}.
*
* @author Stephane Nicoll
*/
public class AppOpticsMetricsExportAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(AppOpticsMetricsExportAutoConfiguration.class));
@Test
public void backsOffWithoutAClock() {
this.contextRunner.run((context) -> assertThat(context)
.doesNotHaveBean(AppOpticsMeterRegistry.class));
}
@Test
public void autoConfiguresItsConfigAndMeterRegistry() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class)
.run((context) -> assertThat(context)
.hasSingleBean(AppOpticsMeterRegistry.class)
.hasSingleBean(AppOpticsConfig.class));
}
@Test
public void autoConfigurationCanBeDisabled() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class)
.withPropertyValues("management.metrics.export.appoptics.enabled=false")
.run((context) -> assertThat(context)
.doesNotHaveBean(AppOpticsMeterRegistry.class)
.doesNotHaveBean(AppOpticsConfig.class));
}
@Test
public void allowsCustomConfigToBeUsed() {
this.contextRunner.withUserConfiguration(CustomConfigConfiguration.class)
.run((context) -> assertThat(context)
.hasSingleBean(AppOpticsMeterRegistry.class)
.hasSingleBean(AppOpticsConfig.class).hasBean("customConfig"));
}
@Test
public void allowsCustomRegistryToBeUsed() {
this.contextRunner.withUserConfiguration(CustomRegistryConfiguration.class)
.run((context) -> assertThat(context)
.hasSingleBean(AppOpticsMeterRegistry.class)
.hasBean("customRegistry").hasSingleBean(AppOpticsConfig.class));
}
@Test
public void stopsMeterRegistryWhenContextIsClosed() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class)
.run((context) -> {
AppOpticsMeterRegistry registry = spyOnDisposableBean(
AppOpticsMeterRegistry.class, context);
context.close();
verify(registry).stop();
});
}
@SuppressWarnings("unchecked")
private <T> T spyOnDisposableBean(Class<T> type,
AssertableApplicationContext context) {
String[] names = context.getBeanNamesForType(type);
assertThat(names).hasSize(1);
String registryBeanName = names[0];
Map<String, Object> disposableBeans = (Map<String, Object>) 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 AppOpticsConfig customConfig() {
return (key) -> null;
}
}
@Configuration
@Import(BaseConfiguration.class)
static class CustomRegistryConfiguration {
@Bean
public AppOpticsMeterRegistry customRegistry(AppOpticsConfig config,
Clock clock) {
return new AppOpticsMeterRegistry(config, clock);
}
}
}
/*
* 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.appoptics;
import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapterTests;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AppOpticsPropertiesConfigAdapter}.
*
* @author Stephane Nicoll
*/
public class AppOpticsPropertiesConfigAdapterTests extends
StepRegistryPropertiesConfigAdapterTests<AppOpticsProperties, AppOpticsPropertiesConfigAdapter> {
@Override
protected AppOpticsProperties createProperties() {
return new AppOpticsProperties();
}
@Override
protected AppOpticsPropertiesConfigAdapter createConfigAdapter(
AppOpticsProperties properties) {
return new AppOpticsPropertiesConfigAdapter(properties);
}
@Test
public void whenPropertiesUrisIsSetAdapterUriReturnsIt() {
AppOpticsProperties properties = createProperties();
properties.setUri("https://appoptics.example.com/v1/measurements");
assertThat(createConfigAdapter(properties).uri())
.isEqualTo("https://appoptics.example.com/v1/measurements");
}
@Test
public void whenPropertiesApiTokenIsSetAdapterApiTokenReturnsIt() {
AppOpticsProperties properties = createProperties();
properties.setApiToken("ABC123");
assertThat(createConfigAdapter(properties).token()).isEqualTo("ABC123");
}
@Test
public void whenPropertiesHostTagIsSetAdapterHostTagReturnsIt() {
AppOpticsProperties properties = createProperties();
properties.setHostTag("node");
assertThat(createConfigAdapter(properties).hostTag()).isEqualTo("node");
}
}
/*
* 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.appoptics;
import io.micrometer.appoptics.AppOpticsConfig;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesTests;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AppOpticsProperties}.
*
* @author Stephane Nicoll
*/
public class AppOpticsPropertiesTests extends StepRegistryPropertiesTests {
@Override
public void defaultValuesAreConsistent() {
AppOpticsProperties properties = new AppOpticsProperties();
AppOpticsConfig config = (key) -> null;
assertStepRegistryDefaultValues(properties, config);
assertThat(properties.getUri()).isEqualToIgnoringWhitespace(config.uri());
assertThat(properties.getHostTag()).isEqualToIgnoringWhitespace(config.hostTag());
}
}
......@@ -206,6 +206,11 @@
<artifactId>micrometer-jersey2</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-appoptics</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-atlas</artifactId>
......
......@@ -1385,6 +1385,15 @@ content into your application. Rather, pick only the properties that you need.
management.metrics.distribution.percentiles-histogram.*= # Whether meter IDs starting with the specified name should publish percentile histograms.
management.metrics.distribution.sla.*= # Specific SLA boundaries for meter IDs starting-with the specified name. The longest match wins.
management.metrics.enable.*= # Whether meter IDs starting-with the specified name should be enabled. The longest match wins, the key `all` can also be used to configure all meters.
management.metrics.export.appoptics.api-token= # AppOptics API token.
management.metrics.export.appoptics.batch-size=500 # Number of measurements per request to use for this backend. If more measurements are found, then multiple requests will be made.
management.metrics.export.appoptics.connect-timeout=5s # Connection timeout for requests to this backend.
management.metrics.export.appoptics.enabled=true # Whether exporting of metrics to this backend is enabled.
management.metrics.export.appoptics.host-tag=instance # Tag that will be mapped to "@host" when shipping metrics to AppOptics.
management.metrics.export.appoptics.num-threads=2 # Number of threads to use with the metrics publishing scheduler.
management.metrics.export.appoptics.read-timeout=10s # Read timeout for requests to this backend.
management.metrics.export.appoptics.step=1m # Step size (i.e. reporting frequency) to use.
management.metrics.export.appoptics.uri=https://api.appoptics.com/v1/measurements # URI to ship metrics to.
management.metrics.export.atlas.batch-size=10000 # Number of measurements per request to use for this backend. If more measurements are found, then multiple requests will be made.
management.metrics.export.atlas.config-refresh-frequency=10s # Frequency for refreshing config settings from the LWC service.
management.metrics.export.atlas.config-time-to-live=150s # Time to live for subscriptions from the LWC service.
......
......@@ -1344,6 +1344,7 @@ Spring Boot Actuator provides dependency management and auto-configuration for
https://micrometer.io[Micrometer], an application metrics facade that supports numerous
monitoring systems, including:
- <<production-ready-metrics-export-appoptics,AppOptics>>
- <<production-ready-metrics-export-atlas,Atlas>>
- <<production-ready-metrics-export-datadog,Datadog>>
- <<production-ready-metrics-export-dynatrace,Dynatrace>>
......@@ -1433,6 +1434,19 @@ annotation markers.
[[production-ready-metrics-export-appoptics]]
==== AppOptics
By default, the AppOptics registry pushes metrics to https://www.appoptics.com/
periodically. To export metrics to SaaS
{micrometer-registry-documentation}/appoptics[AppOptics], your API token must be provided:
[source,properties,indent=0]
----
management.metrics.export.appoptics.api-token=YOUR_TOKEN
----
[[production-ready-metrics-export-atlas]]
==== Atlas
By default, metrics are exported to {micrometer-registry-documentation}/atlas[Atlas]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment