Skip to content

Commit f674299

Browse files
moscheKubik42
authored andcommitted
Deny usage of metric attributes known to cause issues. (elastic#136478)
Due to their mapping or cardinality, certain metric attributes are likely to cause issues. Such attribute names are denied by a new assertion Relates to ES-13074
1 parent 24c4817 commit f674299

File tree

5 files changed

+78
-1
lines changed

5 files changed

+78
-1
lines changed

modules/apm/NAMING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ Examples :
6464

6565
### Attributes
6666

67+
Do not use **high cardinality** attributes, this might result in the APM Java agent dropping events.
68+
6769
Attribute names should follow the same rules. In particular, these rules apply to attributes too:
6870
* elements and separators
6971
* hierarchy/namespaces

modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/MetricNameValidator.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@
99

1010
package org.elasticsearch.telemetry.apm.internal;
1111

12+
import org.apache.logging.log4j.LogManager;
13+
import org.elasticsearch.common.regex.Regex;
14+
15+
import java.util.Map;
1216
import java.util.Objects;
1317
import java.util.Set;
18+
import java.util.function.Predicate;
1419
import java.util.regex.Matcher;
1520
import java.util.regex.Pattern;
1621
import java.util.stream.Collectors;
@@ -40,6 +45,22 @@ public class MetricNameValidator {
4045
"security-crypto"
4146
);
4247

48+
// forbidden attributes known to cause issues due to mapping conflicts or high cardinality
49+
static final Predicate<String> FORBIDDEN_ATTRIBUTE_NAMES = Regex.simpleMatcher(
50+
"index",
51+
// below field names are typically mapped to a timestamp risking mapping errors at ingest time
52+
// if values are not valid timestamps (which would be of high cardinality, and not desired either)
53+
"*.timestamp",
54+
"*_timestamp",
55+
"created",
56+
"*.created",
57+
"*.creation_date",
58+
"ingested",
59+
"*.ingested",
60+
"*.start",
61+
"*.end"
62+
);
63+
4364
private MetricNameValidator() {}
4465

4566
/**
@@ -65,6 +86,20 @@ public static String validate(String metricName) {
6586
return metricName;
6687
}
6788

89+
public static boolean validateAttributeNames(Map<String, Object> attributes) {
90+
if (attributes == null && attributes.isEmpty()) {
91+
return true;
92+
}
93+
for (String attribute : attributes.keySet()) {
94+
if (FORBIDDEN_ATTRIBUTE_NAMES.test(attribute)) {
95+
LogManager.getLogger(MetricNameValidator.class)
96+
.warn("Attribute name [{}] is forbidden due to potential mapping conflicts or assumed high cardinality", attribute);
97+
return false;
98+
}
99+
}
100+
return true;
101+
}
102+
68103
/**
69104
* Due to backwards compatibility some metric names would have to skip validation.
70105
* This is for instance where a threadpool name is too long, or contains `-`

modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelHelper.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import org.apache.logging.log4j.LogManager;
1717
import org.apache.logging.log4j.Logger;
18+
import org.elasticsearch.telemetry.apm.internal.MetricNameValidator;
1819
import org.elasticsearch.telemetry.metric.DoubleWithAttributes;
1920
import org.elasticsearch.telemetry.metric.LongWithAttributes;
2021

@@ -30,6 +31,7 @@ static Attributes fromMap(Map<String, Object> attributes) {
3031
if (attributes == null || attributes.isEmpty()) {
3132
return Attributes.empty();
3233
}
34+
assert MetricNameValidator.validateAttributeNames(attributes) : "invalid metric attributes";
3335
var builder = Attributes.builder();
3436
attributes.forEach((k, v) -> {
3537
if (v instanceof String value) {

modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/AsyncCountersAdapterTests.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,26 @@ public void testDoubleAsyncAdapter() throws Exception {
101101
assertThat(metrics, hasSize(0));
102102
}
103103

104-
public void testNullGaugeRecord() throws Exception {
104+
public void testLongWithInvalidAttribute() {
105+
registry.registerLongAsyncCounter("es.test.name.total", "desc", "unit", () -> new LongWithAttributes(1, Map.of("index", "index1")));
106+
107+
AssertionError error = assertThrows(AssertionError.class, otelMeter::collectMetrics);
108+
assertThat(error.getMessage(), equalTo("invalid metric attributes"));
109+
}
110+
111+
public void testDoubleWithInvalidAttribute() {
112+
registry.registerDoubleAsyncCounter(
113+
"es.test.name.total",
114+
"desc",
115+
"unit",
116+
() -> new DoubleWithAttributes(1.0, Map.of("has_timestamp", "false"))
117+
);
118+
119+
AssertionError error = assertThrows(AssertionError.class, otelMeter::collectMetrics);
120+
assertThat(error.getMessage(), equalTo("invalid metric attributes"));
121+
}
122+
123+
public void testNullRecord() throws Exception {
105124
DoubleAsyncCounter dcounter = registry.registerDoubleAsyncCounter(
106125
"es.test.name.total",
107126
"desc",

modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/GaugeAdapterTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,23 @@ public void testNullGaugeRecord() throws Exception {
118118
metrics = otelMeter.getRecorder().getMeasurements(lgauge);
119119
assertThat(metrics, hasSize(0));
120120
}
121+
122+
public void testLongGaugeWithInvalidAttribute() {
123+
registry.registerLongGauge("es.test.name.total", "desc", "unit", () -> new LongWithAttributes(1, Map.of("index", "index1")));
124+
125+
AssertionError error = assertThrows(AssertionError.class, otelMeter::collectMetrics);
126+
assertThat(error.getMessage(), equalTo("invalid metric attributes"));
127+
}
128+
129+
public void testDoubleGaugeWithInvalidAttribute() {
130+
registry.registerDoubleGauge(
131+
"es.test.name.total",
132+
"desc",
133+
"unit",
134+
() -> new DoubleWithAttributes(1.0, Map.of("has_timestamp", "false"))
135+
);
136+
137+
AssertionError error = assertThrows(AssertionError.class, otelMeter::collectMetrics);
138+
assertThat(error.getMessage(), equalTo("invalid metric attributes"));
139+
}
121140
}

0 commit comments

Comments
 (0)