Skip to content

Commit da4bf48

Browse files
committed
serialize KeyValue lists
1 parent 1f68cce commit da4bf48

7 files changed

Lines changed: 419 additions & 0 deletions

File tree

src/OpenTelemetry.Exporter.Console/Implementation/ConsoleTagWriter.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics;
55
using System.Globalization;
66
using System.Text;
7+
using Microsoft.Extensions.Primitives;
78
using OpenTelemetry.Internal;
89

910
namespace OpenTelemetry.Exporter;
@@ -85,6 +86,18 @@ protected override bool TryWriteEmptyTag(ref ConsoleTag consoleTag, string key,
8586
return true;
8687
}
8788

89+
protected override void WriteKvListTag(ref ConsoleTag state, string key, IEnumerable<KeyValuePair<string, object?>> kvList, int? tagValueMaxLength)
90+
{
91+
var stringValue = Convert.ToString(kvList, CultureInfo.InvariantCulture);
92+
93+
if (stringValue is null)
94+
{
95+
return;
96+
}
97+
98+
this.TryWriteTag(ref state, key, stringValue);
99+
}
100+
88101
internal struct ConsoleTag
89102
{
90103
public string? Key;

src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpCommonFieldNumberConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ internal static class ProtobufOtlpCommonFieldNumberConstants
2929
internal const int AnyValue_Bytes_Value = 7;
3030

3131
internal const int ArrayValue_Value = 1;
32+
33+
internal const int KeyValueList_Values = 1;
3234
}

src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/Serializer/ProtobufOtlpTagWriter.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer
77

88
internal sealed class ProtobufOtlpTagWriter : TagWriter<ProtobufOtlpTagWriter.OtlpTagWriterState, ProtobufOtlpTagWriter.OtlpTagWriterArrayState>
99
{
10+
private const int ReserveSizeForLength = 4;
11+
1012
private ProtobufOtlpTagWriter()
1113
: base(new OtlpArrayTagWriter())
1214
{
@@ -101,6 +103,55 @@ protected override bool TryWriteByteArrayTag(ref OtlpTagWriterState state, strin
101103
return true;
102104
}
103105

106+
protected override void WriteKvListTag(ref OtlpTagWriterState state, string key, IEnumerable<KeyValuePair<string, object?>> kvList, int? tagValueMaxLength)
107+
{
108+
var startPosition = state.WritePosition;
109+
110+
state.WritePosition = ProtobufSerializer.WriteStringWithTag(state.Buffer, state.WritePosition, ProtobufOtlpCommonFieldNumberConstants.KeyValue_Key, key);
111+
112+
// Reserve space for KeyValue.value (AnyValue wrapper)
113+
state.WritePosition = ProtobufSerializer.WriteTag(state.Buffer, state.WritePosition, ProtobufOtlpCommonFieldNumberConstants.KeyValue_Value, ProtobufWireType.LEN);
114+
int anyValueLengthPos = state.WritePosition;
115+
state.WritePosition += ReserveSizeForLength;
116+
117+
// Reserve space for AnyValue.kvlist_value (KeyValueList wrapper)
118+
state.WritePosition = ProtobufSerializer.WriteTag(state.Buffer, state.WritePosition, ProtobufOtlpCommonFieldNumberConstants.AnyValue_Kvlist_Value, ProtobufWireType.LEN);
119+
int kvlistLengthPos = state.WritePosition;
120+
state.WritePosition += ReserveSizeForLength;
121+
122+
try
123+
{
124+
foreach (var kvp in kvList)
125+
{
126+
var startEntryPosition = state.WritePosition;
127+
128+
// Write KeyValueList.values tag (field 1, LEN) + reserve length per entry
129+
state.WritePosition = ProtobufSerializer.WriteTag(state.Buffer, state.WritePosition, ProtobufOtlpCommonFieldNumberConstants.KeyValueList_Values, ProtobufWireType.LEN);
130+
int entryLengthPos = state.WritePosition;
131+
state.WritePosition += ReserveSizeForLength;
132+
133+
// Drop a tag if we fail to write it, and reset the position to the start of the entry to overwrite the reserved tag and length for the next entry.
134+
if (!this.TryWriteTag(ref state, kvp.Key, kvp.Value, tagValueMaxLength))
135+
{
136+
state.WritePosition = startEntryPosition;
137+
continue;
138+
}
139+
140+
ProtobufSerializer.WriteReservedLength(state.Buffer, entryLengthPos, state.WritePosition - (entryLengthPos + ReserveSizeForLength));
141+
}
142+
}
143+
catch (Exception)
144+
{
145+
state.WritePosition = startPosition;
146+
147+
throw;
148+
}
149+
150+
// Fill in reserved lengths
151+
ProtobufSerializer.WriteReservedLength(state.Buffer, kvlistLengthPos, state.WritePosition - (kvlistLengthPos + ReserveSizeForLength));
152+
ProtobufSerializer.WriteReservedLength(state.Buffer, anyValueLengthPos, state.WritePosition - (anyValueLengthPos + ReserveSizeForLength));
153+
}
154+
104155
internal struct OtlpTagWriterState
105156
{
106157
public byte[] Buffer;

src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
using System.Buffers.Text;
5+
using System.Collections;
56
using System.Globalization;
67
using System.Text.Json;
78
using OpenTelemetry.Internal;
@@ -66,4 +67,16 @@ protected override void OnUnsupportedTagDropped(
6667
}
6768

6869
protected override bool TryWriteEmptyTag(ref Utf8JsonWriter state, string key, object? value) => false;
70+
71+
protected override void WriteKvListTag(ref Utf8JsonWriter writer, string key, IEnumerable<KeyValuePair<string, object?>> value, int? tagValueMaxLength)
72+
{
73+
var stringValue = Convert.ToString(value, CultureInfo.InvariantCulture);
74+
75+
if (stringValue is null)
76+
{
77+
return;
78+
}
79+
80+
this.TryWriteTag(ref writer, key, stringValue);
81+
}
6982
}

src/Shared/TagWriter/TagWriter.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,22 @@ public bool TryWriteTag(
6464
case double:
6565
this.WriteFloatingPointTag(ref state, key, Convert.ToDouble(value, CultureInfo.InvariantCulture));
6666
break;
67+
case IEnumerable<KeyValuePair<string, object?>> kvList:
68+
try
69+
{
70+
this.WriteKvListTag(ref state, key, kvList, tagValueMaxLength);
71+
}
72+
catch (Exception ex) when (ex is IndexOutOfRangeException or ArgumentException)
73+
{
74+
throw;
75+
}
76+
catch
77+
{
78+
return this.LogUnsupportedTagTypeAndReturnFalse(key, value);
79+
}
80+
81+
break;
82+
6783
case Array array:
6884
if (value.GetType() == typeof(byte[]) && this.TryWriteByteArrayTag(ref state, key, ((byte[])value).AsSpan()))
6985
{
@@ -134,6 +150,8 @@ public bool TryWriteTag(
134150

135151
protected abstract void WriteArrayTag(ref TTagState state, string key, ref TArrayState value);
136152

153+
protected abstract void WriteKvListTag(ref TTagState state, string key, IEnumerable<KeyValuePair<string, object?>> kvList, int? tagValueMaxLength);
154+
137155
protected abstract void OnUnsupportedTagDropped(
138156
string tagKey,
139157
string tagValueTypeFullName);

0 commit comments

Comments
 (0)