Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions cf-java-logging-support-opentelemetry-agent-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ java -javaagent:/path/to/opentelemetry-javaagent-<version>.jar \

The [OpenTelemetry Java Instrumentation project](https://github.com/open-telemetry/opentelemetry-java-instrumentation) provides detailed documentation on the configuration properties for [Logback](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/logback/logback-appender-1.0/javaagent) and [Log4j](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/log4j/log4j-appender-2.17/javaagent).

### Filtering Metrics

_This feature was introduced with version 4.1.0 of the extension._

You can filter which metrics are exported to Cloud Logging or Dynatrace by name using the following properties:

| Property | Description |
|--------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------|
| `otel.exporter.cloud-logging.metrics.include.names` or `otel.exporter.dynatrace.metrics.include.names` | A comma-separated list of metric names to be forwarded. This may include a wildcard "*" at the end of the name. |
| `otel.exporter.cloud-logging.metrics.exclude.names` or `otel.exporter.dynatrace.metrics.exclude.names` | A comma-separated list of metric names to be rejected. This may include a wildcard "*" at the end of the name. |

Note, that the `include` filter is applied before the `exclude` filter.
That means, if a metric matches both filters, it will be excluded.
The configuration applies to both the `cloud-logging` and `dynatrace` exporters independently.

## Using User-Provided Service Instances

### SAP Cloud Logging
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

public class CloudLoggingMetricsExporterProvider implements ConfigurableMetricExporterProvider {

private static final String GENERIC_CONFIG_PREFIX = "otel.exporter.cloud-logging.";
private static final String METRICS_CONFIG_PREFIX = "otel.exporter.cloud-logging.metrics.";
private static final Logger LOG = Logger.getLogger(CloudLoggingMetricsExporterProvider.class.getName());

private final Function<ConfigProperties, Stream<CloudFoundryServiceInstance>> servicesProvider;
Expand All @@ -43,17 +45,17 @@ public CloudLoggingMetricsExporterProvider() {
}

private static String getCompression(ConfigProperties config) {
String compression = config.getString("otel.exporter.cloud-logging.metrics.compression");
return compression != null ? compression : config.getString("otel.exporter.cloud-logging.compression", "gzip");
String compression = config.getString(METRICS_CONFIG_PREFIX + "compression");
return compression != null ? compression : config.getString(GENERIC_CONFIG_PREFIX + "compression", "gzip");
}

private static Duration getTimeOut(ConfigProperties config) {
Duration timeout = config.getDuration("otel.exporter.cloud-logging.metrics.timeout");
return timeout != null ? timeout : config.getDuration("otel.exporter.cloud-logging.timeout");
Duration timeout = config.getDuration(METRICS_CONFIG_PREFIX + "timeout");
return timeout != null ? timeout : config.getDuration(GENERIC_CONFIG_PREFIX + "timeout");
}

private static AggregationTemporalitySelector getAggregationTemporalitySelector(ConfigProperties config) {
String temporalityStr = config.getString("otel.exporter.cloud-logging.metrics.temporality.preference");
String temporalityStr = config.getString(METRICS_CONFIG_PREFIX + "temporality.preference");
if (temporalityStr == null) {
return AggregationTemporalitySelector.alwaysCumulative();
}
Expand All @@ -71,8 +73,7 @@ private static AggregationTemporalitySelector getAggregationTemporalitySelector(
}

private static DefaultAggregationSelector getDefaultAggregationSelector(ConfigProperties config) {
String defaultHistogramAggregation =
config.getString("otel.exporter.cloud-logging.metrics.default.histogram.aggregation");
String defaultHistogramAggregation = config.getString(METRICS_CONFIG_PREFIX + "default.histogram.aggregation");
if (defaultHistogramAggregation == null) {
return DefaultAggregationSelector.getDefault()
.with(InstrumentType.HISTOGRAM, Aggregation.defaultAggregation());
Expand Down Expand Up @@ -101,8 +102,11 @@ public MetricExporter createExporter(ConfigProperties config) {
List<MetricExporter> exporters = servicesProvider.apply(config).map(svc -> createExporter(config, svc))
.filter(exp -> !(exp instanceof NoopMetricExporter))
.collect(Collectors.toList());
return MultiMetricExporter.composite(exporters, getAggregationTemporalitySelector(config),
getDefaultAggregationSelector(config));
MetricExporter exporter = MultiMetricExporter.composite(exporters, getAggregationTemporalitySelector(config),
getDefaultAggregationSelector(config));
exporter = FilteringMetricExporter.wrap(exporter).withConfig(config).withPropertyPrefix(METRICS_CONFIG_PREFIX)
.build();
return exporter;
}

private MetricExporter createExporter(ConfigProperties config, CloudFoundryServiceInstance service) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ public class DynatraceMetricsExporterProvider implements ConfigurableMetricExpor

public static final String CRED_DYNATRACE_APIURL = "apiurl";
public static final String DT_APIURL_METRICS_SUFFIX = "/v2/otlp/v1/metrics";

private static final String GENERIC_CONFIG_PREFIX = "otel.exporter.dynatrace.";
private static final String METRICS_CONFIG_PREFIX = "otel.exporter.dynatrace.metrics.";

private static final Logger LOG = Logger.getLogger(DynatraceMetricsExporterProvider.class.getName());
private static final AggregationTemporalitySelector ALWAYS_DELTA = instrumentType -> AggregationTemporality.DELTA;
private final Function<ConfigProperties, CloudFoundryServiceInstance> serviceProvider;
Expand All @@ -41,17 +45,17 @@ public DynatraceMetricsExporterProvider(Function<ConfigProperties, CloudFoundryS
}

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");
String compression = config.getString(METRICS_CONFIG_PREFIX + "compression");
return compression != null ? compression : config.getString(GENERIC_CONFIG_PREFIX + "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");
Duration timeout = config.getDuration(METRICS_CONFIG_PREFIX + "timeout");
return timeout != null ? timeout : config.getDuration(GENERIC_CONFIG_PREFIX + "timeout");
}

private static AggregationTemporalitySelector getAggregationTemporalitySelector(ConfigProperties config) {
String temporalityStr = config.getString("otel.exporter.dynatrace.metrics.temporality.preference");
String temporalityStr = config.getString(METRICS_CONFIG_PREFIX + "temporality.preference");
if (temporalityStr == null) {
return ALWAYS_DELTA;
}
Expand All @@ -71,8 +75,7 @@ private static AggregationTemporalitySelector getAggregationTemporalitySelector(
}

private static DefaultAggregationSelector getDefaultAggregationSelector(ConfigProperties config) {
String defaultHistogramAggregation =
config.getString("otel.exporter.dynatrace.metrics.default.histogram.aggregation");
String defaultHistogramAggregation = config.getString(METRICS_CONFIG_PREFIX + "default.histogram.aggregation");
if (defaultHistogramAggregation == null) {
return DefaultAggregationSelector.getDefault()
.with(InstrumentType.HISTOGRAM, Aggregation.defaultAggregation());
Expand Down Expand Up @@ -147,7 +150,9 @@ public MetricExporter createExporter(ConfigProperties config) {

LOG.info(
"Created metrics exporter for service binding " + cfService.getName() + " (" + cfService.getLabel() + ")");
return builder.build();
MetricExporter exporter = builder.build();
return FilteringMetricExporter.wrap(exporter).withConfig(config).withPropertyPrefix(METRICS_CONFIG_PREFIX)
.build();
}

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

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.export.MemoryMode;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.export.MetricExporter;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.function.Predicate;

import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;

public class FilteringMetricExporter implements MetricExporter {

private static final String INCLUDED_NAMES_KEY = "include.names";
private static final String EXCLUDED_NAMES_KEY = "exclude.names";

private final MetricExporter delegate;
private final Predicate<MetricData> predicate;

private FilteringMetricExporter(MetricExporter delegate, Predicate<MetricData> predicate) {
this.delegate = delegate;
this.predicate = predicate;
}

@Override
public CompletableResultCode export(Collection<MetricData> collection) {
List<MetricData> filteredMetrics = collection.stream().filter(predicate).collect(toList());
return delegate.export(filteredMetrics);
}

@Override
public CompletableResultCode flush() {
return delegate.flush();
}

@Override
public CompletableResultCode shutdown() {
return delegate.shutdown();
}

@Override
public void close() {
delegate.close();
}

@Override
public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
return delegate.getAggregationTemporality(instrumentType);
}

@Override
public Aggregation getDefaultAggregation(InstrumentType instrumentType) {
return delegate.getDefaultAggregation(instrumentType);
}

@Override
public MemoryMode getMemoryMode() {
return delegate.getMemoryMode();
}

public static Builder wrap(MetricExporter delegate) {
return new Builder(delegate);
}

public static class Builder {

private final MetricExporter delegate;
private ConfigProperties config;
private String prefix = "";

public Builder(MetricExporter delegate) {
this.delegate = delegate;
}

public Builder withConfig(ConfigProperties config) {
this.config = config;
return this;
}

public Builder withPropertyPrefix(String prefix) {
this.prefix = prefix.endsWith(".") ? prefix : prefix + ".";
return this;
}

public MetricExporter build() {
if (config == null) {
return delegate;
}

List<String> includedNames = config.getList(prefix + INCLUDED_NAMES_KEY);
List<String> excludedNames = config.getList(prefix + EXCLUDED_NAMES_KEY);
if (includedNames.isEmpty() && excludedNames.isEmpty()) {
return delegate;
}

Predicate<MetricData> predicate = metricData -> true;
predicate = addInclusions(predicate, includedNames);
predicate = addExclusions(predicate, excludedNames);
return new FilteringMetricExporter(delegate, predicate);
}

private static Predicate<MetricData> addInclusions(Predicate<MetricData> predicate,
List<String> includedNames) {
if (includedNames.isEmpty()) {
return predicate;
}

HashSet<String> names = getNames(includedNames);
if (!names.isEmpty()) {
predicate = predicate.and(metricData -> names.contains(metricData.getName()));
}
List<String> prefixes = getPrefixes(includedNames);
if (!prefixes.isEmpty()) {
predicate = predicate.and(
metricData -> prefixes.stream().anyMatch(p -> metricData.getName().startsWith(p)));
}
return predicate;
}

private static Predicate<MetricData> addExclusions(Predicate<MetricData> predicate,
List<String> excludedNames) {
if (excludedNames.isEmpty()) {
return predicate;
}

HashSet<String> names = getNames(excludedNames);
if (!names.isEmpty()) {
predicate = predicate.and(metricData -> !names.contains(metricData.getName()));
}
List<String> prefixes = getPrefixes(excludedNames);
if (!prefixes.isEmpty()) {
predicate = predicate.and(
metricData -> prefixes.stream().anyMatch(p -> !metricData.getName().startsWith(p)));
}
return predicate;
}

private static HashSet<String> getNames(List<String> names) {
return names.stream().filter(n -> !n.endsWith("*")).collect(toCollection(HashSet::new));
}

private static List<String> getPrefixes(List<String> names) {
return names.stream().filter(n -> n.endsWith("*")).map(n -> n.substring(0, n.length() - 1))
.collect(toList());
}
}
}
Loading