Skip to content

Commit

Permalink
Add OpenTelemetry Metrics Exporters for Dynatrace
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
KarstenSchnitter committed Dec 11, 2023
1 parent 4fed8c3 commit bc2b372
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter;

import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider;
import io.opentelemetry.sdk.common.export.RetryPolicy;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.metrics.internal.aggregator.AggregationUtil;
import io.pivotal.cfenv.core.CfEnv;
import io.pivotal.cfenv.core.CfService;

import java.time.Duration;
import java.util.Locale;
import java.util.function.Function;
import java.util.logging.Logger;

import static io.opentelemetry.sdk.metrics.Aggregation.explicitBucketHistogram;

public class DynatraceMetricsExporterProvider implements ConfigurableMetricExporterProvider {

private static final Logger LOG = Logger.getLogger(DynatraceMetricsExporterProvider.class.getName());
public static final String CRED_DYNATRACE_APIURL = "apiurl";
public static final String DT_APIURL_METRICS_SUFFIX = "/v2/otlp/v1/metrics";

private final Function<ConfigProperties, CfService> serviceProvider;

public DynatraceMetricsExporterProvider() {
this(config -> new DynatraceServiceProvider(config, new CfEnv()).get());
}

public DynatraceMetricsExporterProvider(Function<ConfigProperties, CfService> serviceProvider) {
this.serviceProvider = serviceProvider;
}

private static String getCompression(ConfigProperties config) {
String compression = config.getString("otel.exporter.dynatrace.metrics.compression");
return compression != null ? compression : config.getString("otel.exporter.dynatrace.compression", "gzip");
}

private static Duration getTimeOut(ConfigProperties config) {
Duration timeout = config.getDuration("otel.exporter.dynatrace.metrics.timeout");
return timeout != null ? timeout : config.getDuration("otel.exporter.dynatrace.timeout");
}

private static DefaultAggregationSelector getDefaultAggregationSelector(ConfigProperties config) {
String defaultHistogramAggregation =
config.getString("otel.exporter.dynatrace.metrics.default.histogram.aggregation");
if (defaultHistogramAggregation == null) {
return DefaultAggregationSelector.getDefault().with(InstrumentType.HISTOGRAM, Aggregation.defaultAggregation());
}
if (AggregationUtil.aggregationName(Aggregation.base2ExponentialBucketHistogram())
.equalsIgnoreCase(defaultHistogramAggregation)) {
return
DefaultAggregationSelector.getDefault()
.with(InstrumentType.HISTOGRAM, Aggregation.base2ExponentialBucketHistogram());
} else if (AggregationUtil.aggregationName(explicitBucketHistogram())
.equalsIgnoreCase(defaultHistogramAggregation)) {
return DefaultAggregationSelector.getDefault().with(InstrumentType.HISTOGRAM, Aggregation.explicitBucketHistogram());
} else {
throw new ConfigurationException(
"Unrecognized default histogram aggregation: " + defaultHistogramAggregation);
}
}

@Override
public String getName() {
return "dynatrace";
}


private static boolean isBlank(String text) {
return text == null || text.trim().isEmpty();
}

@Override
public MetricExporter createExporter(ConfigProperties config) {
CfService cfService = serviceProvider.apply(config);
if (cfService == null) {
LOG.info("No dynatrace service binding found. Skipping metrics exporter registration.");
return NoopMetricExporter.getInstance();
}

LOG.info("Creating metrics exporter for service binding " + cfService.getName() + " (" + cfService.getLabel() + ")");

String apiUrl = cfService.getCredentials().getString(CRED_DYNATRACE_APIURL);
if (isBlank(apiUrl)) {
LOG.warning("Credential \"" + CRED_DYNATRACE_APIURL + "\" not found. Skipping dynatrace exporter configuration");
return NoopMetricExporter.getInstance();
}
String tokenName = config.getString("otel.javaagent.extension.sap.cf.binding.dynatrace.metrics.token-name");
if (isBlank(tokenName)) {
LOG.warning("Configuration \"otel.javaagent.extension.sap.cf.binding.dynatrace.metrics.token-name\" not found. Skipping dynatrace exporter configuration");
return NoopMetricExporter.getInstance();
}
String apiToken = cfService.getCredentials().getString(tokenName);
if (isBlank(apiUrl)) {
LOG.warning("Credential \"" + tokenName + "\" not found. Skipping dynatrace exporter configuration");
return NoopMetricExporter.getInstance();
}

OtlpGrpcMetricExporterBuilder builder = OtlpGrpcMetricExporter.builder();
builder.setEndpoint(apiUrl + DT_APIURL_METRICS_SUFFIX)
.setCompression(getCompression(config))
.addHeader("Authorization", "ApiToken " + apiToken)
.setRetryPolicy(RetryPolicy.getDefault())
.setAggregationTemporalitySelector(AggregationTemporalitySelector.alwaysCumulative())
.setDefaultAggregationSelector(getDefaultAggregationSelector(config));

Duration timeOut = getTimeOut(config);
if (timeOut != null) {
builder.setTimeout(timeOut);
}

LOG.info("Created metrics exporter for service binding " + cfService.getName() + " (" + cfService.getLabel() + ")");
return builder.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.pivotal.cfenv.core.CfEnv;
import io.pivotal.cfenv.core.CfService;

import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;

class DynatraceServiceProvider implements Supplier<CfService> {

private static final String DEFAULT_USER_PROVIDED_LABEL = "user-provided";
private static final String DEFAULT_DYNATRACE_LABEL = "dynatrace";
private static final String DEFAULT_DYNATRACE_TAG = "dynatrace";

private final CfService service;

public DynatraceServiceProvider(ConfigProperties config, CfEnv cfEnv) {
String userProvidedLabel = getUserProvidedLabel(config);
String dynatraceLabel = getDynatraceLabel(config);
String dynatraceTag = getDynatraceTag(config);
List<CfService> userProvided = cfEnv.findServicesByLabel(userProvidedLabel);
List<CfService> managed = cfEnv.findServicesByLabel(dynatraceLabel);
this.service = Stream.concat(userProvided.stream(), managed.stream())
.filter(svc -> svc.existsByTagIgnoreCase(dynatraceTag)).findFirst().orElse(null);

}

private String getUserProvidedLabel(ConfigProperties config) {
return config.getString("otel.javaagent.extension.sap.cf.binding.user-provided.label", DEFAULT_USER_PROVIDED_LABEL);
}

private String getDynatraceLabel(ConfigProperties config) {
return config.getString("otel.javaagent.extension.sap.cf.binding.dynatrace.label", DEFAULT_DYNATRACE_LABEL);
}

private String getDynatraceTag(ConfigProperties config) {
return config.getString("otel.javaagent.extension.sap.cf.binding.dynatrace.tag", DEFAULT_DYNATRACE_TAG);
}

@Override
public CfService get() {
return service;
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter.CloudLoggingMetricsExporterProvider
com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter.CloudLoggingMetricsExporterProvider
com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter.DynatraceMetricsExporterProvider

0 comments on commit bc2b372

Please sign in to comment.