Skip to content

Commit 52bb9fa

Browse files
authored
Fix OTLP marshaling for empty string attributes (#8014)
1 parent 3789507 commit 52bb9fa

6 files changed

Lines changed: 82 additions & 12 deletions

File tree

exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/Serializer.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,16 @@ public void serializeStringWithContext(
257257
if (string == null || string.isEmpty()) {
258258
return;
259259
}
260+
writeStringWithContext(field, string, context);
261+
}
262+
263+
/**
264+
* Writes a protobuf {@code string} field, even if it matches the default value. This method reads
265+
* elements from context, use together with {@link StatelessMarshalerUtil#getUtf8Size(String,
266+
* MarshalerContext)}.
267+
*/
268+
public void writeStringWithContext(ProtoFieldInfo field, String string, MarshalerContext context)
269+
throws IOException {
260270
if (context.marshalStringNoAllocation()) {
261271
writeString(field, string, context.getSize(), context);
262272
} else {

exporters/common/src/main/java/io/opentelemetry/exporter/internal/marshal/StatelessMarshalerUtil.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,22 @@ public static int sizeStringWithContext(
9393
if (value == null || value.isEmpty()) {
9494
return sizeBytes(field, 0);
9595
}
96+
return sizeBytes(field, getUtf8Size(value, context));
97+
}
98+
99+
/**
100+
* Returns the UTF-8 size of a string, adding data to the context for later serialization. Use
101+
* together with {@link Serializer#writeString(ProtoFieldInfo, String, int, MarshalerContext)}.
102+
*/
103+
public static int getUtf8Size(String value, MarshalerContext context) {
96104
if (context.marshalStringNoAllocation()) {
97105
int utf8Size = context.getStringEncoder().getUtf8Size(value);
98106
context.addSize(utf8Size);
99-
return sizeBytes(field, utf8Size);
107+
return utf8Size;
100108
} else {
101109
byte[] valueUtf8 = MarshalerUtil.toBytes(value);
102110
context.addData(valueUtf8);
103-
return sizeBytes(field, valueUtf8.length);
111+
return valueUtf8.length;
104112
}
105113
}
106114

exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/StringAnyValueMarshaler.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,10 @@ static MarshalerWithSize create(String value) {
3333

3434
@Override
3535
public void writeTo(Serializer output) throws IOException {
36-
if (valueUtf8.length == 0) {
37-
return;
38-
}
3936
output.writeString(AnyValue.STRING_VALUE, valueUtf8);
4037
}
4138

4239
private static int calculateSize(byte[] valueUtf8) {
43-
if (valueUtf8.length == 0) {
44-
return 0;
45-
}
4640
return AnyValue.STRING_VALUE.getTagSize()
4741
+ CodedOutputStream.computeByteArraySizeNoTag(valueUtf8);
4842
}

exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/StringAnyValueStatelessMarshaler.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package io.opentelemetry.exporter.internal.otlp;
77

8+
import io.opentelemetry.exporter.internal.marshal.CodedOutputStream;
89
import io.opentelemetry.exporter.internal.marshal.MarshalerContext;
910
import io.opentelemetry.exporter.internal.marshal.Serializer;
1011
import io.opentelemetry.exporter.internal.marshal.StatelessMarshaler;
@@ -26,11 +27,14 @@ private StringAnyValueStatelessMarshaler() {}
2627
@Override
2728
public void writeTo(Serializer output, String value, MarshalerContext context)
2829
throws IOException {
29-
output.serializeStringWithContext(AnyValue.STRING_VALUE, value, context);
30+
output.writeStringWithContext(AnyValue.STRING_VALUE, value, context);
3031
}
3132

3233
@Override
3334
public int getBinarySerializedSize(String value, MarshalerContext context) {
34-
return StatelessMarshalerUtil.sizeStringWithContext(AnyValue.STRING_VALUE, value, context);
35+
int utf8Size = StatelessMarshalerUtil.getUtf8Size(value, context);
36+
return AnyValue.STRING_VALUE.getTagSize()
37+
+ CodedOutputStream.computeUInt32SizeNoTag(utf8Size)
38+
+ utf8Size;
3539
}
3640
}

exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/ValueMarshalerTest.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,23 @@ private static Stream<Arguments> serializeAnyValueArgs() {
123123
of("hello world".getBytes(StandardCharsets.UTF_8)),
124124
AnyValue.newBuilder()
125125
.setBytesValue(ByteString.copyFrom("hello world".getBytes(StandardCharsets.UTF_8)))
126-
.build()));
126+
.build()),
127+
// empty values
128+
arguments(of(""), AnyValue.newBuilder().setStringValue("").build()),
129+
arguments(of(false), AnyValue.newBuilder().setBoolValue(false).build()),
130+
arguments(of(0), AnyValue.newBuilder().setIntValue(0).build()),
131+
arguments(of(0.0), AnyValue.newBuilder().setDoubleValue(0.0).build()),
132+
arguments(
133+
Value.of(Collections.emptyList()),
134+
AnyValue.newBuilder().setArrayValue(ArrayValue.newBuilder().build()).build()),
135+
arguments(
136+
of(Collections.emptyMap()),
137+
AnyValue.newBuilder().setKvlistValue(KeyValueList.newBuilder().build()).build()),
138+
arguments(of(new byte[0]), AnyValue.newBuilder().setBytesValue(ByteString.EMPTY).build())
139+
// TODO add test for true empty value
140+
// after https://github.com/open-telemetry/opentelemetry-java/pull/7973
141+
// arguments(Value.empty(), AnyValue.newBuilder().build())
142+
);
127143
}
128144

129145
@SuppressWarnings("unchecked")

exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/traces/TraceRequestMarshalerTest.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,15 @@ void toProtoSpan(MarshalerSource marshalerSource) {
157157
"nested", Value.of("value"))))
158158
.put("heterogeneousArray", Value.of(Value.of("string"), Value.of(123L)))
159159
.put("empty", Value.empty())
160+
.put("empty_string", "")
161+
.put("false_value", false)
162+
.put("zero_int", 0L)
163+
.put("zero_double", 0.0)
164+
.put("empty_array", Value.of(Collections.emptyList()))
165+
.put("empty_map", Value.of(Collections.emptyMap()))
166+
.put("empty_bytes", Value.of(new byte[] {}))
160167
.build())
161-
.setTotalAttributeCount(13)
168+
.setTotalAttributeCount(20)
162169
.setEvents(
163170
Collections.singletonList(
164171
EventData.create(12347, "my_event", Attributes.empty())))
@@ -275,6 +282,37 @@ void toProtoSpan(MarshalerSource marshalerSource) {
275282
.build())
276283
.build())
277284
.build())
285+
.build(),
286+
KeyValue.newBuilder()
287+
.setKey("empty_string")
288+
.setValue(AnyValue.newBuilder().setStringValue("").build())
289+
.build(),
290+
KeyValue.newBuilder()
291+
.setKey("false_value")
292+
.setValue(AnyValue.newBuilder().setBoolValue(false).build())
293+
.build(),
294+
KeyValue.newBuilder()
295+
.setKey("zero_int")
296+
.setValue(AnyValue.newBuilder().setIntValue(0).build())
297+
.build(),
298+
KeyValue.newBuilder()
299+
.setKey("zero_double")
300+
.setValue(AnyValue.newBuilder().setDoubleValue(0.0).build())
301+
.build(),
302+
KeyValue.newBuilder()
303+
.setKey("empty_array")
304+
.setValue(
305+
AnyValue.newBuilder().setArrayValue(ArrayValue.newBuilder().build()).build())
306+
.build(),
307+
KeyValue.newBuilder()
308+
.setKey("empty_map")
309+
.setValue(
310+
AnyValue.newBuilder().setKvlistValue(KeyValueList.newBuilder().build()).build())
311+
.build(),
312+
KeyValue.newBuilder()
313+
.setKey("empty_bytes")
314+
.setValue(
315+
AnyValue.newBuilder().setBytesValue(ByteString.copyFrom(new byte[] {})).build())
278316
.build());
279317
assertThat(protoSpan.getDroppedAttributesCount()).isEqualTo(1);
280318
assertThat(protoSpan.getEventsList())

0 commit comments

Comments
 (0)