Skip to content

Commit 3418b26

Browse files
Allow metric filtering by name
Implementation of a decorator pattern filtering the metrics data by the configured names. Includes are applied before excludes. This change filters only by explicit name. Further commits introducing wildcards and documentation to follow. Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
1 parent 59c147e commit 3418b26

4 files changed

Lines changed: 301 additions & 17 deletions

File tree

cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/CloudLoggingMetricsExporterProvider.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
public class CloudLoggingMetricsExporterProvider implements ConfigurableMetricExporterProvider {
2929

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

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

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

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

5557
private static AggregationTemporalitySelector getAggregationTemporalitySelector(ConfigProperties config) {
56-
String temporalityStr = config.getString("otel.exporter.cloud-logging.metrics.temporality.preference");
58+
String temporalityStr = config.getString(METRICS_CONFIG_PREFIX + "temporality.preference");
5759
if (temporalityStr == null) {
5860
return AggregationTemporalitySelector.alwaysCumulative();
5961
}
@@ -71,8 +73,7 @@ private static AggregationTemporalitySelector getAggregationTemporalitySelector(
7173
}
7274

7375
private static DefaultAggregationSelector getDefaultAggregationSelector(ConfigProperties config) {
74-
String defaultHistogramAggregation =
75-
config.getString("otel.exporter.cloud-logging.metrics.default.histogram.aggregation");
76+
String defaultHistogramAggregation = config.getString(METRICS_CONFIG_PREFIX + "default.histogram.aggregation");
7677
if (defaultHistogramAggregation == null) {
7778
return DefaultAggregationSelector.getDefault()
7879
.with(InstrumentType.HISTOGRAM, Aggregation.defaultAggregation());
@@ -101,8 +102,11 @@ public MetricExporter createExporter(ConfigProperties config) {
101102
List<MetricExporter> exporters = servicesProvider.apply(config).map(svc -> createExporter(config, svc))
102103
.filter(exp -> !(exp instanceof NoopMetricExporter))
103104
.collect(Collectors.toList());
104-
return MultiMetricExporter.composite(exporters, getAggregationTemporalitySelector(config),
105-
getDefaultAggregationSelector(config));
105+
MetricExporter exporter = MultiMetricExporter.composite(exporters, getAggregationTemporalitySelector(config),
106+
getDefaultAggregationSelector(config));
107+
exporter = FilteringMetricExporter.wrap(exporter).withConfig(config).withPropertyPrefix(METRICS_CONFIG_PREFIX)
108+
.build();
109+
return exporter;
106110
}
107111

108112
private MetricExporter createExporter(ConfigProperties config, CloudFoundryServiceInstance service) {

cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/DynatraceMetricsExporterProvider.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ public class DynatraceMetricsExporterProvider implements ConfigurableMetricExpor
2828

2929
public static final String CRED_DYNATRACE_APIURL = "apiurl";
3030
public static final String DT_APIURL_METRICS_SUFFIX = "/v2/otlp/v1/metrics";
31+
32+
private static final String GENERIC_CONFIG_PREFIX = "otel.exporter.dynatrace.";
33+
private static final String METRICS_CONFIG_PREFIX = "otel.exporter.dynatrace.metrics.";
34+
3135
private static final Logger LOG = Logger.getLogger(DynatraceMetricsExporterProvider.class.getName());
3236
private static final AggregationTemporalitySelector ALWAYS_DELTA = instrumentType -> AggregationTemporality.DELTA;
3337
private final Function<ConfigProperties, CloudFoundryServiceInstance> serviceProvider;
@@ -41,17 +45,17 @@ public DynatraceMetricsExporterProvider(Function<ConfigProperties, CloudFoundryS
4145
}
4246

4347
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");
48+
String compression = config.getString(METRICS_CONFIG_PREFIX + "compression");
49+
return compression != null ? compression : config.getString(GENERIC_CONFIG_PREFIX + "compression", "gzip");
4650
}
4751

4852
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");
53+
Duration timeout = config.getDuration(METRICS_CONFIG_PREFIX + "timeout");
54+
return timeout != null ? timeout : config.getDuration(GENERIC_CONFIG_PREFIX + "timeout");
5155
}
5256

5357
private static AggregationTemporalitySelector getAggregationTemporalitySelector(ConfigProperties config) {
54-
String temporalityStr = config.getString("otel.exporter.dynatrace.metrics.temporality.preference");
58+
String temporalityStr = config.getString(METRICS_CONFIG_PREFIX + "temporality.preference");
5559
if (temporalityStr == null) {
5660
return ALWAYS_DELTA;
5761
}
@@ -71,8 +75,7 @@ private static AggregationTemporalitySelector getAggregationTemporalitySelector(
7175
}
7276

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

148151
LOG.info(
149152
"Created metrics exporter for service binding " + cfService.getName() + " (" + cfService.getLabel() + ")");
150-
return builder.build();
153+
MetricExporter exporter = builder.build();
154+
return FilteringMetricExporter.wrap(exporter).withConfig(config).withPropertyPrefix(METRICS_CONFIG_PREFIX)
155+
.build();
151156
}
152157

153158
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter;
2+
3+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
4+
import io.opentelemetry.sdk.common.CompletableResultCode;
5+
import io.opentelemetry.sdk.common.export.MemoryMode;
6+
import io.opentelemetry.sdk.metrics.Aggregation;
7+
import io.opentelemetry.sdk.metrics.InstrumentType;
8+
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
9+
import io.opentelemetry.sdk.metrics.data.MetricData;
10+
import io.opentelemetry.sdk.metrics.export.MetricExporter;
11+
12+
import java.util.Collection;
13+
import java.util.List;
14+
import java.util.function.Predicate;
15+
import java.util.stream.Collectors;
16+
17+
public class FilteringMetricExporter implements MetricExporter {
18+
19+
private static final String INCLUDED_NAMES_KEY = "include.names";
20+
private static final String EXCLUDED_NAMES_KEY = "exclude.names";
21+
22+
private final MetricExporter delegate;
23+
private final Predicate<MetricData> predicate;
24+
25+
private FilteringMetricExporter(MetricExporter delegate, Predicate<MetricData> predicate) {
26+
this.delegate = delegate;
27+
this.predicate = predicate;
28+
}
29+
30+
@Override
31+
public CompletableResultCode export(Collection<MetricData> collection) {
32+
List<MetricData> filteredMetrics = collection.stream().filter(predicate).collect(Collectors.toList());
33+
return delegate.export(filteredMetrics);
34+
}
35+
36+
@Override
37+
public CompletableResultCode flush() {
38+
return delegate.flush();
39+
}
40+
41+
@Override
42+
public CompletableResultCode shutdown() {
43+
return delegate.shutdown();
44+
}
45+
46+
@Override
47+
public void close() {
48+
delegate.close();
49+
}
50+
51+
@Override
52+
public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
53+
return delegate.getAggregationTemporality(instrumentType);
54+
}
55+
56+
@Override
57+
public Aggregation getDefaultAggregation(InstrumentType instrumentType) {
58+
return delegate.getDefaultAggregation(instrumentType);
59+
}
60+
61+
@Override
62+
public MemoryMode getMemoryMode() {
63+
return delegate.getMemoryMode();
64+
}
65+
66+
public static Builder wrap(MetricExporter delegate) {
67+
return new Builder(delegate);
68+
}
69+
70+
public static class Builder {
71+
72+
private final MetricExporter delegate;
73+
private ConfigProperties config;
74+
private String prefix = "";
75+
76+
public Builder(MetricExporter delegate) {
77+
this.delegate = delegate;
78+
}
79+
80+
public Builder withConfig(ConfigProperties config) {
81+
this.config = config;
82+
return this;
83+
}
84+
85+
public Builder withPropertyPrefix(String prefix) {
86+
this.prefix = prefix.endsWith(".") ? prefix : prefix + ".";
87+
return this;
88+
}
89+
90+
public MetricExporter build() {
91+
if (config == null) {
92+
return delegate;
93+
}
94+
95+
List<String> includedNames = config.getList(prefix + INCLUDED_NAMES_KEY);
96+
List<String> excludedNames = config.getList(prefix + EXCLUDED_NAMES_KEY);
97+
if (includedNames.isEmpty() && excludedNames.isEmpty()) {
98+
return delegate;
99+
}
100+
101+
Predicate<MetricData> predicate = metricData -> true;
102+
if (!includedNames.isEmpty()) {
103+
predicate = predicate.and(metricData -> includedNames.contains(metricData.getName()));
104+
}
105+
if (!excludedNames.isEmpty()) {
106+
predicate = predicate.and(metricData -> !excludedNames.contains(metricData.getName()));
107+
}
108+
return new FilteringMetricExporter(delegate, predicate);
109+
}
110+
}
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter;
2+
3+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
4+
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
5+
import io.opentelemetry.sdk.common.export.MemoryMode;
6+
import io.opentelemetry.sdk.metrics.Aggregation;
7+
import io.opentelemetry.sdk.metrics.InstrumentType;
8+
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
9+
import io.opentelemetry.sdk.metrics.data.MetricData;
10+
import io.opentelemetry.sdk.metrics.export.MetricExporter;
11+
import org.assertj.core.data.MapEntry;
12+
import org.junit.jupiter.api.BeforeEach;
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.ExtendWith;
15+
import org.mockito.ArgumentCaptor;
16+
import org.mockito.Captor;
17+
import org.mockito.Mock;
18+
import org.mockito.junit.jupiter.MockitoExtension;
19+
20+
import java.util.HashMap;
21+
import java.util.List;
22+
23+
import static io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties.createFromMap;
24+
import static java.util.Arrays.asList;
25+
import static java.util.Collections.emptyMap;
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.mockito.Mock.Strictness.LENIENT;
28+
import static org.mockito.Mockito.verify;
29+
import static org.mockito.Mockito.when;
30+
31+
@ExtendWith(MockitoExtension.class)
32+
class FilteringMetricExporterTest {
33+
34+
@Mock
35+
private MetricExporter delegate;
36+
37+
@Mock(strictness = LENIENT)
38+
private MetricData includedMetric;
39+
40+
@Mock(strictness = LENIENT)
41+
private MetricData excludedMetric;
42+
43+
@Mock(strictness = LENIENT)
44+
private MetricData anotherMetric;
45+
46+
@Captor
47+
ArgumentCaptor<List<MetricData>> exported;
48+
49+
@BeforeEach
50+
void setUp() {
51+
when(includedMetric.getName()).thenReturn("included");
52+
when(excludedMetric.getName()).thenReturn("excluded");
53+
when(anotherMetric.getName()).thenReturn("another");
54+
}
55+
56+
@Test
57+
void exportsAllWithoutConfig() {
58+
try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).build()) {
59+
exporter.export(asList(includedMetric, excludedMetric, anotherMetric));
60+
}
61+
verify(delegate).export(exported.capture());
62+
assertThat(exported.getValue()).containsExactlyInAnyOrder(includedMetric, excludedMetric, anotherMetric);
63+
}
64+
65+
@Test
66+
void exportsAllWithEmptyConfig() {
67+
DefaultConfigProperties config = createFromMap(emptyMap());
68+
try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).withConfig(config).build()) {
69+
exporter.export(asList(includedMetric, excludedMetric, anotherMetric));
70+
}
71+
verify(delegate).export(exported.capture());
72+
assertThat(exported.getValue()).containsExactlyInAnyOrder(includedMetric, excludedMetric, anotherMetric);
73+
}
74+
75+
@Test
76+
void exportsOnlyIncluded() {
77+
ConfigProperties config = createConfig(MapEntry.entry("include.names", "included"));
78+
try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).withConfig(config).build()) {
79+
exporter.export(asList(includedMetric, excludedMetric, anotherMetric));
80+
}
81+
verify(delegate).export(exported.capture());
82+
assertThat(exported.getValue()).containsExactlyInAnyOrder(includedMetric);
83+
}
84+
85+
@Test
86+
void rejectsEncluded() {
87+
ConfigProperties config = createConfig(MapEntry.entry("exclude.names", "excluded"));
88+
try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).withConfig(config).build()) {
89+
exporter.export(asList(includedMetric, excludedMetric, anotherMetric));
90+
}
91+
verify(delegate).export(exported.capture());
92+
assertThat(exported.getValue()).containsExactlyInAnyOrder(includedMetric, anotherMetric);
93+
}
94+
95+
@Test
96+
void rejectsExcludedFromIncluded() {
97+
ConfigProperties config = createConfig(MapEntry.entry("include.names", "included,excluded"),
98+
MapEntry.entry("exclude.names", "excluded"));
99+
try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).withConfig(config).build()) {
100+
exporter.export(asList(includedMetric, excludedMetric, anotherMetric));
101+
}
102+
verify(delegate).export(exported.capture());
103+
assertThat(exported.getValue()).containsExactlyInAnyOrder(includedMetric);
104+
}
105+
106+
@SafeVarargs
107+
private static ConfigProperties createConfig(MapEntry<String, String>... entries) {
108+
HashMap<String, String> map = new HashMap<>();
109+
for (MapEntry<String, String> entry: entries) {
110+
map.put(entry.key, entry.value);
111+
}
112+
return createFromMap(map);
113+
}
114+
115+
@Test
116+
void close() {
117+
try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).build()) {
118+
// nothing to do
119+
}
120+
verify(delegate).close();
121+
}
122+
123+
@Test
124+
void flush() {
125+
try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).build()) {
126+
exporter.flush();
127+
}
128+
verify(delegate).flush();
129+
}
130+
131+
@Test
132+
void shutdown() {
133+
try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).build()) {
134+
exporter.shutdown();
135+
}
136+
verify(delegate).shutdown();
137+
}
138+
139+
@Test
140+
void getAggregationTemporality() {
141+
when(delegate.getAggregationTemporality(InstrumentType.COUNTER)).thenReturn(AggregationTemporality.DELTA);
142+
try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).build()) {
143+
assertThat(exporter.getAggregationTemporality(InstrumentType.COUNTER)).isEqualTo(
144+
AggregationTemporality.DELTA);
145+
}
146+
}
147+
148+
@Test
149+
void getDefaultAggregation() {
150+
when(delegate.getDefaultAggregation(InstrumentType.COUNTER)).thenReturn(Aggregation.defaultAggregation());
151+
try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).build()) {
152+
assertThat(exporter.getDefaultAggregation(InstrumentType.COUNTER)).isEqualTo(
153+
Aggregation.defaultAggregation());
154+
}
155+
}
156+
157+
@Test
158+
void getMemoryMode() {
159+
when(delegate.getMemoryMode()).thenReturn(MemoryMode.IMMUTABLE_DATA);
160+
try (MetricExporter exporter = FilteringMetricExporter.wrap(delegate).build()) {
161+
assertThat(exporter.getMemoryMode()).isEqualTo(MemoryMode.IMMUTABLE_DATA);
162+
}
163+
}
164+
}

0 commit comments

Comments
 (0)