Skip to content

Commit 4892920

Browse files
Add OpenTelemetry Metrics Exporters for Dynatrace
Register a metrics exporters, that forward data to Dynatrace, if an appropriate service binding is found or do a non-operation otherwise. This allows developers to explicitly configure the export, e.g. by `otel.metrics.exporter=dynatrace`. Additional exporters can be added with comma-separated labels. Signed-off-by: Karsten Schnitter <[email protected]>
1 parent d2770a5 commit 4892920

File tree

3 files changed

+174
-1
lines changed

3 files changed

+174
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter;
2+
3+
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
4+
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder;
5+
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
6+
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder;
7+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
8+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
9+
import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider;
10+
import io.opentelemetry.sdk.common.export.RetryPolicy;
11+
import io.opentelemetry.sdk.metrics.Aggregation;
12+
import io.opentelemetry.sdk.metrics.InstrumentType;
13+
import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
14+
import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector;
15+
import io.opentelemetry.sdk.metrics.export.MetricExporter;
16+
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregationUtil;
17+
import io.pivotal.cfenv.core.CfEnv;
18+
import io.pivotal.cfenv.core.CfService;
19+
20+
import java.time.Duration;
21+
import java.util.Locale;
22+
import java.util.function.Function;
23+
import java.util.logging.Logger;
24+
25+
import static io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram;
26+
27+
public class DynatraceMetricsExporterProvider implements ConfigurableMetricExporterProvider {
28+
29+
private static final Logger LOG = Logger.getLogger(DynatraceMetricsExporterProvider.class.getName());
30+
public static final String CRED_DYNATRACE_APIURL = "apiurl";
31+
public static final String DT_APIURL_METRICS_SUFFIX = "/v2/otlp/v1/metrics";
32+
33+
private final Function<ConfigProperties, CfService> serviceProvider;
34+
35+
public DynatraceMetricsExporterProvider() {
36+
this(config -> new DynatraceServiceProvider(config, new CfEnv()).get());
37+
}
38+
39+
public DynatraceMetricsExporterProvider(Function<ConfigProperties, CfService> serviceProvider) {
40+
this.serviceProvider = serviceProvider;
41+
}
42+
43+
private static String getCompression(ConfigProperties config) {
44+
String compression = config.getString("otel.exporter.dynatrace.metrics.compression");
45+
return compression != null ? compression : config.getString("otel.exporter.dynatrace.compression", "gzip");
46+
}
47+
48+
private static Duration getTimeOut(ConfigProperties config) {
49+
Duration timeout = config.getDuration("otel.exporter.dynatrace.metrics.timeout");
50+
return timeout != null ? timeout : config.getDuration("otel.exporter.dynatrace.timeout");
51+
}
52+
53+
private static DefaultAggregationSelector getDefaultAggregationSelector(ConfigProperties config) {
54+
String defaultHistogramAggregation =
55+
config.getString("otel.exporter.dynatrace.metrics.default.histogram.aggregation");
56+
if (defaultHistogramAggregation == null) {
57+
return DefaultAggregationSelector.getDefault().with(InstrumentType.HISTOGRAM, Aggregation.defaultAggregation());
58+
}
59+
if (AggregationUtil.aggregationName(Aggregation.base2ExponentialBucketHistogram())
60+
.equalsIgnoreCase(defaultHistogramAggregation)) {
61+
return
62+
DefaultAggregationSelector.getDefault()
63+
.with(InstrumentType.HISTOGRAM, Aggregation.base2ExponentialBucketHistogram());
64+
} else if (AggregationUtil.aggregationName(explicitBucketHistogram())
65+
.equalsIgnoreCase(defaultHistogramAggregation)) {
66+
return DefaultAggregationSelector.getDefault().with(InstrumentType.HISTOGRAM, Aggregation.explicitBucketHistogram());
67+
} else {
68+
throw new ConfigurationException(
69+
"Unrecognized default histogram aggregation: " + defaultHistogramAggregation);
70+
}
71+
}
72+
73+
@Override
74+
public String getName() {
75+
return "dynatrace";
76+
}
77+
78+
79+
private static boolean isBlank(String text) {
80+
return text == null || text.trim().isEmpty();
81+
}
82+
83+
@Override
84+
public MetricExporter createExporter(ConfigProperties config) {
85+
CfService cfService = serviceProvider.apply(config);
86+
if (cfService == null) {
87+
LOG.info("No dynatrace service binding found. Skipping metrics exporter registration.");
88+
return NoopMetricExporter.getInstance();
89+
}
90+
91+
LOG.info("Creating metrics exporter for service binding " + cfService.getName() + " (" + cfService.getLabel() + ")");
92+
93+
String apiUrl = cfService.getCredentials().getString(CRED_DYNATRACE_APIURL);
94+
if (isBlank(apiUrl)) {
95+
LOG.warning("Credential \"" + CRED_DYNATRACE_APIURL + "\" not found. Skipping dynatrace exporter configuration");
96+
return NoopMetricExporter.getInstance();
97+
}
98+
String tokenName = config.getString("otel.javaagent.extension.sap.cf.binding.dynatrace.metrics.token-name");
99+
if (isBlank(tokenName)) {
100+
LOG.warning("Configuration \"otel.javaagent.extension.sap.cf.binding.dynatrace.metrics.token-name\" not found. Skipping dynatrace exporter configuration");
101+
return NoopMetricExporter.getInstance();
102+
}
103+
String apiToken = cfService.getCredentials().getString(tokenName);
104+
if (isBlank(apiUrl)) {
105+
LOG.warning("Credential \"" + tokenName + "\" not found. Skipping dynatrace exporter configuration");
106+
return NoopMetricExporter.getInstance();
107+
}
108+
109+
OtlpHttpMetricExporterBuilder builder = OtlpHttpMetricExporter.builder();
110+
builder.setEndpoint(apiUrl + DT_APIURL_METRICS_SUFFIX)
111+
.setCompression(getCompression(config))
112+
.addHeader("Authorization", "ApiToken " + apiToken)
113+
.setRetryPolicy(RetryPolicy.getDefault())
114+
.setAggregationTemporalitySelector(AggregationTemporalitySelector.alwaysCumulative())
115+
.setDefaultAggregationSelector(getDefaultAggregationSelector(config));
116+
117+
Duration timeOut = getTimeOut(config);
118+
if (timeOut != null) {
119+
builder.setTimeout(timeOut);
120+
}
121+
122+
LOG.info("Created metrics exporter for service binding " + cfService.getName() + " (" + cfService.getLabel() + ")");
123+
return builder.build();
124+
}
125+
126+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter;
2+
3+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
4+
import io.pivotal.cfenv.core.CfEnv;
5+
import io.pivotal.cfenv.core.CfService;
6+
7+
import java.util.List;
8+
import java.util.function.Supplier;
9+
import java.util.stream.Stream;
10+
11+
class DynatraceServiceProvider implements Supplier<CfService> {
12+
13+
private static final String DEFAULT_USER_PROVIDED_LABEL = "user-provided";
14+
private static final String DEFAULT_DYNATRACE_LABEL = "dynatrace";
15+
private static final String DEFAULT_DYNATRACE_TAG = "dynatrace";
16+
17+
private final CfService service;
18+
19+
public DynatraceServiceProvider(ConfigProperties config, CfEnv cfEnv) {
20+
String userProvidedLabel = getUserProvidedLabel(config);
21+
String dynatraceLabel = getDynatraceLabel(config);
22+
String dynatraceTag = getDynatraceTag(config);
23+
List<CfService> userProvided = cfEnv.findServicesByLabel(userProvidedLabel);
24+
List<CfService> managed = cfEnv.findServicesByLabel(dynatraceLabel);
25+
this.service = Stream.concat(userProvided.stream(), managed.stream())
26+
.filter(svc -> svc.existsByTagIgnoreCase(dynatraceTag)).findFirst().orElse(null);
27+
28+
}
29+
30+
private String getUserProvidedLabel(ConfigProperties config) {
31+
return config.getString("otel.javaagent.extension.sap.cf.binding.user-provided.label", DEFAULT_USER_PROVIDED_LABEL);
32+
}
33+
34+
private String getDynatraceLabel(ConfigProperties config) {
35+
return config.getString("otel.javaagent.extension.sap.cf.binding.dynatrace.label", DEFAULT_DYNATRACE_LABEL);
36+
}
37+
38+
private String getDynatraceTag(ConfigProperties config) {
39+
return config.getString("otel.javaagent.extension.sap.cf.binding.dynatrace.tag", DEFAULT_DYNATRACE_TAG);
40+
}
41+
42+
@Override
43+
public CfService get() {
44+
return service;
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter.CloudLoggingMetricsExporterProvider
1+
com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter.CloudLoggingMetricsExporterProvider
2+
com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter.DynatraceMetricsExporterProvider

0 commit comments

Comments
 (0)