Skip to content

Commit

Permalink
Add new configuration option to limit the size of string attribute va…
Browse files Browse the repository at this point in the history
…lues
  • Loading branch information
iNikem committed Jul 30, 2020
1 parent 78ed649 commit 99de8ea
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 1 deletion.
50 changes: 50 additions & 0 deletions api/src/main/java/io/opentelemetry/internal/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package io.opentelemetry.internal;

import io.opentelemetry.common.AttributeValue;
import java.util.List;
import javax.annotation.concurrent.Immutable;

/** Internal utility methods for working with attribute keys, attribute values, and metric names. */
Expand Down Expand Up @@ -57,5 +59,53 @@ public static boolean isValidMetricName(String metricName) {
return metricName.matches(pattern);
}

/**
* If given attribute is of type STRING and has more characters than given {@code limit} then
* return new AttributeValue with string truncated to {@code limit} characters.
*
* <p>If given attribute is of type STRING_ARRAY and non-empty then return new AttributeValue with
* every element truncated to {@code limit} characters.
*
* <p>Otherwise return given {@code value}
*
* @throws IllegalArgumentException if limit is zero or negative
*/
public static AttributeValue cutIfNeeded(AttributeValue value, int limit) {
Utils.checkArgument(limit > 0, "attribute value limit cannot be %d", limit);

if (value == null
|| (value.getType() != AttributeValue.Type.STRING
&& value.getType() != AttributeValue.Type.STRING_ARRAY)) {
return value;
}

if (value.getType() == AttributeValue.Type.STRING_ARRAY) {
List<String> strings = value.getStringArrayValue();
if (strings.isEmpty()) {
return value;
}

String[] newStrings = new String[strings.size()];
for (int i = 0; i < strings.size(); i++) {
String string = strings.get(i);
newStrings[i] = string == null ? null : cutIfNeeded(string, limit);
}

return AttributeValue.arrayAttributeValue(newStrings);
}

String string = value.getStringValue();
return string.length() <= limit
? value
: AttributeValue.stringAttributeValue(string.substring(0, limit));
}

private static String cutIfNeeded(String s, int limit) {
if (s == null || s.length() <= limit) {
return s;
}
return s.substring(0, limit);
}

private StringUtils() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.opentelemetry.common.Attributes;
import io.opentelemetry.common.ReadableAttributes;
import io.opentelemetry.common.ReadableKeyValuePairs.KeyValueConsumer;
import io.opentelemetry.internal.StringUtils;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
import io.opentelemetry.sdk.resources.Resource;
Expand Down Expand Up @@ -320,6 +321,11 @@ public void setAttribute(String key, AttributeValue value) {
if (attributes == null) {
attributes = new AttributesMap(traceConfig.getMaxNumberOfAttributes());
}

if (traceConfig.getMaxLengthOfAttributeValue() > 0) {
value = StringUtils.cutIfNeeded(value, traceConfig.getMaxLengthOfAttributeValue());
}

attributes.put(key, value);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.opentelemetry.common.Attributes;
import io.opentelemetry.common.ReadableAttributes;
import io.opentelemetry.common.ReadableKeyValuePairs.KeyValueConsumer;
import io.opentelemetry.internal.StringUtils;
import io.opentelemetry.internal.Utils;
import io.opentelemetry.sdk.common.Clock;
import io.opentelemetry.sdk.common.InstrumentationLibraryInfo;
Expand Down Expand Up @@ -190,6 +191,11 @@ public Span.Builder setAttribute(String key, AttributeValue value) {
if (attributes == null) {
attributes = new AttributesMap(traceConfig.getMaxNumberOfAttributes());
}

if (traceConfig.getMaxLengthOfAttributeValue() > 0) {
value = StringUtils.cutIfNeeded(value, traceConfig.getMaxLengthOfAttributeValue());
}

attributes.put(key, value);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
* {@link Event}.
* <li>{@code otel.config.max.link.attrs}: to set the global default max number of attributes per
* {@link Link}.
* <li>{@code otel.config.max.attr.length}: to set the global default max length of string
* attribute value in characters.
* </ul>
*
* <p>For environment variables, {@link TraceConfig} will look for the following names:
Expand All @@ -76,6 +78,8 @@
* {@link Event}.
* <li>{@code OTEL_CONFIG_MAX_LINK_ATTRS}: to set the global default max number of attributes per
* {@link Link}.
* <li>{@code OTEL_CONFIG_MAX_ATTR_LENGTH}: to set the global default max length of string
* attribute value in characters.
* </ul>
*/
@AutoValue
Expand All @@ -89,6 +93,7 @@ public abstract class TraceConfig {
private static final int DEFAULT_SPAN_MAX_NUM_LINKS = 32;
private static final int DEFAULT_SPAN_MAX_NUM_ATTRIBUTES_PER_EVENT = 32;
private static final int DEFAULT_SPAN_MAX_NUM_ATTRIBUTES_PER_LINK = 32;
private static final int DEFAULT_KEY_SPAN_ATTRIBUTE_MAX_VALUE_LENGTH = 0;

/**
* Returns the default {@code TraceConfig}.
Expand Down Expand Up @@ -144,6 +149,13 @@ public static TraceConfig getDefault() {
*/
public abstract int getMaxNumberOfAttributesPerLink();

/**
* Returns the global default max length of string attribute value in characters.
*
* @return the global default max length of string attribute value in characters.
*/
public abstract int getMaxLengthOfAttributeValue();

/**
* Returns a new {@link Builder}.
*
Expand All @@ -156,7 +168,8 @@ private static Builder newBuilder() {
.setMaxNumberOfEvents(DEFAULT_SPAN_MAX_NUM_EVENTS)
.setMaxNumberOfLinks(DEFAULT_SPAN_MAX_NUM_LINKS)
.setMaxNumberOfAttributesPerEvent(DEFAULT_SPAN_MAX_NUM_ATTRIBUTES_PER_EVENT)
.setMaxNumberOfAttributesPerLink(DEFAULT_SPAN_MAX_NUM_ATTRIBUTES_PER_LINK);
.setMaxNumberOfAttributesPerLink(DEFAULT_SPAN_MAX_NUM_ATTRIBUTES_PER_LINK)
.setMaxLengthOfAttributeValue(DEFAULT_KEY_SPAN_ATTRIBUTE_MAX_VALUE_LENGTH);
}

/**
Expand All @@ -176,6 +189,7 @@ public abstract static class Builder extends ConfigBuilder<Builder> {
private static final String KEY_SPAN_MAX_NUM_ATTRIBUTES_PER_EVENT =
"otel.config.max.event.attrs";
private static final String KEY_SPAN_MAX_NUM_ATTRIBUTES_PER_LINK = "otel.config.max.link.attrs";
private static final String KEY_SPAN_ATTRIBUTE_MAX_VALUE_LENGTH = "otel.config.max.attr.length";

Builder() {}

Expand Down Expand Up @@ -214,6 +228,10 @@ protected Builder fromConfigMap(
if (intValue != null) {
this.setMaxNumberOfAttributesPerLink(intValue);
}
intValue = getIntProperty(KEY_SPAN_ATTRIBUTE_MAX_VALUE_LENGTH, configMap);
if (intValue != null) {
this.setMaxLengthOfAttributeValue(intValue);
}
return this;
}

Expand Down Expand Up @@ -325,6 +343,16 @@ public Builder setSamplerProbability(double samplerProbability) {
*/
public abstract Builder setMaxNumberOfAttributesPerLink(int maxNumberOfAttributesPerLink);

/**
* Sets the global default max length of string attribute value in characters.
*
* @param maxLengthOfAttributeValue the global default max length of string attribute value in
* characters. It must be non-negative otherwise {@link #build()} will throw an exception.
* Value of 0 means no limit.
* @return this.
*/
public abstract Builder setMaxLengthOfAttributeValue(int maxLengthOfAttributeValue);

abstract TraceConfig autoBuild();

/**
Expand All @@ -343,6 +371,8 @@ public TraceConfig build() {
traceConfig.getMaxNumberOfAttributesPerEvent() > 0, "maxNumberOfAttributesPerEvent");
Preconditions.checkArgument(
traceConfig.getMaxNumberOfAttributesPerLink() > 0, "maxNumberOfAttributesPerLink");
Preconditions.checkArgument(
traceConfig.getMaxLengthOfAttributeValue() >= 0, "maxLengthOfAttributeValue");
return traceConfig;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,64 @@ public void droppingAttributes() {
}
}

@Test
public void tooLargeAttributeValuesAreTruncated() {
TraceConfig traceConfig =
tracerSdkFactory
.getActiveTraceConfig()
.toBuilder()
.setMaxLengthOfAttributeValue(10)
.build();
tracerSdkFactory.updateActiveTraceConfig(traceConfig);
Span.Builder spanBuilder = tracerSdk.spanBuilder(SPAN_NAME);
spanBuilder.setAttribute("builderStringSmall", "small");
spanBuilder.setAttribute("builderStringLarge", "very large string that we have to cut");
spanBuilder.setAttribute("builderLong", 42L);
spanBuilder.setAttribute(
"builderStringLargeValue",
AttributeValue.stringAttributeValue("very large string that we have to cut"));
spanBuilder.setAttribute(
"builderStringArray",
AttributeValue.arrayAttributeValue("small", null, "very large string that we have to cut"));

RecordEventsReadableSpan span = (RecordEventsReadableSpan) spanBuilder.startSpan();
span.setAttribute("spanStringSmall", "small");
span.setAttribute("spanStringLarge", "very large string that we have to cut");
span.setAttribute("spanLong", 42L);
span.setAttribute(
"spanStringLarge",
AttributeValue.stringAttributeValue("very large string that we have to cut"));
span.setAttribute(
"spanStringArray",
AttributeValue.arrayAttributeValue("small", null, "very large string that we have to cut"));

try {
ReadableAttributes attrs = span.toSpanData().getAttributes();
assertThat(attrs.get("builderStringSmall"))
.isEqualTo(AttributeValue.stringAttributeValue("small"));
assertThat(attrs.get("builderStringLarge"))
.isEqualTo(AttributeValue.stringAttributeValue("very large"));
assertThat(attrs.get("builderLong")).isEqualTo(AttributeValue.longAttributeValue(42L));
assertThat(attrs.get("builderStringLargeValue"))
.isEqualTo(AttributeValue.stringAttributeValue("very large"));
assertThat(attrs.get("builderStringArray"))
.isEqualTo(AttributeValue.arrayAttributeValue("small", null, "very large"));

assertThat(attrs.get("spanStringSmall"))
.isEqualTo(AttributeValue.stringAttributeValue("small"));
assertThat(attrs.get("spanStringLarge"))
.isEqualTo(AttributeValue.stringAttributeValue("very large"));
assertThat(attrs.get("spanLong")).isEqualTo(AttributeValue.longAttributeValue(42L));
assertThat(attrs.get("spanStringLarge"))
.isEqualTo(AttributeValue.stringAttributeValue("very large"));
assertThat(attrs.get("spanStringArray"))
.isEqualTo(AttributeValue.arrayAttributeValue("small", null, "very large"));
} finally {
span.end();
tracerSdkFactory.updateActiveTraceConfig(TraceConfig.getDefault());
}
}

@Test
public void addAttributes_OnlyViaSampler() {
TraceConfig traceConfig =
Expand Down

0 comments on commit 99de8ea

Please sign in to comment.