diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index c58d53f4531..ca2e9cf199d 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -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 diff --git a/src/OpenTelemetry.Exporter.Console/Implementation/ConsoleTagWriter.cs b/src/OpenTelemetry.Exporter.Console/Implementation/ConsoleTagWriter.cs index f34d14cb3fb..2f36df00915 100644 --- a/src/OpenTelemetry.Exporter.Console/Implementation/ConsoleTagWriter.cs +++ b/src/OpenTelemetry.Exporter.Console/Implementation/ConsoleTagWriter.cs @@ -51,10 +51,10 @@ protected override void WriteBooleanTag(ref ConsoleTag consoleTag, string key, b 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 value) { consoleTag.Key = key; - consoleTag.Value = value; + consoleTag.Value = value.ToString(); } protected override void WriteArrayTag(ref ConsoleTag consoleTag, string key, ArraySegment arrayUtf8JsonBytes) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpTagWriter.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpTagWriter.cs index ffc47dd25a2..64315fd8e0a 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpTagWriter.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpTagWriter.cs @@ -45,9 +45,9 @@ protected override void WriteBooleanTag(ref RepeatedField t tags.Add(new OtlpCommon.KeyValue { Key = key, Value = ToAnyValue(value) }); } - protected override void WriteStringTag(ref RepeatedField tags, string key, string value) + protected override void WriteStringTag(ref RepeatedField tags, string key, ReadOnlySpan 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 tags, string key, ref OtlpCommon.ArrayValue value) @@ -95,9 +95,9 @@ public override void WriteBooleanValue(ref OtlpCommon.ArrayValue array, bool val array.Values.Add(ToAnyValue(value)); } - public override void WriteStringValue(ref OtlpCommon.ArrayValue array, string value) + public override void WriteStringValue(ref OtlpCommon.ArrayValue array, ReadOnlySpan value) { - array.Values.Add(ToAnyValue(value)); + array.Values.Add(ToAnyValue(value.ToString())); } public override void EndWriteArray(ref OtlpCommon.ArrayValue array) diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs index c83b1f92003..d40d126b92e 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagWriter.cs @@ -49,7 +49,7 @@ protected override void WriteFloatingPointTag(ref Utf8JsonWriter writer, string 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 value) => writer.WriteString(key, value); protected override void WriteArrayTag(ref Utf8JsonWriter writer, string key, ArraySegment arrayUtf8JsonBytes) diff --git a/src/Shared/TagTransformer.cs b/src/Shared/TagTransformer.cs deleted file mode 100644 index 0d98f076b5c..00000000000 --- a/src/Shared/TagTransformer.cs +++ /dev/null @@ -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 - where T : notnull -{ - protected TagTransformer() - { - } - - public bool TryTransformTag( - KeyValuePair 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; - } -} diff --git a/src/Shared/TagTransformerJsonHelper.cs b/src/Shared/TagTransformerJsonHelper.cs deleted file mode 100644 index 9934d33422e..00000000000 --- a/src/Shared/TagTransformerJsonHelper.cs +++ /dev/null @@ -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; - -/// -/// This class has to be partial so that JSON source generator can provide code for the JsonSerializerContext. -/// -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 -} diff --git a/src/Shared/TagWriter/ArrayTagWriter.cs b/src/Shared/TagWriter/ArrayTagWriter.cs index b5eec4e2f0c..ac5bba8bf0d 100644 --- a/src/Shared/TagWriter/ArrayTagWriter.cs +++ b/src/Shared/TagWriter/ArrayTagWriter.cs @@ -18,7 +18,7 @@ internal abstract class ArrayTagWriter 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 value); public abstract void EndWriteArray(ref TArrayState state); } diff --git a/src/Shared/TagWriter/JsonStringArrayTagWriter.cs b/src/Shared/TagWriter/JsonStringArrayTagWriter.cs index 21addd5ba5b..8dd2c086d79 100644 --- a/src/Shared/TagWriter/JsonStringArrayTagWriter.cs +++ b/src/Shared/TagWriter/JsonStringArrayTagWriter.cs @@ -75,7 +75,7 @@ public override void WriteNullValue(ref JsonArrayTagWriterState state) state.Writer.WriteNullValue(); } - public override void WriteStringValue(ref JsonArrayTagWriterState state, string value) + public override void WriteStringValue(ref JsonArrayTagWriterState state, ReadOnlySpan value) { state.Writer.WriteStringValue(value); } diff --git a/src/Shared/TagWriter/TagWriter.cs b/src/Shared/TagWriter/TagWriter.cs index fe4e5ef9c9c..fb56903363a 100644 --- a/src/Shared/TagWriter/TagWriter.cs +++ b/src/Shared/TagWriter/TagWriter.cs @@ -3,9 +3,7 @@ #nullable enable -using System.Buffers; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; namespace OpenTelemetry.Internal; @@ -35,9 +33,14 @@ public bool TryWriteTag( 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 @@ public bool TryWriteTag( 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 @@ public bool TryWriteTag( 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 value); protected abstract void WriteArrayTag(ref TTagState state, string key, ref TArrayState value); @@ -113,14 +119,27 @@ protected abstract void OnUnsupportedTagDropped( string tagKey, string tagValueTypeFullName); - [return: NotNullIfNotNull(nameof(value))] - private static string? TruncateString(string? value, int? maxLength) + private static ReadOnlySpan TruncateString(ReadOnlySpan 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 destination = stackalloc char[1]; + destination[0] = value; + this.WriteStringTag(ref state, key, destination); + } + + private void WriteCharValue(ref TArrayState state, char value) + { + Span 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 @@ private void WriteArrayTagInternal(ref TTagState state, string key, Array array, // 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 @@ private void WriteArrayTagIntNetFramework(ref TArrayState arrayState, Array arra 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 @@ private void WriteArrayTagLongNetFramework(ref TArrayState arrayState, Array arr 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); + var item = array.GetValue(i); + if (item == null) + { + this.arrayWriter.WriteNullValue(ref arrayState); + continue; + } + + switch (item) + { + 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; + } } - else + } + + private void WriteToArrayCovariant(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)) { - string?[] stringArray = ArrayPool.Shared.Rent(array.Length); - try + if (array.GetType() == typeof(sbyte[])) { - 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.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); } - finally + else { - ArrayPool.Shared.Return(stringArray); + 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 WriteToArray(ref TArrayState arrayState, TItem[] array) + private void WriteStructToArray(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 @@ private void WriteToArray(ref TArrayState arrayState, TItem[] array) } } - private void WriteStringsToArray(ref TArrayState arrayState, ReadOnlySpan 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 @@ private void WriteStringsToArray(ref TArrayState arrayState, ReadOnlySpan("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 void ToStringIsCalledForAllOtherTypes() 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) diff --git a/test/OpenTelemetry.Tests/Shared/AssemblyVersionExtensionsTests.cs b/test/OpenTelemetry.Tests/Internal/AssemblyVersionExtensionsTests.cs similarity index 95% rename from test/OpenTelemetry.Tests/Shared/AssemblyVersionExtensionsTests.cs rename to test/OpenTelemetry.Tests/Internal/AssemblyVersionExtensionsTests.cs index f057e8754c6..22cf81be0ee 100644 --- a/test/OpenTelemetry.Tests/Shared/AssemblyVersionExtensionsTests.cs +++ b/test/OpenTelemetry.Tests/Internal/AssemblyVersionExtensionsTests.cs @@ -4,10 +4,9 @@ #nullable enable using System.Reflection; -using OpenTelemetry.Internal; using Xunit; -namespace OpenTelemetry.Tests; +namespace OpenTelemetry.Internal.Tests; public class AssemblyVersionExtensionsTests { diff --git a/test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs b/test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs new file mode 100644 index 00000000000..0e7a8d45fa7 --- /dev/null +++ b/test/OpenTelemetry.Tests/Internal/JsonStringArrayTagWriterTests.cs @@ -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("array", data)); + + Assert.True(result); + Assert.Equal(expectedValue, tag.Value); + } + + private sealed class TestTagWriter : JsonStringArrayTagWriter + { + 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 value) + { + throw new NotImplementedException(); + } + + protected override void WriteArrayTag(ref Tag tag, string key, ArraySegment 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; + } + } +} diff --git a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj index 87c8a444837..106ba9ef4bd 100644 --- a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj +++ b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj @@ -25,7 +25,7 @@ - + diff --git a/test/OpenTelemetry.Tests/Shared/TagTransformerJsonHelperTest.cs b/test/OpenTelemetry.Tests/Shared/TagTransformerJsonHelperTest.cs deleted file mode 100644 index 14a310d2248..00000000000 --- a/test/OpenTelemetry.Tests/Shared/TagTransformerJsonHelperTest.cs +++ /dev/null @@ -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); - } -}