[shared] Remove TagTransformer and improve TagWriter (#5602)

This commit is contained in:
Mikel Blanchard 2024-05-14 11:44:41 -07:00 committed by GitHub
parent a578ed3f9b
commit cf8ca09a55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 418 additions and 470 deletions

View File

@ -259,8 +259,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A49299
src\Shared\SemanticConventions.cs = src\Shared\SemanticConventions.cs
src\Shared\SpanAttributeConstants.cs = src\Shared\SpanAttributeConstants.cs
src\Shared\StatusHelper.cs = src\Shared\StatusHelper.cs
src\Shared\TagTransformer.cs = src\Shared\TagTransformer.cs
src\Shared\TagTransformerJsonHelper.cs = src\Shared\TagTransformerJsonHelper.cs
src\Shared\ThreadSafeRandom.cs = src\Shared\ThreadSafeRandom.cs
EndProjectSection
EndProject

View File

@ -51,10 +51,10 @@ internal sealed class ConsoleTagWriter : JsonStringArrayTagWriter<ConsoleTagWrit
consoleTag.Value = value ? "true" : "false";
}
protected override void WriteStringTag(ref ConsoleTag consoleTag, string key, string value)
protected override void WriteStringTag(ref ConsoleTag consoleTag, string key, ReadOnlySpan<char> value)
{
consoleTag.Key = key;
consoleTag.Value = value;
consoleTag.Value = value.ToString();
}
protected override void WriteArrayTag(ref ConsoleTag consoleTag, string key, ArraySegment<byte> arrayUtf8JsonBytes)

View File

@ -45,9 +45,9 @@ internal sealed class OtlpTagWriter : TagWriter<RepeatedField<OtlpCommon.KeyValu
tags.Add(new OtlpCommon.KeyValue { Key = key, Value = ToAnyValue(value) });
}
protected override void WriteStringTag(ref RepeatedField<OtlpCommon.KeyValue> tags, string key, string value)
protected override void WriteStringTag(ref RepeatedField<OtlpCommon.KeyValue> tags, string key, ReadOnlySpan<char> value)
{
tags.Add(new OtlpCommon.KeyValue { Key = key, Value = ToAnyValue(value) });
tags.Add(new OtlpCommon.KeyValue { Key = key, Value = ToAnyValue(value.ToString()) });
}
protected override void WriteArrayTag(ref RepeatedField<OtlpCommon.KeyValue> tags, string key, ref OtlpCommon.ArrayValue value)
@ -95,9 +95,9 @@ internal sealed class OtlpTagWriter : TagWriter<RepeatedField<OtlpCommon.KeyValu
array.Values.Add(ToAnyValue(value));
}
public override void WriteStringValue(ref OtlpCommon.ArrayValue array, string value)
public override void WriteStringValue(ref OtlpCommon.ArrayValue array, ReadOnlySpan<char> value)
{
array.Values.Add(ToAnyValue(value));
array.Values.Add(ToAnyValue(value.ToString()));
}
public override void EndWriteArray(ref OtlpCommon.ArrayValue array)

View File

@ -49,7 +49,7 @@ internal sealed class ZipkinTagWriter : JsonStringArrayTagWriter<Utf8JsonWriter>
protected override void WriteBooleanTag(ref Utf8JsonWriter writer, string key, bool value)
=> writer.WriteString(key, value ? "true" : "false");
protected override void WriteStringTag(ref Utf8JsonWriter writer, string key, string value)
protected override void WriteStringTag(ref Utf8JsonWriter writer, string key, ReadOnlySpan<char> value)
=> writer.WriteString(key, value);
protected override void WriteArrayTag(ref Utf8JsonWriter writer, string key, ArraySegment<byte> arrayUtf8JsonBytes)

View File

@ -1,208 +0,0 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
#nullable enable
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace OpenTelemetry.Internal;
internal abstract class TagTransformer<T>
where T : notnull
{
protected TagTransformer()
{
}
public bool TryTransformTag(
KeyValuePair<string, object> tag,
[NotNullWhen(true)] out T? result,
int? tagValueMaxLength = null)
{
if (tag.Value == null)
{
result = default;
return false;
}
switch (tag.Value)
{
case char:
case string:
result = this.TransformStringTag(tag.Key, TruncateString(Convert.ToString(tag.Value)!, tagValueMaxLength));
break;
case bool b:
result = this.TransformBooleanTag(tag.Key, b);
break;
case byte:
case sbyte:
case short:
case ushort:
case int:
case uint:
case long:
result = this.TransformIntegralTag(tag.Key, Convert.ToInt64(tag.Value));
break;
case float:
case double:
result = this.TransformFloatingPointTag(tag.Key, Convert.ToDouble(tag.Value));
break;
case Array array:
try
{
result = this.TransformArrayTagInternal(tag.Key, array, tagValueMaxLength);
}
catch
{
// If an exception is thrown when calling ToString
// on any element of the array, then the entire array value
// is ignored.
return this.LogUnsupportedTagTypeAndReturnDefault(tag.Key, tag.Value, out result);
}
break;
// All other types are converted to strings including the following
// built-in value types:
// case nint: Pointer type.
// case nuint: Pointer type.
// case ulong: May throw an exception on overflow.
// case decimal: Converting to double produces rounding errors.
default:
try
{
var stringValue = TruncateString(Convert.ToString(tag.Value), tagValueMaxLength);
if (stringValue == null)
{
return this.LogUnsupportedTagTypeAndReturnDefault(tag.Key, tag.Value, out result);
}
result = this.TransformStringTag(tag.Key, stringValue);
}
catch
{
// If ToString throws an exception then the tag is ignored.
return this.LogUnsupportedTagTypeAndReturnDefault(tag.Key, tag.Value, out result);
}
break;
}
return true;
}
protected abstract T TransformIntegralTag(string key, long value);
protected abstract T TransformFloatingPointTag(string key, double value);
protected abstract T TransformBooleanTag(string key, bool value);
protected abstract T TransformStringTag(string key, string value);
protected abstract T TransformArrayTag(string key, Array array);
protected abstract void OnUnsupportedTagDropped(
string tagKey,
string tagValueTypeFullName);
[return: NotNullIfNotNull(nameof(value))]
private static string? TruncateString(string? value, int? maxLength)
{
return maxLength.HasValue && value?.Length > maxLength
? value.Substring(0, maxLength.Value)
: value;
}
private T TransformArrayTagInternal(string key, Array array, int? tagValueMaxLength)
{
// This switch ensures the values of the resultant array-valued tag are of the same type.
return array switch
{
char[] => this.TransformArrayTag(key, array),
string[] => this.ConvertToStringArrayThenTransformArrayTag(key, array, tagValueMaxLength),
bool[] => this.TransformArrayTag(key, array),
byte[] => this.TransformArrayTag(key, array),
sbyte[] => this.TransformArrayTag(key, array),
short[] => this.TransformArrayTag(key, array),
ushort[] => this.TransformArrayTag(key, array),
#if NETFRAMEWORK
int[] => this.TransformArrayTagIntNetFramework(key, array, tagValueMaxLength),
#else
int[] => this.TransformArrayTag(key, array),
#endif
uint[] => this.TransformArrayTag(key, array),
#if NETFRAMEWORK
long[] => this.TransformArrayTagLongNetFramework(key, array, tagValueMaxLength),
#else
long[] => this.TransformArrayTag(key, array),
#endif
float[] => this.TransformArrayTag(key, array),
double[] => this.TransformArrayTag(key, array),
_ => this.ConvertToStringArrayThenTransformArrayTag(key, array, tagValueMaxLength),
};
}
#if NETFRAMEWORK
private T TransformArrayTagIntNetFramework(string key, Array array, int? tagValueMaxLength)
{
// Note: On .NET Framework x86 nint[] & nuint[] fall into int[] case
var arrayType = array.GetType();
if (arrayType == typeof(nint[])
|| arrayType == typeof(nuint[]))
{
return this.ConvertToStringArrayThenTransformArrayTag(key, array, tagValueMaxLength);
}
return this.TransformArrayTag(key, array);
}
private T TransformArrayTagLongNetFramework(string key, Array array, int? tagValueMaxLength)
{
// Note: On .NET Framework x64 nint[] & nuint[] fall into long[] case
var arrayType = array.GetType();
if (arrayType == typeof(nint[])
|| arrayType == typeof(nuint[]))
{
return this.ConvertToStringArrayThenTransformArrayTag(key, array, tagValueMaxLength);
}
return this.TransformArrayTag(key, array);
}
#endif
private T ConvertToStringArrayThenTransformArrayTag(string key, Array array, int? tagValueMaxLength)
{
string?[] stringArray;
if (array is string?[] arrayAsStringArray
&& (!tagValueMaxLength.HasValue || !arrayAsStringArray.Any(s => s?.Length > tagValueMaxLength)))
{
stringArray = arrayAsStringArray;
}
else
{
stringArray = new string?[array.Length];
for (var i = 0; i < array.Length; ++i)
{
var item = array.GetValue(i);
stringArray[i] = item == null
? null
: TruncateString(Convert.ToString(item), tagValueMaxLength);
}
}
return this.TransformArrayTag(key, stringArray);
}
private bool LogUnsupportedTagTypeAndReturnDefault(string key, object value, out T? result)
{
Debug.Assert(value != null, "value was null");
this.OnUnsupportedTagDropped(key, value!.GetType().ToString());
result = default;
return false;
}
}

View File

@ -1,53 +0,0 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
#nullable enable
using System.Text.Json;
#if NET6_0_OR_GREATER
using System.Text.Json.Serialization;
#endif
namespace OpenTelemetry.Internal;
/// <summary>
/// This class has to be partial so that JSON source generator can provide code for the JsonSerializerContext.
/// </summary>
internal static partial class TagTransformerJsonHelper
{
#if NET6_0_OR_GREATER
// In net6.0 or higher ships System.Text.Json "in box" as part of the base class libraries;
// meaning the consumer automatically got upgraded to use v6.0 System.Text.Json
// which has support for using source generators for JSON serialization.
// The source generator makes the serialization faster and also AOT compatible.
internal static string JsonSerializeArrayTag(Array array)
{
return JsonSerializer.Serialize(array, typeof(Array), ArrayTagJsonContext.Default);
}
[JsonSerializable(typeof(Array))]
[JsonSerializable(typeof(char))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(byte))]
[JsonSerializable(typeof(sbyte))]
[JsonSerializable(typeof(short))]
[JsonSerializable(typeof(ushort))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(uint))]
[JsonSerializable(typeof(long))]
[JsonSerializable(typeof(ulong))]
[JsonSerializable(typeof(float))]
[JsonSerializable(typeof(double))]
private sealed partial class ArrayTagJsonContext : JsonSerializerContext
{
}
#else
internal static string JsonSerializeArrayTag(Array array)
{
return JsonSerializer.Serialize(array);
}
#endif
}

View File

@ -18,7 +18,7 @@ internal abstract class ArrayTagWriter<TArrayState>
public abstract void WriteBooleanValue(ref TArrayState state, bool value);
public abstract void WriteStringValue(ref TArrayState state, string value);
public abstract void WriteStringValue(ref TArrayState state, ReadOnlySpan<char> value);
public abstract void EndWriteArray(ref TArrayState state);
}

View File

@ -75,7 +75,7 @@ internal abstract class JsonStringArrayTagWriter<TTagState> : TagWriter<TTagStat
state.Writer.WriteNullValue();
}
public override void WriteStringValue(ref JsonArrayTagWriterState state, string value)
public override void WriteStringValue(ref JsonArrayTagWriterState state, ReadOnlySpan<char> value)
{
state.Writer.WriteStringValue(value);
}

View File

@ -3,9 +3,7 @@
#nullable enable
using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace OpenTelemetry.Internal;
@ -35,9 +33,14 @@ internal abstract class TagWriter<TTagState, TArrayState>
switch (tag.Value)
{
case char:
case string:
this.WriteStringTag(ref state, tag.Key, TruncateString(Convert.ToString(tag.Value)!, tagValueMaxLength));
case char c:
this.WriteCharTag(ref state, tag.Key, c);
break;
case string s:
this.WriteStringTag(
ref state,
tag.Key,
TruncateString(s.AsSpan(), tagValueMaxLength));
break;
case bool b:
this.WriteBooleanTag(ref state, tag.Key, b);
@ -79,13 +82,16 @@ internal abstract class TagWriter<TTagState, TArrayState>
default:
try
{
var stringValue = TruncateString(Convert.ToString(tag.Value/*TODO: , CultureInfo.InvariantCulture*/), tagValueMaxLength);
var stringValue = Convert.ToString(tag.Value/*TODO: , CultureInfo.InvariantCulture*/);
if (stringValue == null)
{
return this.LogUnsupportedTagTypeAndReturnFalse(tag.Key, tag.Value);
}
this.WriteStringTag(ref state, tag.Key, stringValue);
this.WriteStringTag(
ref state,
tag.Key,
TruncateString(stringValue.AsSpan(), tagValueMaxLength));
}
catch
{
@ -105,7 +111,7 @@ internal abstract class TagWriter<TTagState, TArrayState>
protected abstract void WriteBooleanTag(ref TTagState state, string key, bool value);
protected abstract void WriteStringTag(ref TTagState state, string key, string value);
protected abstract void WriteStringTag(ref TTagState state, string key, ReadOnlySpan<char> value);
protected abstract void WriteArrayTag(ref TTagState state, string key, ref TArrayState value);
@ -113,14 +119,27 @@ internal abstract class TagWriter<TTagState, TArrayState>
string tagKey,
string tagValueTypeFullName);
[return: NotNullIfNotNull(nameof(value))]
private static string? TruncateString(string? value, int? maxLength)
private static ReadOnlySpan<char> TruncateString(ReadOnlySpan<char> value, int? maxLength)
{
return maxLength.HasValue && value?.Length > maxLength
? value.Substring(0, maxLength.Value)
return maxLength.HasValue && value.Length > maxLength
? value.Slice(0, maxLength.Value)
: value;
}
private void WriteCharTag(ref TTagState state, string key, char value)
{
Span<char> destination = stackalloc char[1];
destination[0] = value;
this.WriteStringTag(ref state, key, destination);
}
private void WriteCharValue(ref TArrayState state, char value)
{
Span<char> destination = stackalloc char[1];
destination[0] = value;
this.arrayWriter.WriteStringValue(ref state, destination);
}
private void WriteArrayTagInternal(ref TTagState state, string key, Array array, int? tagValueMaxLength)
{
var arrayState = this.arrayWriter.BeginWriteArray();
@ -128,24 +147,21 @@ internal abstract class TagWriter<TTagState, TArrayState>
// This switch ensures the values of the resultant array-valued tag are of the same type.
switch (array)
{
case char[] charArray: this.WriteToArray(ref arrayState, charArray); break;
case string[]: this.ConvertToStringArrayThenWriteArrayTag(ref arrayState, array, tagValueMaxLength); break;
case bool[] boolArray: this.WriteToArray(ref arrayState, boolArray); break;
case byte[] byteArray: this.WriteToArray(ref arrayState, byteArray); break;
case sbyte[] sbyteArray: this.WriteToArray(ref arrayState, sbyteArray); break;
case short[] shortArray: this.WriteToArray(ref arrayState, shortArray); break;
case ushort[] ushortArray: this.WriteToArray(ref arrayState, ushortArray); break;
case uint[] uintArray: this.WriteToArray(ref arrayState, uintArray); break;
case char[] charArray: this.WriteStructToArray(ref arrayState, charArray); break;
case string?[] stringArray: this.WriteStringsToArray(ref arrayState, stringArray, tagValueMaxLength); break;
case bool[] boolArray: this.WriteStructToArray(ref arrayState, boolArray); break;
case byte[] byteArray: this.WriteToArrayCovariant(ref arrayState, byteArray); break;
case short[] shortArray: this.WriteToArrayCovariant(ref arrayState, shortArray); break;
#if NETFRAMEWORK
case int[]: this.WriteArrayTagIntNetFramework(ref arrayState, array, tagValueMaxLength); break;
case long[]: this.WriteArrayTagLongNetFramework(ref arrayState, array, tagValueMaxLength); break;
#else
case int[] intArray: this.WriteToArray(ref arrayState, intArray); break;
case long[] longArray: this.WriteToArray(ref arrayState, longArray); break;
case int[] intArray: this.WriteToArrayCovariant(ref arrayState, intArray); break;
case long[] longArray: this.WriteToArrayCovariant(ref arrayState, longArray); break;
#endif
case float[] floatArray: this.WriteToArray(ref arrayState, floatArray); break;
case double[] doubleArray: this.WriteToArray(ref arrayState, doubleArray); break;
default: this.ConvertToStringArrayThenWriteArrayTag(ref arrayState, array, tagValueMaxLength); break;
case float[] floatArray: this.WriteStructToArray(ref arrayState, floatArray); break;
case double[] doubleArray: this.WriteStructToArray(ref arrayState, doubleArray); break;
default: this.WriteToArrayTypeChecked(ref arrayState, array, tagValueMaxLength); break;
}
this.arrayWriter.EndWriteArray(ref arrayState);
@ -162,11 +178,11 @@ internal abstract class TagWriter<TTagState, TArrayState>
if (arrayType == typeof(nint[])
|| arrayType == typeof(nuint[]))
{
this.ConvertToStringArrayThenWriteArrayTag(ref arrayState, array, tagValueMaxLength);
this.WriteToArrayTypeChecked(ref arrayState, array, tagValueMaxLength);
return;
}
this.WriteToArray(ref arrayState, (int[])array);
this.WriteToArrayCovariant(ref arrayState, (int[])array);
}
private void WriteArrayTagLongNetFramework(ref TArrayState arrayState, Array array, int? tagValueMaxLength)
@ -177,51 +193,143 @@ internal abstract class TagWriter<TTagState, TArrayState>
if (arrayType == typeof(nint[])
|| arrayType == typeof(nuint[]))
{
this.ConvertToStringArrayThenWriteArrayTag(ref arrayState, array, tagValueMaxLength);
this.WriteToArrayTypeChecked(ref arrayState, array, tagValueMaxLength);
return;
}
this.WriteToArray(ref arrayState, (long[])array);
this.WriteToArrayCovariant(ref arrayState, (long[])array);
}
#endif
private void ConvertToStringArrayThenWriteArrayTag(ref TArrayState arrayState, Array array, int? tagValueMaxLength)
private void WriteToArrayTypeChecked(ref TArrayState arrayState, Array array, int? tagValueMaxLength)
{
if (array is string?[] arrayAsStringArray
&& (!tagValueMaxLength.HasValue || !arrayAsStringArray.Any(s => s?.Length > tagValueMaxLength)))
for (var i = 0; i < array.Length; ++i)
{
this.WriteStringsToArray(ref arrayState, arrayAsStringArray);
}
else
{
string?[] stringArray = ArrayPool<string?>.Shared.Rent(array.Length);
try
var item = array.GetValue(i);
if (item == null)
{
for (var i = 0; i < array.Length; ++i)
{
var item = array.GetValue(i);
stringArray[i] = item == null
? null
: TruncateString(Convert.ToString(item/*TODO: , CultureInfo.InvariantCulture*/), tagValueMaxLength);
}
this.WriteStringsToArray(ref arrayState, new(stringArray, 0, array.Length));
this.arrayWriter.WriteNullValue(ref arrayState);
continue;
}
finally
switch (item)
{
ArrayPool<string?>.Shared.Return(stringArray);
case char c:
this.WriteCharValue(ref arrayState, c);
break;
case string s:
this.arrayWriter.WriteStringValue(
ref arrayState,
TruncateString(s.AsSpan(), tagValueMaxLength));
break;
case bool b:
this.arrayWriter.WriteBooleanValue(ref arrayState, b);
break;
case byte:
case sbyte:
case short:
case ushort:
case int:
case uint:
case long:
this.arrayWriter.WriteIntegralValue(ref arrayState, Convert.ToInt64(item));
break;
case float:
case double:
this.arrayWriter.WriteFloatingPointValue(ref arrayState, Convert.ToDouble(item));
break;
// All other types are converted to strings including the following
// built-in value types:
// case Array: Nested array.
// case nint: Pointer type.
// case nuint: Pointer type.
// case ulong: May throw an exception on overflow.
// case decimal: Converting to double produces rounding errors.
default:
var stringValue = Convert.ToString(item/*TODO: , CultureInfo.InvariantCulture*/);
if (stringValue == null)
{
this.arrayWriter.WriteNullValue(ref arrayState);
}
else
{
this.arrayWriter.WriteStringValue(
ref arrayState,
TruncateString(stringValue.AsSpan(), tagValueMaxLength));
}
break;
}
}
}
private void WriteToArray<TItem>(ref TArrayState arrayState, TItem[] array)
private void WriteToArrayCovariant<TItem>(ref TArrayState arrayState, TItem[] array)
where TItem : struct
{
// Note: The runtime treats int[]/uint[], byte[]/sbyte[],
// short[]/ushort[], and long[]/ulong[] as covariant.
if (typeof(TItem) == typeof(byte))
{
if (array.GetType() == typeof(sbyte[]))
{
this.WriteStructToArray(ref arrayState, (sbyte[])(object)array);
}
else
{
this.WriteStructToArray(ref arrayState, (byte[])(object)array);
}
}
else if (typeof(TItem) == typeof(short))
{
if (array.GetType() == typeof(ushort[]))
{
this.WriteStructToArray(ref arrayState, (ushort[])(object)array);
}
else
{
this.WriteStructToArray(ref arrayState, (short[])(object)array);
}
}
else if (typeof(TItem) == typeof(int))
{
if (array.GetType() == typeof(uint[]))
{
this.WriteStructToArray(ref arrayState, (uint[])(object)array);
}
else
{
this.WriteStructToArray(ref arrayState, (int[])(object)array);
}
}
else if (typeof(TItem) == typeof(long))
{
if (array.GetType() == typeof(ulong[]))
{
this.WriteToArrayTypeChecked(ref arrayState, array, tagValueMaxLength: null);
}
else
{
this.WriteStructToArray(ref arrayState, (long[])(object)array);
}
}
else
{
Debug.Fail("Unexpected type encountered");
throw new NotSupportedException();
}
}
private void WriteStructToArray<TItem>(ref TArrayState arrayState, TItem[] array)
where TItem : struct
{
foreach (TItem item in array)
{
if (typeof(TItem) == typeof(char))
{
this.arrayWriter.WriteStringValue(ref arrayState, Convert.ToString((char)(object)item)!);
this.WriteCharValue(ref arrayState, (char)(object)item);
}
else if (typeof(TItem) == typeof(bool))
{
@ -272,9 +380,9 @@ internal abstract class TagWriter<TTagState, TArrayState>
}
}
private void WriteStringsToArray(ref TArrayState arrayState, ReadOnlySpan<string?> data)
private void WriteStringsToArray(ref TArrayState arrayState, string?[] array, int? tagValueMaxLength)
{
foreach (var item in data)
foreach (var item in array)
{
if (item == null)
{
@ -282,7 +390,9 @@ internal abstract class TagWriter<TTagState, TArrayState>
}
else
{
this.arrayWriter.WriteStringValue(ref arrayState, item);
this.arrayWriter.WriteStringValue(
ref arrayState,
TruncateString(item.AsSpan(), tagValueMaxLength));
}
}
}

View File

@ -137,6 +137,44 @@ public class OtlpAttributeTests
Assert.Equal(Convert.ToString(value), attribute.Value.StringValue);
}
[Fact]
public void ObjectArrayTypesSupported()
{
var obj = new object();
var objectArray = new object[] { null, "a", 'b', true, int.MaxValue, long.MaxValue, float.MaxValue, double.MaxValue, obj };
var kvp = new KeyValuePair<string, object>("key", objectArray);
Assert.True(TryTransformTag(kvp, out var attribute));
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase);
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.None, attribute.Value.ArrayValue.Values[0].ValueCase);
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.StringValue, attribute.Value.ArrayValue.Values[1].ValueCase);
Assert.Equal("a", attribute.Value.ArrayValue.Values[1].StringValue);
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.StringValue, attribute.Value.ArrayValue.Values[2].ValueCase);
Assert.Equal("b", attribute.Value.ArrayValue.Values[2].StringValue);
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.BoolValue, attribute.Value.ArrayValue.Values[3].ValueCase);
Assert.True(attribute.Value.ArrayValue.Values[3].BoolValue);
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.IntValue, attribute.Value.ArrayValue.Values[4].ValueCase);
Assert.Equal(int.MaxValue, attribute.Value.ArrayValue.Values[4].IntValue);
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.IntValue, attribute.Value.ArrayValue.Values[5].ValueCase);
Assert.Equal(long.MaxValue, attribute.Value.ArrayValue.Values[5].IntValue);
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.DoubleValue, attribute.Value.ArrayValue.Values[6].ValueCase);
Assert.Equal(float.MaxValue, attribute.Value.ArrayValue.Values[6].DoubleValue);
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.DoubleValue, attribute.Value.ArrayValue.Values[7].ValueCase);
Assert.Equal(double.MaxValue, attribute.Value.ArrayValue.Values[7].DoubleValue);
Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.StringValue, attribute.Value.ArrayValue.Values[8].ValueCase);
Assert.Equal(obj.ToString(), attribute.Value.ArrayValue.Values[8].StringValue);
}
[Fact]
public void StringArrayTypesSupported()
{
@ -183,7 +221,7 @@ public class OtlpAttributeTests
new nint[] { 1, 2, 3 },
new nuint[] { 1, 2, 3 },
new decimal[] { 1, 2, 3 },
new object[] { 1, new object(), false, null },
new object[] { new object[3], new object(), null },
};
foreach (var value in testValues)

View File

@ -4,10 +4,9 @@
#nullable enable
using System.Reflection;
using OpenTelemetry.Internal;
using Xunit;
namespace OpenTelemetry.Tests;
namespace OpenTelemetry.Internal.Tests;
public class AssemblyVersionExtensionsTests
{

View File

@ -0,0 +1,204 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
#nullable enable
using System.Text;
using Xunit;
namespace OpenTelemetry.Internal.Tests;
public class JsonStringArrayTagWriterTests
{
[Theory]
[InlineData(new object[] { new char[] { }, "[]" })]
[InlineData(new object[] { new char[] { 'a' }, """["a"]""" })]
[InlineData(new object[] { new char[] { '1', '2', '3' }, """["1","2","3"]""" })]
public void CharArray(char[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new string[] { }, "[]" })]
[InlineData(new object[] { new string[] { "one" }, """["one"]""" })]
[InlineData(new object[] { new string[] { "" }, """[""]""" })]
[InlineData(new object[] { new string[] { "a", "b", "c", "d" }, """["a","b","c","d"]""" })]
[InlineData(new object[] { new string[] { "\r\n", "\t", "\"" }, """["\r\n","\t","\u0022"]""" })]
[InlineData(new object[] { new string[] { "longlonglonglonglonglonglonglonglong" }, """["longlonglonglonglonglonglonglonglong"]""" })]
public void StringArray(string[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new bool[] { }, "[]" })]
[InlineData(new object[] { new bool[] { true }, "[true]" })]
[InlineData(new object[] { new bool[] { true, false, false, true }, "[true,false,false,true]" })]
public void BooleanArray(bool[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new byte[] { }, "[]" })]
[InlineData(new object[] { new byte[] { 0 }, "[0]" })]
[InlineData(new object[] { new byte[] { byte.MaxValue, byte.MinValue, 4, 13 }, "[255,0,4,13]" })]
public void ByteArray(byte[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new sbyte[] { }, "[]" })]
[InlineData(new object[] { new sbyte[] { 0 }, "[0]" })]
[InlineData(new object[] { new sbyte[] { sbyte.MaxValue, sbyte.MinValue, 4, 13 }, "[127,-128,4,13]" })]
public void SByteArray(sbyte[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new short[] { }, "[]" })]
[InlineData(new object[] { new short[] { 0 }, "[0]" })]
[InlineData(new object[] { new short[] { short.MaxValue, short.MinValue, 4, 13 }, "[32767,-32768,4,13]" })]
public void ShortArray(short[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new ushort[] { }, "[]" })]
[InlineData(new object[] { new ushort[] { 0 }, "[0]" })]
[InlineData(new object[] { new ushort[] { ushort.MaxValue, ushort.MinValue, 4, 13 }, "[65535,0,4,13]" })]
public void UShortArray(ushort[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new int[] { }, "[]" })]
[InlineData(new object[] { new int[] { 0 }, "[0]" })]
[InlineData(new object[] { new int[] { int.MaxValue, int.MinValue, 4, 13 }, "[2147483647,-2147483648,4,13]" })]
public void IntArray(int[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new uint[] { }, "[]" })]
[InlineData(new object[] { new uint[] { 0 }, "[0]" })]
[InlineData(new object[] { new uint[] { uint.MaxValue, uint.MinValue, 4, 13 }, "[4294967295,0,4,13]" })]
public void UIntArray(uint[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new long[] { }, "[]" })]
[InlineData(new object[] { new long[] { 0 }, "[0]" })]
[InlineData(new object[] { new long[] { long.MaxValue, long.MinValue, 4, 13 }, "[9223372036854775807,-9223372036854775808,4,13]" })]
public void LongArray(long[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new ulong[] { }, "[]" })]
[InlineData(new object[] { new ulong[] { 0 }, """["0"]""" })]
[InlineData(new object[] { new ulong[] { ulong.MaxValue, ulong.MinValue, 4, 13 }, """["18446744073709551615","0","4","13"]""" })]
public void ULongArray(ulong[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new float[] { }, "[]" })]
[InlineData(new object[] { new float[] { 0 }, "[0]" })]
[InlineData(new object[] { new float[] { float.MaxValue, float.MinValue, 4, 13 }, "[3.4028234663852886E+38,-3.4028234663852886E+38,4,13]" })]
#if NETFRAMEWORK
[InlineData(new object[] { new float[] { float.Epsilon }, "[1.4012984643248171E-45]" })]
#else
[InlineData(new object[] { new float[] { float.Epsilon }, "[1.401298464324817E-45]" })]
#endif
public void FloatArray(float[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new double[] { }, "[]" })]
[InlineData(new object[] { new double[] { 0 }, "[0]" })]
[InlineData(new object[] { new double[] { double.MaxValue, double.MinValue, 4, 13 }, "[1.7976931348623157E+308,-1.7976931348623157E+308,4,13]" })]
#if NETFRAMEWORK
[InlineData(new object[] { new double[] { double.Epsilon }, "[4.9406564584124654E-324]" })]
#else
[InlineData(new object[] { new double[] { double.Epsilon }, "[5E-324]" })]
#endif
public void DoubleArray(double[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
[Theory]
[InlineData(new object[] { new object?[] { }, "[]" })]
[InlineData(new object[] { new object?[] { null, float.MinValue, float.MaxValue, double.MinValue, double.MaxValue, int.MinValue, int.MaxValue, long.MinValue, long.MaxValue, true, false, "Hello world", new object[] { "inner array" } }, """[null,-3.4028234663852886E+38,3.4028234663852886E+38,-1.7976931348623157E+308,1.7976931348623157E+308,-2147483648,2147483647,-9223372036854775808,9223372036854775807,true,false,"Hello world","System.Object[]"]""" })]
public void ObjectArray(object?[] data, string expectedValue)
{
VerifySerialization(data, expectedValue);
}
private static void VerifySerialization(Array data, string expectedValue)
{
TestTagWriter.Tag tag = default;
var result = TestTagWriter.Instance.TryWriteTag(ref tag, new KeyValuePair<string, object?>("array", data));
Assert.True(result);
Assert.Equal(expectedValue, tag.Value);
}
private sealed class TestTagWriter : JsonStringArrayTagWriter<TestTagWriter.Tag>
{
private TestTagWriter()
{
}
public static TestTagWriter Instance { get; } = new();
protected override void WriteIntegralTag(ref Tag tag, string key, long value)
{
throw new NotImplementedException();
}
protected override void WriteFloatingPointTag(ref Tag tag, string key, double value)
{
throw new NotImplementedException();
}
protected override void WriteBooleanTag(ref Tag tag, string key, bool value)
{
throw new NotImplementedException();
}
protected override void WriteStringTag(ref Tag tag, string key, ReadOnlySpan<char> value)
{
throw new NotImplementedException();
}
protected override void WriteArrayTag(ref Tag tag, string key, ArraySegment<byte> arrayUtf8JsonBytes)
{
tag.Key = key;
tag.Value = Encoding.UTF8.GetString(arrayUtf8JsonBytes.Array!, 0, arrayUtf8JsonBytes.Count);
}
protected override void OnUnsupportedTagDropped(string tagKey, string tagValueTypeFullName)
{
}
public struct Tag
{
public string? Key;
public string? Value;
}
}
}

View File

@ -25,7 +25,7 @@
<Compile Include="$(RepoRoot)\src\Shared\PeriodicExportingMetricReaderHelper.cs" Link="Includes\PeriodicExportingMetricReaderHelper.cs" />
<Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
<Compile Include="$(RepoRoot)\src\Shared\PooledList.cs" Link="Includes\PooledList.cs" />
<Compile Include="$(RepoRoot)\src\Shared\TagTransformerJsonHelper.cs" Link="Includes\TagTransformerJsonHelper.cs" />
<Compile Include="$(RepoRoot)\src\Shared\TagWriter\*.cs" Link="Includes\TagWriter\%(Filename).cs" />
</ItemGroup>
<ItemGroup>

View File

@ -1,140 +0,0 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using OpenTelemetry.Internal;
using Xunit;
namespace OpenTelemetry.Tests.Shared;
public class TagTransformerJsonHelperTest
{
[Theory]
[InlineData(new object[] { new char[] { } })]
[InlineData(new object[] { new char[] { 'a' } })]
[InlineData(new object[] { new char[] { '1', '2', '3' } })]
public void CharArray(char[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new string[] { } })]
[InlineData(new object[] { new string[] { "one" } })]
[InlineData(new object[] { new string[] { "" } })]
[InlineData(new object[] { new string[] { "a", "b", "c", "d" } })]
[InlineData(new object[] { new string[] { "\r\n", "\t", "\"" } })]
[InlineData(new object[] { new string[] { "longlonglonglonglonglonglonglonglong" } })]
public void StringArray(string[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new bool[] { } })]
[InlineData(new object[] { new bool[] { true } })]
[InlineData(new object[] { new bool[] { true, false, false, true } })]
public void BooleanArray(bool[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new byte[] { } })]
[InlineData(new object[] { new byte[] { 0 } })]
[InlineData(new object[] { new byte[] { byte.MaxValue, byte.MinValue, 4, 13 } })]
public void ByteArray(byte[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new sbyte[] { } })]
[InlineData(new object[] { new sbyte[] { 0 } })]
[InlineData(new object[] { new sbyte[] { sbyte.MaxValue, sbyte.MinValue, 4, 13 } })]
public void SByteArray(sbyte[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new short[] { } })]
[InlineData(new object[] { new short[] { 0 } })]
[InlineData(new object[] { new short[] { short.MaxValue, short.MinValue, 4, 13 } })]
public void ShortArray(short[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new ushort[] { } })]
[InlineData(new object[] { new ushort[] { 0 } })]
[InlineData(new object[] { new ushort[] { ushort.MaxValue, ushort.MinValue, 4, 13 } })]
public void UShortArray(ushort[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new int[] { } })]
[InlineData(new object[] { new int[] { 0 } })]
[InlineData(new object[] { new int[] { int.MaxValue, int.MinValue, 4, 13 } })]
public void IntArray(int[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new uint[] { } })]
[InlineData(new object[] { new uint[] { 0 } })]
[InlineData(new object[] { new uint[] { uint.MaxValue, uint.MinValue, 4, 13 } })]
public void UIntArray(uint[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new long[] { } })]
[InlineData(new object[] { new long[] { 0 } })]
[InlineData(new object[] { new long[] { long.MaxValue, long.MinValue, 4, 13 } })]
public void LongArray(long[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new ulong[] { } })]
[InlineData(new object[] { new ulong[] { 0 } })]
[InlineData(new object[] { new ulong[] { ulong.MaxValue, ulong.MinValue, 4, 13 } })]
public void ULongArray(ulong[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new float[] { } })]
[InlineData(new object[] { new float[] { 0 } })]
[InlineData(new object[] { new float[] { float.MaxValue, float.MinValue, 4, 13 } })]
[InlineData(new object[] { new float[] { float.Epsilon } })]
public void FloatArray(float[] data)
{
VerifySerialization(data);
}
[Theory]
[InlineData(new object[] { new double[] { } })]
[InlineData(new object[] { new double[] { 0 } })]
[InlineData(new object[] { new double[] { double.MaxValue, double.MinValue, 4, 13 } })]
[InlineData(new object[] { new double[] { double.Epsilon } })]
public void DoubleArray(double[] data)
{
VerifySerialization(data);
}
private static void VerifySerialization(Array data)
{
var reflectionBasedResult = System.Text.Json.JsonSerializer.Serialize(data);
var rawResult = TagTransformerJsonHelper.JsonSerializeArrayTag(data);
Assert.Equal(reflectionBasedResult, rawResult);
}
}