Merge pull request #26258 from pirgeo

* gh-26258:
  Polish "Refine documentation"
  Refine documentation
  Polish "Add properties for Dynatrace metrics API v2 ingest with Micrometer"
  Add properties for Dynatrace metrics API v2 ingest with Micrometer

Closes gh-26258
This commit is contained in:
Andy Wilkinson 2021-07-16 17:39:37 +01:00
commit d60191add2
7 changed files with 349 additions and 39 deletions

View File

@ -16,47 +16,38 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.dynatrace;
import java.util.Map;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
/**
* {@link ConfigurationProperties @ConfigurationProperties} for configuring Dynatrace
* metrics export.
*
* @author Andy Wilkinson
* @author Georg Pirklbauer
* @since 2.1.0
*/
@ConfigurationProperties(prefix = "management.metrics.export.dynatrace")
public class DynatraceProperties extends StepRegistryProperties {
private final V1 v1 = new V1();
private final V2 v2 = new V2();
/**
* Dynatrace authentication token.
*/
private String apiToken;
/**
* ID of the custom device that is exporting metrics to Dynatrace.
*/
private String deviceId;
/**
* Technology type for exported metrics. Used to group metrics under a logical
* technology name in the Dynatrace UI.
*/
private String technologyType = "java";
/**
* URI to ship metrics to. Should be used for SaaS, self managed instances or to
* en-route through an internal proxy.
*/
private String uri;
/**
* Group for exported metrics. Used to specify custom device group name in the
* Dynatrace UI.
*/
private String group;
public String getApiToken() {
return this.apiToken;
}
@ -65,20 +56,26 @@ public class DynatraceProperties extends StepRegistryProperties {
this.apiToken = apiToken;
}
@Deprecated
@DeprecatedConfigurationProperty(replacement = "management.metrics.export.dynatrace.v1.device-id")
public String getDeviceId() {
return this.deviceId;
return this.v1.getDeviceId();
}
@Deprecated
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
this.v1.setDeviceId(deviceId);
}
@Deprecated
@DeprecatedConfigurationProperty(replacement = "management.metrics.export.dynatrace.v1.technology-type")
public String getTechnologyType() {
return this.technologyType;
return this.v1.getTechnologyType();
}
@Deprecated
public void setTechnologyType(String technologyType) {
this.technologyType = technologyType;
this.v1.setTechnologyType(technologyType);
}
public String getUri() {
@ -89,12 +86,112 @@ public class DynatraceProperties extends StepRegistryProperties {
this.uri = uri;
}
@Deprecated
@DeprecatedConfigurationProperty(replacement = "management.metrics.export.dynatrace.v1.group")
public String getGroup() {
return this.group;
return this.v1.getGroup();
}
@Deprecated
public void setGroup(String group) {
this.group = group;
this.v1.setGroup(group);
}
public V1 getV1() {
return this.v1;
}
public V2 getV2() {
return this.v2;
}
public static class V1 {
/**
* ID of the custom device that is exporting metrics to Dynatrace.
*/
private String deviceId;
/**
* Group for exported metrics. Used to specify custom device group name in the
* Dynatrace UI.
*/
private String group;
/**
* Technology type for exported metrics. Used to group metrics under a logical
* technology name in the Dynatrace UI.
*/
private String technologyType = "java";
public String getDeviceId() {
return this.deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getGroup() {
return this.group;
}
public void setGroup(String group) {
this.group = group;
}
public String getTechnologyType() {
return this.technologyType;
}
public void setTechnologyType(String technologyType) {
this.technologyType = technologyType;
}
}
public static class V2 {
/**
* Default dimensions that are added to all metrics in the form of key-value
* pairs. These are overwritten by Micrometer tags if they use the same key.
*/
private Map<String, String> defaultDimensions;
/**
* Whether to enable Dynatrace metadata export.
*/
private boolean enrichWithDynatraceMetadata = true;
/**
* Prefix string that is added to all exported metrics.
*/
private String metricKeyPrefix;
public Map<String, String> getDefaultDimensions() {
return this.defaultDimensions;
}
public void setDefaultDimensions(Map<String, String> defaultDimensions) {
this.defaultDimensions = defaultDimensions;
}
public boolean isEnrichWithDynatraceMetadata() {
return this.enrichWithDynatraceMetadata;
}
public void setEnrichWithDynatraceMetadata(Boolean enrichWithDynatraceMetadata) {
this.enrichWithDynatraceMetadata = enrichWithDynatraceMetadata;
}
public String getMetricKeyPrefix() {
return this.metricKeyPrefix;
}
public void setMetricKeyPrefix(String metricKeyPrefix) {
this.metricKeyPrefix = metricKeyPrefix;
}
}
}

View File

@ -16,14 +16,21 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.dynatrace;
import java.util.Map;
import java.util.function.Function;
import io.micrometer.dynatrace.DynatraceApiVersion;
import io.micrometer.dynatrace.DynatraceConfig;
import org.springframework.boot.actuate.autoconfigure.metrics.export.dynatrace.DynatraceProperties.V1;
import org.springframework.boot.actuate.autoconfigure.metrics.export.dynatrace.DynatraceProperties.V2;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter;
/**
* Adapter to convert {@link DynatraceProperties} to a {@link DynatraceConfig}.
*
* @author Andy Wilkinson
* @author Georg Pirklbauer
*/
class DynatracePropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter<DynatraceProperties>
implements DynatraceConfig {
@ -44,12 +51,12 @@ class DynatracePropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapt
@Override
public String deviceId() {
return get(DynatraceProperties::getDeviceId, DynatraceConfig.super::deviceId);
return get(v1(V1::getDeviceId), DynatraceConfig.super::deviceId);
}
@Override
public String technologyType() {
return get(DynatraceProperties::getTechnologyType, DynatraceConfig.super::technologyType);
return get(v1(V1::getTechnologyType), DynatraceConfig.super::technologyType);
}
@Override
@ -59,7 +66,36 @@ class DynatracePropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapt
@Override
public String group() {
return get(DynatraceProperties::getGroup, DynatraceConfig.super::group);
return get(v1(V1::getGroup), DynatraceConfig.super::group);
}
@Override
public DynatraceApiVersion apiVersion() {
return get((properties) -> (properties.getV1().getDeviceId() != null) ? DynatraceApiVersion.V1
: DynatraceApiVersion.V2, DynatraceConfig.super::apiVersion);
}
@Override
public String metricKeyPrefix() {
return get(v2(V2::getMetricKeyPrefix), DynatraceConfig.super::metricKeyPrefix);
}
@Override
public Map<String, String> defaultDimensions() {
return get(v2(V2::getDefaultDimensions), DynatraceConfig.super::defaultDimensions);
}
@Override
public boolean enrichWithDynatraceMetadata() {
return get(v2(V2::isEnrichWithDynatraceMetadata), DynatraceConfig.super::enrichWithDynatraceMetadata);
}
private <V> Function<DynatraceProperties, V> v1(Function<V1, V> getter) {
return (properties) -> getter.apply(properties.getV1());
}
private <V> Function<DynatraceProperties, V> v2(Function<V2, V> getter) {
return (properties) -> getter.apply(properties.getV2());
}
}

View File

@ -48,14 +48,15 @@ class DynatraceMetricsExportAutoConfigurationTests {
}
@Test
void failsWithoutAUri() {
void failsWithADeviceIdWithoutAUri() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class)
.withPropertyValues("management.metrics.export.dynatrace.device-id:dev-1")
.run((context) -> assertThat(context).hasFailed());
}
@Test
void autoConfiguresConfigAndMeterRegistry() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class).with(mandatoryProperties())
this.contextRunner.withUserConfiguration(BaseConfiguration.class).with(v1MandatoryProperties())
.run((context) -> assertThat(context).hasSingleBean(DynatraceMeterRegistry.class)
.hasSingleBean(DynatraceConfig.class));
}
@ -85,14 +86,25 @@ class DynatraceMetricsExportAutoConfigurationTests {
@Test
void allowsCustomRegistryToBeUsed() {
this.contextRunner.withUserConfiguration(CustomRegistryConfiguration.class).with(mandatoryProperties())
this.contextRunner.withUserConfiguration(CustomRegistryConfiguration.class).with(v1MandatoryProperties())
.run((context) -> assertThat(context).hasSingleBean(DynatraceMeterRegistry.class)
.hasBean("customRegistry").hasSingleBean(DynatraceConfig.class));
}
@Test
void stopsMeterRegistryWhenContextIsClosed() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class).with(mandatoryProperties()).run((context) -> {
void stopsMeterRegistryForV1ApiWhenContextIsClosed() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class).with(v1MandatoryProperties())
.run((context) -> {
DynatraceMeterRegistry registry = context.getBean(DynatraceMeterRegistry.class);
assertThat(registry.isClosed()).isFalse();
context.close();
assertThat(registry.isClosed()).isTrue();
});
}
@Test
void stopsMeterRegistryForV2ApiWhenContextIsClosed() {
this.contextRunner.withUserConfiguration(BaseConfiguration.class).run((context) -> {
DynatraceMeterRegistry registry = context.getBean(DynatraceMeterRegistry.class);
assertThat(registry.isClosed()).isFalse();
context.close();
@ -100,7 +112,7 @@ class DynatraceMetricsExportAutoConfigurationTests {
});
}
private Function<ApplicationContextRunner, ApplicationContextRunner> mandatoryProperties() {
private Function<ApplicationContextRunner, ApplicationContextRunner> v1MandatoryProperties() {
return (runner) -> runner.withPropertyValues(
"management.metrics.export.dynatrace.uri=https://dynatrace.example.com",
"management.metrics.export.dynatrace.api-token=abcde",

View File

@ -16,6 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.dynatrace;
import java.util.HashMap;
import io.micrometer.dynatrace.DynatraceApiVersion;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@ -24,6 +27,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link DynatracePropertiesConfigAdapter}.
*
* @author Andy Wilkinson
* @author Georg Pirklbauer
*/
class DynatracePropertiesConfigAdapterTests {
@ -42,6 +46,7 @@ class DynatracePropertiesConfigAdapterTests {
}
@Test
@Deprecated
void whenPropertiesDeviceIdIsSetAdapterDeviceIdReturnsIt() {
DynatraceProperties properties = new DynatraceProperties();
properties.setDeviceId("dev-1");
@ -49,6 +54,15 @@ class DynatracePropertiesConfigAdapterTests {
}
@Test
@Deprecated
void whenPropertiesV1DeviceIdIsSetAdapterDeviceIdReturnsIt() {
DynatraceProperties properties = new DynatraceProperties();
properties.getV1().setDeviceId("dev-1");
assertThat(new DynatracePropertiesConfigAdapter(properties).deviceId()).isEqualTo("dev-1");
}
@Test
@Deprecated
void whenPropertiesTechnologyTypeIsSetAdapterTechnologyTypeReturnsIt() {
DynatraceProperties properties = new DynatraceProperties();
properties.setTechnologyType("tech-1");
@ -56,10 +70,88 @@ class DynatracePropertiesConfigAdapterTests {
}
@Test
void whenPropertiesV1TechnologyTypeIsSetAdapterTechnologyTypeReturnsIt() {
DynatraceProperties properties = new DynatraceProperties();
properties.getV1().setTechnologyType("tech-1");
assertThat(new DynatracePropertiesConfigAdapter(properties).technologyType()).isEqualTo("tech-1");
}
@Test
@Deprecated
void whenPropertiesGroupIsSetAdapterGroupReturnsIt() {
DynatraceProperties properties = new DynatraceProperties();
properties.setGroup("group-1");
assertThat(new DynatracePropertiesConfigAdapter(properties).group()).isEqualTo("group-1");
}
@Test
void whenPropertiesV1GroupIsSetAdapterGroupReturnsIt() {
DynatraceProperties properties = new DynatraceProperties();
properties.getV1().setGroup("group-1");
assertThat(new DynatracePropertiesConfigAdapter(properties).group()).isEqualTo("group-1");
}
@Test
@SuppressWarnings("deprecation")
void whenDeviceIdIsSetThenAdapterApiVersionIsV1() {
DynatraceProperties properties = new DynatraceProperties();
properties.setDeviceId("dev-1");
assertThat(new DynatracePropertiesConfigAdapter(properties).apiVersion()).isSameAs(DynatraceApiVersion.V1);
}
@Test
void whenV1DeviceIdIsSetThenAdapterApiVersionIsV1() {
DynatraceProperties properties = new DynatraceProperties();
properties.getV1().setDeviceId("dev-1");
assertThat(new DynatracePropertiesConfigAdapter(properties).apiVersion()).isSameAs(DynatraceApiVersion.V1);
}
@Test
void whenDeviceIdIsNotSetThenAdapterApiVersionIsV2() {
DynatraceProperties properties = new DynatraceProperties();
assertThat(new DynatracePropertiesConfigAdapter(properties).apiVersion()).isSameAs(DynatraceApiVersion.V2);
}
@Test
void whenPropertiesMetricKeyPrefixIsSetAdapterGroupReturnsIt() {
DynatraceProperties properties = new DynatraceProperties();
properties.getV2().setMetricKeyPrefix("my.prefix");
assertThat(new DynatracePropertiesConfigAdapter(properties).metricKeyPrefix()).isEqualTo("my.prefix");
}
@Test
void whenPropertiesEnrichWithOneAgentMetadataIsSetAdapterGroupReturnsIt() {
DynatraceProperties properties = new DynatraceProperties();
properties.getV2().setEnrichWithDynatraceMetadata(true);
assertThat(new DynatracePropertiesConfigAdapter(properties).enrichWithDynatraceMetadata()).isTrue();
}
@Test
void whenPropertiesDefaultDimensionsIsSetAdapterGroupReturnsIt() {
DynatraceProperties properties = new DynatraceProperties();
HashMap<String, String> defaultDimensions = new HashMap<>();
defaultDimensions.put("dim1", "value1");
defaultDimensions.put("dim2", "value2");
properties.getV2().setDefaultDimensions(defaultDimensions);
assertThat(new DynatracePropertiesConfigAdapter(properties).defaultDimensions())
.containsExactlyEntriesOf(defaultDimensions);
}
@Test
@SuppressWarnings("deprecation")
void defaultValues() {
DynatraceProperties properties = new DynatraceProperties();
assertThat(properties.getApiToken()).isNull();
assertThat(properties.getUri()).isNull();
assertThat(properties.getV1().getDeviceId()).isNull();
assertThat(properties.getV1().getTechnologyType()).isEqualTo("java");
assertThat(properties.getV1().getGroup()).isNull();
assertThat(properties.getV2().getMetricKeyPrefix()).isNull();
assertThat(properties.getV2().isEnrichWithDynatraceMetadata()).isTrue();
assertThat(properties.getV2().getDefaultDimensions()).isNull();
assertThat(properties.getDeviceId()).isNull();
assertThat(properties.getTechnologyType()).isEqualTo("java");
assertThat(properties.getGroup()).isNull();
}
}

View File

@ -30,12 +30,14 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class DynatracePropertiesTests extends StepRegistryPropertiesTests {
@SuppressWarnings("deprecation")
@Test
void defaultValuesAreConsistent() {
DynatraceProperties properties = new DynatraceProperties();
DynatraceConfig config = (key) -> null;
assertStepRegistryDefaultValues(properties, config);
assertThat(properties.getTechnologyType()).isEqualTo(config.technologyType());
assertThat(properties.getV1().getTechnologyType()).isEqualTo(config.technologyType());
}
}

View File

@ -147,7 +147,68 @@ You can also change the interval at which metrics are sent to Datadog:
[[actuator.metrics.export.dynatrace]]
==== Dynatrace
Dynatrace registry pushes metrics to the configured URI periodically.
Dynatrace offers two metrics ingest APIs, both of which are implemented for {micrometer-registry-docs}/dynatrace[Micrometer].
Config properties in the `v1` namespace only apply when exporting to the {dynatrace-help}/dynatrace-api/environment-api/metric-v1/[Timeseries v1 API].
Config properties in the `v2` namespace only apply when exporting to the {dynatrace-help}/dynatrace-api/environment-api/metric-v2/post-ingest-metrics/[Metrics v2 API].
Please note that this integration can only export to either the `v1` or `v2` version of the API at a time.
If the `device-id` (required for v1, but not used in v2) is set in the `v1` namespace, metrics will be exported to the `v1` endpoint.
Otherwise, `v2` is assumed.
[[actuator.metrics.export.dynatrace.v2-api]]
===== v2 API
The v2 API can be used in two ways.
If a local OneAgent is running on the host, metrics will be automatically exported to the {dynatrace-help}/how-to-use-dynatrace/metrics/metric-ingestion/ingestion-methods/local-api/[local OneAgent ingest endpoint].
The ingest endpoint forwards the metrics to the Dynatrace backend.
This is the default behaviour and requires no special setup beyond a dependency on `io.micrometer:micrometer-registry-dynatrace`.
If no local OneAgent is running, the endpoint of the {dynatrace-help}/dynatrace-api/environment-api/metric-v2/post-ingest-metrics/[Metrics v2 API] and an API token are required.
The {dynatrace-help}/dynatrace-api/basics/dynatrace-api-authentication/[API token] must have the "Ingest metrics" (`metrics.ingest`) permission set.
It is recommended to limit the scope of the token to this one permission.
Please ensure that the endpoint URI contains the path (e.g. `/api/v2/metrics/ingest`).
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
management:
metrics:
export:
dynatrace:
# uri: "https://{your-domain}/e/{your-environment-id}/api/v2/metrics/ingest" for managed deployments.
uri: "https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest"
api-token: "YOUR_TOKEN" # should be read from a secure source and not hard-coded.
----
When using the Dynatrace v2 API, the following optional features are available:
* Metric key prefix: sets a prefix that will be prepended to all exported metric keys.
* Enrich with Dynatrace metadata: if a OneAgent or Dynatrace operator is running, enrich metrics with additional metadata (e.g. about the host, process or pod).
* Default dimensions: specify key-value pairs that are added to all exported metrics.
If tags with the same key are specified using Micrometer, they overwrite the default dimensions.
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
management:
metrics:
export:
dynatrace:
# specify token and uri or leave blank for OneAgent export
v2:
metric-key-prefix: "your.key.prefix"
enrich-with-dynatrace-metadata: true
default-dimensions:
key1: "value1"
key2: "value2"
----
[[actuator.metrics.export.dynatrace.v1-api]]
===== v1 API (Legacy)
The Dynatrace v1 API metrics registry pushes metrics to the configured URI periodically using the {dynatrace-help}/dynatrace-api/environment-api/metric-v1/[Timeseries v1 API].
For backwards-compatibility with existing setups, when `device-id` is set (required for v1, but not used in v2), metrics will be exported to the Timeseries v1 endpoint.
To export metrics to {micrometer-registry-docs}/dynatrace[Dynatrace], your API token, device ID, and URI must be provided:
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
@ -156,12 +217,21 @@ To export metrics to {micrometer-registry-docs}/dynatrace[Dynatrace], your API t
metrics:
export:
dynatrace:
api-token: "YOUR_TOKEN"
device-id: "YOUR_DEVICE_ID"
uri: "YOUR_URI"
# uri: "https://{your-domain}/e/{your-environment-id}" on managed deployments.
uri: "https://{your-environment-id}.live.dynatrace.com"
api-token: "YOUR_TOKEN" # should be read from a secure property source
v1:
device-id: "YOUR_DEVICE_ID"
----
You can also change the interval at which metrics are sent to Dynatrace:
For the v1 API, the base environment URI must be specified without a path as the v1 endpoint path will be added automatically.
[[actuator.metrics.export.dynatrace.version-independent-settings]]
===== Version-independent Settings
In addition to the API endpoint and token, you can also change the interval at which metrics are sent to Dynatrace.
The default export interval is `60s`.
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
@ -172,6 +242,8 @@ You can also change the interval at which metrics are sent to Dynatrace:
step: "30s"
----
More information on how to set up the Dynatrace exporter for Micrometer can be found in {micrometer-registry-docs}/dynatrace[the Micrometer documentation].
[[actuator.metrics.export.elastic]]
@ -188,8 +260,6 @@ The location of the Elastic server to use can be provided using the following pr
host: "https://elastic.example.com:8086"
----
[[actuator.metrics.export.ganglia]]
==== Ganglia
By default, metrics are exported to {micrometer-registry-docs}/ganglia[Ganglia] running on your local machine.

View File

@ -93,6 +93,7 @@
:spring-webservices-docs: https://docs.spring.io/spring-ws/docs/{spring-webservices-version}/reference/html/
:ant-docs: https://ant.apache.org/manual
:dependency-management-plugin-code: https://github.com/spring-gradle-plugins/dependency-management-plugin
:dynatrace-help: https://www.dynatrace.com/support/help
:embedded-mongo-code: https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo/blob/de.flapdoodle.embed.mongo-{embedded-mongo-version}
:gradle-docs: https://docs.gradle.org/current/userguide
:hibernate-docs: https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html