diff --git a/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md b/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md index 4b0cab56f41..8278480772b 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md @@ -2,6 +2,25 @@ ## Unreleased +* Improve the conversion and formatting of attribute values. + The list of data types that must be supported per the + [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/common#attribute) + is more narrow than what the .NET OpenTelemetry SDK supports. Numeric + [built-in value types](https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/built-in-types) + are supported by converting to a `long` or `double` as appropriate except for + numeric types that could cause overflow (`ulong`) or rounding (`decimal`) + which are converted to strings. Non-numeric built-in types - `string`, + `char`, `bool` are supported. All other types are converted to a `string`. + Array values are also supported. + ([#3281](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3281)) +* Fix conversion of array-valued resource attributes. They were previously + converted to a string like "System.String[]". + ([#3281](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3281)) +* Fix exporting of array-valued attributes on an `Activity`. Previously, each + item in the array would result in a new tag on an exported `Activity`. Now, + array-valued attributes are serialzed to a JSON-array representation. + ([#3281](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3281)) + ## 1.3.0-beta.2 Released 2022-May-16 diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs index 33a3f8ee332..99be25fdbc9 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs +++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs @@ -230,20 +230,6 @@ public static JaegerSpanRef ToJaegerSpanRef(this in ActivityLink link) return new JaegerSpanRef(refType, traceId.Low, traceId.High, spanId.Low); } - public static JaegerTag ToJaegerTag(this KeyValuePair attribute) - { - return attribute.Value switch - { - string s => new JaegerTag(attribute.Key, JaegerTagType.STRING, vStr: s), - int i => new JaegerTag(attribute.Key, JaegerTagType.LONG, vLong: Convert.ToInt64(i)), - long l => new JaegerTag(attribute.Key, JaegerTagType.LONG, vLong: l), - float f => new JaegerTag(attribute.Key, JaegerTagType.DOUBLE, vDouble: Convert.ToDouble(f)), - double d => new JaegerTag(attribute.Key, JaegerTagType.DOUBLE, vDouble: d), - bool b => new JaegerTag(attribute.Key, JaegerTagType.BOOL, vBool: b), - _ => new JaegerTag(attribute.Key, JaegerTagType.STRING, vStr: attribute.Value.ToString()), - }; - } - public static long ToEpochMicroseconds(this DateTime utcDateTime) { // Truncate sub-microsecond precision before offsetting by the Unix Epoch to avoid @@ -260,42 +246,6 @@ public static long ToEpochMicroseconds(this DateTimeOffset timestamp) return microseconds - UnixEpochMicroseconds; } - private static void ProcessJaegerTagArray(ref PooledList tags, KeyValuePair activityTag) - { - if (activityTag.Value is int[] intArray) - { - foreach (var item in intArray) - { - JaegerTag jaegerTag = new JaegerTag(activityTag.Key, JaegerTagType.LONG, vLong: Convert.ToInt64(item)); - PooledList.Add(ref tags, jaegerTag); - } - } - else if (activityTag.Value is string[] stringArray) - { - foreach (var item in stringArray) - { - JaegerTag jaegerTag = new JaegerTag(activityTag.Key, JaegerTagType.STRING, vStr: item); - PooledList.Add(ref tags, jaegerTag); - } - } - else if (activityTag.Value is bool[] boolArray) - { - foreach (var item in boolArray) - { - JaegerTag jaegerTag = new JaegerTag(activityTag.Key, JaegerTagType.BOOL, vBool: item); - PooledList.Add(ref tags, jaegerTag); - } - } - else if (activityTag.Value is double[] doubleArray) - { - foreach (var item in doubleArray) - { - JaegerTag jaegerTag = new JaegerTag(activityTag.Key, JaegerTagType.DOUBLE, vDouble: item); - PooledList.Add(ref tags, jaegerTag); - } - } - } - private struct TagEnumerationState : IActivityEnumerator>, PeerServiceResolver.IPeerServiceState { public PooledList Tags; @@ -316,14 +266,15 @@ private struct TagEnumerationState : IActivityEnumerator activityTag) { - if (activityTag.Value is Array) - { - ProcessJaegerTagArray(ref this.Tags, activityTag); - } - else if (activityTag.Value != null) + if (activityTag.Value != null) { var key = activityTag.Key; - var jaegerTag = activityTag.ToJaegerTag(); + + if (!JaegerTagTransformer.Instance.TryTransformTag(activityTag, out var jaegerTag)) + { + return true; + } + if (jaegerTag.VStr != null) { PeerServiceResolver.InspectTag(ref this, key, jaegerTag.VStr); @@ -400,18 +351,14 @@ private struct EventTagsEnumerationState : IActivityEnumerator tag) { - if (tag.Value is Array) - { - ProcessJaegerTagArray(ref this.Tags, tag); - } - else if (tag.Value != null) + if (JaegerTagTransformer.Instance.TryTransformTag(tag, out var result)) { - PooledList.Add(ref this.Tags, tag.ToJaegerTag()); - } + PooledList.Add(ref this.Tags, result); - if (tag.Key == "event") - { - this.HasEvent = true; + if (tag.Key == "event") + { + this.HasEvent = true; + } } return true; diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTagTransformer.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTagTransformer.cs new file mode 100644 index 00000000000..ccc38b2efe7 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerTagTransformer.cs @@ -0,0 +1,52 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Exporter.Jaeger.Implementation; + +internal sealed class JaegerTagTransformer : TagTransformer +{ + private JaegerTagTransformer() + { + } + + public static JaegerTagTransformer Instance { get; } = new(); + + protected override JaegerTag TransformIntegralTag(string key, long value) + { + return new JaegerTag(key, JaegerTagType.LONG, vLong: value); + } + + protected override JaegerTag TransformFloatingPointTag(string key, double value) + { + return new JaegerTag(key, JaegerTagType.DOUBLE, vDouble: value); + } + + protected override JaegerTag TransformBooleanTag(string key, bool value) + { + return new JaegerTag(key, JaegerTagType.BOOL, vBool: value); + } + + protected override JaegerTag TransformStringTag(string key, string value) + { + return new JaegerTag(key, JaegerTagType.STRING, vStr: value); + } + + protected override JaegerTag TransformArrayTag(string key, Array array) + => this.TransformStringTag(key, System.Text.Json.JsonSerializer.Serialize(array)); +} diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/Process.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/Process.cs index 95bb58587b1..1d76584748e 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/Implementation/Process.cs +++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/Process.cs @@ -15,7 +15,6 @@ // using System.Collections.Generic; -using System.Linq; using System.Text; using Thrift.Protocol; using Thrift.Protocol.Entities; @@ -29,20 +28,6 @@ public Process(string serviceName) this.ServiceName = serviceName; } - public Process(string serviceName, IEnumerable> processTags) - : this(serviceName, processTags?.Select(pt => pt.ToJaegerTag()).ToDictionary(pt => pt.Key, pt => pt)) - { - } - - internal Process(string serviceName, Dictionary processTags) - : this(serviceName) - { - if (processTags != null) - { - this.Tags = processTags; - } - } - public string ServiceName { get; internal set; } internal Dictionary Tags { get; set; } diff --git a/src/OpenTelemetry.Exporter.Jaeger/JaegerExporter.cs b/src/OpenTelemetry.Exporter.Jaeger/JaegerExporter.cs index 5d57747a57b..4a26ce813d4 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/JaegerExporter.cs +++ b/src/OpenTelemetry.Exporter.Jaeger/JaegerExporter.cs @@ -145,12 +145,15 @@ internal void SetResourceAndInitializeBatch(Resource resource) } } - if (process.Tags == null) + if (JaegerTagTransformer.Instance.TryTransformTag(label, out var result)) { - process.Tags = new Dictionary(); - } + if (process.Tags == null) + { + process.Tags = new Dictionary(); + } - process.Tags[key] = label.ToJaegerTag(); + process.Tags[key] = result; + } } if (serviceName != null) diff --git a/src/OpenTelemetry.Exporter.Jaeger/OpenTelemetry.Exporter.Jaeger.csproj b/src/OpenTelemetry.Exporter.Jaeger/OpenTelemetry.Exporter.Jaeger.csproj index 5b8c06244cc..4f51b3ecab5 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/OpenTelemetry.Exporter.Jaeger.csproj +++ b/src/OpenTelemetry.Exporter.Jaeger/OpenTelemetry.Exporter.Jaeger.csproj @@ -33,10 +33,12 @@ + + diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs index 6ec0710b17d..6e62e19e805 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs @@ -355,8 +355,7 @@ public bool ForEach(KeyValuePair activityTag) this.Created = true; } - var attribute = activityTag.ToOtlpAttribute(); - if (attribute != null) + if (OtlpKeyValueTransformer.Instance.TryTransformTag(activityTag, out var attribute)) { PooledList.Add(ref this.Tags, attribute); diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs index 5cb08330049..6139831d162 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs @@ -103,10 +103,9 @@ internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord) { otlpLogRecord.Body = new OtlpCommon.AnyValue { StringValue = stateValue.Value as string }; } - else + else if (OtlpKeyValueTransformer.Instance.TryTransformTag(stateValue, out var result)) { - var otlpAttribute = stateValue.ToOtlpAttribute(); - otlpLogRecord.Attributes.Add(otlpAttribute); + otlpLogRecord.Attributes.Add(result); } } } @@ -150,8 +149,10 @@ void ProcessScope(LogRecordScope scope, OtlpLogs.LogRecord otlpLog) foreach (var scopeItem in scope) { var scopeItemWithDepthInfo = new KeyValuePair($"[Scope.{scopeDepth}]:{scopeItem.Key}", scopeItem.Value); - var otlpAttribute = scopeItemWithDepthInfo.ToOtlpAttribute(); - otlpLog.Attributes.Add(otlpAttribute); + if (OtlpKeyValueTransformer.Instance.TryTransformTag(scopeItemWithDepthInfo, out var result)) + { + otlpLog.Attributes.Add(result); + } } } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs index cc6305b0bc6..fe21b8d058e 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs @@ -279,7 +279,10 @@ private static void AddAttributes(ReadOnlyTagCollection tags, RepeatedField -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using OtlpCommon = Opentelemetry.Proto.Common.V1; - -namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation -{ - internal static class OtlpCommonExtensions - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static OtlpCommon.KeyValue ToOtlpAttribute(this KeyValuePair kvp) - { - if (kvp.Value == null) - { - return null; - } - - var value = ToOtlpValue(kvp.Value); - - if (value == null) - { - OpenTelemetryProtocolExporterEventSource.Log.UnsupportedAttributeType(kvp.Value.GetType().ToString(), kvp.Key); - return null; - } - - return new OtlpCommon.KeyValue { Key = kvp.Key, Value = value }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static OtlpCommon.AnyValue ToOtlpValue(object value) - { - switch (value) - { - case char: - case string: - return new OtlpCommon.AnyValue { StringValue = Convert.ToString(value) }; - case bool b: - return new OtlpCommon.AnyValue { BoolValue = b }; - case byte: - case sbyte: - case short: - case ushort: - case int: - case uint: - case long: - return new OtlpCommon.AnyValue { IntValue = Convert.ToInt64(value) }; - case float: - case double: - return new OtlpCommon.AnyValue { DoubleValue = Convert.ToDouble(value) }; - case Array array: - return ToOtlpArrayValue(array); - - // 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 - { - return value != null - ? new OtlpCommon.AnyValue { StringValue = value.ToString() } - : null; - } - catch - { - return null; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static OtlpCommon.AnyValue ToOtlpArrayValue(Array array) - { -#pragma warning disable SA1011 // Closing square brackets should be spaced correctly - var arrayValue = new OtlpCommon.ArrayValue(); - switch (array) - { - case char[]: - case string[]: - case bool[]: - case byte[]: - case sbyte[]: - case short[]: - case ushort[]: - case int[]: - case uint[]: - case long[]: - case float[]: - case double[]: - foreach (var item in array) - { - arrayValue.Values.Add(ToOtlpValue(item) ?? new OtlpCommon.AnyValue { }); - } - - return new OtlpCommon.AnyValue { ArrayValue = arrayValue }; - default: - foreach (var item in array) - { - try - { - var value = item != null - ? ToOtlpValue(item.ToString()) - : new OtlpCommon.AnyValue { }; - arrayValue.Values.Add(value); - } - catch - { - return null; - } - } - - return new OtlpCommon.AnyValue { ArrayValue = arrayValue }; - } -#pragma warning restore SA1011 // Closing square brackets should be spaced correctly - } - } -} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpKeyValueTransformer.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpKeyValueTransformer.cs new file mode 100644 index 00000000000..dc254a451ca --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpKeyValueTransformer.cs @@ -0,0 +1,95 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using OpenTelemetry.Internal; +using OtlpCommon = Opentelemetry.Proto.Common.V1; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; + +internal sealed class OtlpKeyValueTransformer : TagAndValueTransformer +{ + private OtlpKeyValueTransformer() + { + } + + public static OtlpKeyValueTransformer Instance { get; } = new(); + + protected override OtlpCommon.KeyValue TransformIntegralTag(string key, long value) + { + return new OtlpCommon.KeyValue { Key = key, Value = this.TransformIntegralValue(value) }; + } + + protected override OtlpCommon.KeyValue TransformFloatingPointTag(string key, double value) + { + return new OtlpCommon.KeyValue { Key = key, Value = this.TransformFloatingPointValue(value) }; + } + + protected override OtlpCommon.KeyValue TransformBooleanTag(string key, bool value) + { + return new OtlpCommon.KeyValue { Key = key, Value = this.TransformBooleanValue(value) }; + } + + protected override OtlpCommon.KeyValue TransformStringTag(string key, string value) + { + return new OtlpCommon.KeyValue { Key = key, Value = this.TransformStringValue(value) }; + } + + protected override OtlpCommon.KeyValue TransformArrayTag(string key, Array array) + { + return new OtlpCommon.KeyValue { Key = key, Value = this.TransformArrayValue(array) }; + } + + protected override OtlpCommon.AnyValue TransformIntegralValue(long value) + { + return new OtlpCommon.AnyValue { IntValue = value }; + } + + protected override OtlpCommon.AnyValue TransformFloatingPointValue(double value) + { + return new OtlpCommon.AnyValue { DoubleValue = value }; + } + + protected override OtlpCommon.AnyValue TransformBooleanValue(bool value) + { + return new OtlpCommon.AnyValue { BoolValue = value }; + } + + protected override OtlpCommon.AnyValue TransformStringValue(string value) + { + return new OtlpCommon.AnyValue { StringValue = value }; + } + + protected override OtlpCommon.AnyValue TransformArrayValue(Array array) + { + var arrayValue = new OtlpCommon.ArrayValue(); + + foreach (var item in array) + { + try + { + var value = item != null ? this.TransformValue(item) : new OtlpCommon.AnyValue(); + arrayValue.Values.Add(value); + } + catch + { + return null; + } + } + + return new OtlpCommon.AnyValue { ArrayValue = arrayValue }; + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.cs index fd03cb6cf67..5086b0cbc2c 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ResourceExtensions.cs @@ -30,10 +30,9 @@ public static OtlpResource.Resource ToOtlpResource(this Resource resource) foreach (KeyValuePair attribute in resource.Attributes) { - var otlpAttribute = attribute.ToOtlpAttribute(); - if (otlpAttribute != null) + if (OtlpKeyValueTransformer.Instance.TryTransformTag(attribute, out var result)) { - processResource.Attributes.Add(otlpAttribute); + processResource.Attributes.Add(result); } } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj index e01f6ec65b5..fdb451dd39f 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj @@ -49,6 +49,8 @@ + + diff --git a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md index 5585eaf7770..9b7b23f4f76 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md @@ -2,6 +2,25 @@ ## Unreleased +* Improve the conversion and formatting of attribute values. + The list of data types that must be supported per the + [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/common#attribute) + is more narrow than what the .NET OpenTelemetry SDK supports. Numeric + [built-in value types](https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/built-in-types) + are supported by converting to a `long` or `double` as appropriate except for + numeric types that could cause overflow (`ulong`) or rounding (`decimal`) + which are converted to strings. Non-numeric built-in types - `string`, + `char`, `bool` are supported. All other types are converted to a `string`. + Array values are also supported. + ([#3281](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3281)) +* Fix conversion of array-valued resource attributes. They were previously + converted to a string like "System.String[]". + ([#3281](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3281)) +* Fix exporting of array-valued attributes on an `Activity`. Previously, each + item in the array would result in a new tag on an exported `Activity`. Now, + array-valued attributes are serialzed to a JSON-array representation. + ([#3281](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3281)) + ## 1.3.0-beta.2 Released 2022-May-16 diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs index 1ee3b7cf236..ac75989f35a 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs @@ -17,7 +17,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading; using OpenTelemetry.Internal; @@ -178,12 +177,18 @@ public void Write(Utf8JsonWriter writer) { foreach (var tag in this.LocalEndpoint.Tags ?? Enumerable.Empty>()) { - writer.WriteString(tag.Key, ConvertObjectToString(tag.Value)); + if (ZipkinTagTransformer.Instance.TryTransformTag(tag, out var result)) + { + writer.WriteString(tag.Key, result); + } } foreach (var tag in this.Tags) { - writer.WriteString(tag.Key, ConvertObjectToString(tag.Value)); + if (ZipkinTagTransformer.Instance.TryTransformTag(tag, out var result)) + { + writer.WriteString(tag.Key, result); + } } } finally @@ -196,26 +201,5 @@ public void Write(Utf8JsonWriter writer) writer.WriteEndObject(); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string ConvertObjectToString(object obj) - { - return obj switch - { - string stringVal => stringVal, - bool boolVal => GetBoolString(boolVal), - int[] arrayValue => string.Join(",", arrayValue), - long[] arrayValue => string.Join(",", arrayValue), - double[] arrayValue => string.Join(",", arrayValue), - bool[] arrayValue => string.Join(",", arrayValue.Select(GetBoolString)), - _ => obj.ToString(), - }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string GetBoolString(bool value) - { - return value ? "true" : "false"; - } } } diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagTransformer.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagTransformer.cs new file mode 100644 index 00000000000..0a7a0d85b2e --- /dev/null +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinTagTransformer.cs @@ -0,0 +1,40 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Exporter.Zipkin.Implementation; + +internal sealed class ZipkinTagTransformer : TagTransformer +{ + private ZipkinTagTransformer() + { + } + + public static ZipkinTagTransformer Instance { get; } = new(); + + protected override string TransformIntegralTag(string key, long value) => value.ToString(); + + protected override string TransformFloatingPointTag(string key, double value) => value.ToString(); + + protected override string TransformBooleanTag(string key, bool value) => value ? "true" : "false"; + + protected override string TransformStringTag(string key, string value) => value; + + protected override string TransformArrayTag(string key, Array array) + => this.TransformStringTag(key, System.Text.Json.JsonSerializer.Serialize(array)); +} diff --git a/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj b/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj index bd7d123d74f..c3eca2e116a 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj +++ b/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj @@ -28,6 +28,7 @@ + diff --git a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs index c80d41f28e5..0db72d4cba7 100644 --- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs +++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs @@ -384,6 +384,12 @@ public void MetricViewIgnored(string instrumentName, string meterName, string re this.WriteEvent(41, instrumentName, meterName, reason, fix); } + [Event(42, Message = "Unsupported attribute type '{0}' for '{1}'. Attribute will not be exported.", Level = EventLevel.Warning)] + public void UnsupportedAttributeType(string type, string key) + { + this.WriteEvent(42, type.ToString(), key); + } + #if DEBUG public class OpenTelemetryEventListener : EventListener { diff --git a/src/OpenTelemetry/Internal/TagAndValueTransformer.cs b/src/OpenTelemetry/Internal/TagAndValueTransformer.cs new file mode 100644 index 00000000000..4438dfb9e31 --- /dev/null +++ b/src/OpenTelemetry/Internal/TagAndValueTransformer.cs @@ -0,0 +1,61 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Diagnostics; + +namespace OpenTelemetry.Internal; + +internal abstract class TagAndValueTransformer : TagTransformer +{ + protected TValue TransformValue(object value) + { + Debug.Assert(value != null, $"{nameof(value)} was null"); + + switch (value) + { + case char: + case string: + return this.TransformStringValue(Convert.ToString(value)); + case bool b: + return this.TransformBooleanValue(b); + case byte: + case sbyte: + case short: + case ushort: + case int: + case uint: + case long: + return this.TransformIntegralValue(Convert.ToInt64(value)); + case float: + case double: + return this.TransformFloatingPointValue(Convert.ToDouble(value)); + default: + // This could throw an exception. The caller is expected to handle. + return this.TransformStringValue(value.ToString()); + } + } + + protected abstract TValue TransformIntegralValue(long value); + + protected abstract TValue TransformFloatingPointValue(double value); + + protected abstract TValue TransformBooleanValue(bool value); + + protected abstract TValue TransformStringValue(string value); + + protected abstract TValue TransformArrayValue(Array value); +} diff --git a/src/OpenTelemetry/Internal/TagTransformer.cs b/src/OpenTelemetry/Internal/TagTransformer.cs new file mode 100644 index 00000000000..e63ccd623f9 --- /dev/null +++ b/src/OpenTelemetry/Internal/TagTransformer.cs @@ -0,0 +1,138 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; + +namespace OpenTelemetry.Internal; + +internal abstract class TagTransformer +{ + public bool TryTransformTag(KeyValuePair tag, out T result) + { + if (tag.Value == null) + { + result = default; + return false; + } + + switch (tag.Value) + { + case char: + case string: + result = this.TransformStringTag(tag.Key, Convert.ToString(tag.Value)); + 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); + } + catch + { + // If an exception is thrown when calling ToString + // on any element of the array, then the entire array value + // is ignored. + OpenTelemetrySdkEventSource.Log.UnsupportedAttributeType(tag.Value.GetType().ToString(), tag.Key); + result = default; + return false; + } + + 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 + { + result = this.TransformStringTag(tag.Key, tag.Value.ToString()); + } + catch + { + // If ToString throws an exception then the tag is ignored. + OpenTelemetrySdkEventSource.Log.UnsupportedAttributeType(tag.Value.GetType().ToString(), tag.Key); + result = default; + return false; + } + + 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); + + private T TransformArrayTagInternal(string key, Array array) + { + // 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.TransformArrayTag(key, array), + 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), + int[] => this.TransformArrayTag(key, array), + uint[] => this.TransformArrayTag(key, array), + long[] => this.TransformArrayTag(key, array), + float[] => this.TransformArrayTag(key, array), + double[] => this.TransformArrayTag(key, array), + _ => this.ConvertToStringArrayThenTransformArrayTag(key, array), + }; + } + + private T ConvertToStringArrayThenTransformArrayTag(string key, Array array) + { + var stringArray = new string[array.Length]; + + for (var i = 0; i < array.Length; ++i) + { + stringArray[i] = array.GetValue(i)?.ToString(); + } + + return this.TransformArrayTag(key, stringArray); + } +} diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs index 44567a04294..ad4d6db9c01 100644 --- a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs +++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // + using System; using System.Collections.Generic; using System.Diagnostics; @@ -102,18 +103,15 @@ public void JaegerActivityConverterTest_ConvertActivityToJaegerSpan_AllPropertie var logs = jaegerSpan.Logs.ToArray(); var jaegerLog = logs[0]; Assert.Equal(activity.Events.First().Timestamp.ToEpochMicroseconds(), jaegerLog.Timestamp); - Assert.Equal(4, jaegerLog.Fields.Count); + Assert.Equal(3, jaegerLog.Fields.Count); var eventFields = jaegerLog.Fields.ToArray(); var eventField = eventFields[0]; Assert.Equal("key", eventField.Key); Assert.Equal("value", eventField.VStr); eventField = eventFields[1]; Assert.Equal("string_array", eventField.Key); - Assert.Equal("a", eventField.VStr); + Assert.Equal(@"[""a"",""b""]", eventField.VStr); eventField = eventFields[2]; - Assert.Equal("string_array", eventField.Key); - Assert.Equal("b", eventField.VStr); - eventField = eventFields[3]; Assert.Equal("event", eventField.Key); Assert.Equal("Event1", eventField.VStr); @@ -167,12 +165,12 @@ public void JaegerActivityConverterTest_ConvertActivityToJaegerSpan_NoAttributes var logs = jaegerSpan.Logs.ToArray(); var jaegerLog = logs[0]; Assert.Equal(activity.Events.First().Timestamp.ToEpochMicroseconds(), jaegerLog.Timestamp); - Assert.Equal(4, jaegerLog.Fields.Count); + Assert.Equal(3, jaegerLog.Fields.Count); var eventFields = jaegerLog.Fields.ToArray(); var eventField = eventFields[0]; Assert.Equal("key", eventField.Key); Assert.Equal("value", eventField.VStr); - eventField = eventFields[3]; + eventField = eventFields[2]; Assert.Equal("event", eventField.Key); Assert.Equal("Event1", eventField.VStr); @@ -298,24 +296,29 @@ public void JaegerActivityConverterTest_ConvertActivityToJaegerSpan_NoLinks() Assert.Equal(true, tag.VBool); tag = tags[6]; - Assert.Equal(JaegerTagType.LONG, tag.VType); + Assert.Equal(JaegerTagType.STRING, tag.VType); Assert.Equal("int_array", tag.Key); - Assert.Equal(1, tag.VLong); + Assert.Equal(System.Text.Json.JsonSerializer.Serialize(new[] { 1, 2 }), tag.VStr); - tag = tags[8]; - Assert.Equal(JaegerTagType.BOOL, tag.VType); + tag = tags[7]; + Assert.Equal(JaegerTagType.STRING, tag.VType); Assert.Equal("bool_array", tag.Key); - Assert.Equal(true, tag.VBool); + Assert.Equal(System.Text.Json.JsonSerializer.Serialize(new[] { true, false }), tag.VStr); - tag = tags[10]; - Assert.Equal(JaegerTagType.DOUBLE, tag.VType); + tag = tags[8]; + Assert.Equal(JaegerTagType.STRING, tag.VType); Assert.Equal("double_array", tag.Key); - Assert.Equal(1, tag.VDouble); + Assert.Equal(System.Text.Json.JsonSerializer.Serialize(new[] { 1, 1.1 }), tag.VStr); - tag = tags[12]; + tag = tags[9]; Assert.Equal(JaegerTagType.STRING, tag.VType); Assert.Equal("string_array", tag.Key); - Assert.Equal("a", tag.VStr); + Assert.Equal(System.Text.Json.JsonSerializer.Serialize(new[] { "a", "b" }), tag.VStr); + + tag = tags[10]; + Assert.Equal(JaegerTagType.STRING, tag.VType); + Assert.Equal("obj_array", tag.Key); + Assert.Equal(System.Text.Json.JsonSerializer.Serialize(new[] { 1.ToString(), false.ToString(), new object().ToString(), "string", string.Empty, null }), tag.VStr); // The second to last tag should be span.kind in this case tag = tags[tags.Length - 2]; @@ -332,12 +335,12 @@ public void JaegerActivityConverterTest_ConvertActivityToJaegerSpan_NoLinks() var logs = jaegerSpan.Logs.ToArray(); var jaegerLog = logs[0]; Assert.Equal(activity.Events.First().Timestamp.ToEpochMicroseconds(), jaegerLog.Timestamp); - Assert.Equal(4, jaegerLog.Fields.Count); + Assert.Equal(3, jaegerLog.Fields.Count); var eventFields = jaegerLog.Fields.ToArray(); var eventField = eventFields[0]; Assert.Equal("key", eventField.Key); Assert.Equal("value", eventField.VStr); - eventField = eventFields[3]; + eventField = eventFields[2]; Assert.Equal("event", eventField.Key); Assert.Equal("Event1", eventField.VStr); Assert.Equal(activity.Events.First().Timestamp.ToEpochMicroseconds(), jaegerLog.Timestamp); @@ -635,6 +638,8 @@ internal static Activity CreateTestActivity( var attributes = new Dictionary { + { "exceptionFromToString", new MyToStringMethodThrowsAnException() }, + { "exceptionFromToStringInArray", new MyToStringMethodThrowsAnException[] { new MyToStringMethodThrowsAnException() } }, { "stringKey", "value" }, { "longKey", 1L }, { "longKey2", 1 }, @@ -645,6 +650,7 @@ internal static Activity CreateTestActivity( { "bool_array", new bool[] { true, false } }, { "double_array", new double[] { 1.0, 1.1 } }, { "string_array", new string[] { "a", "b" } }, + { "obj_array", new object[] { 1, false, new object(), "string", string.Empty, null } }, }; if (additionalAttributes != null) { @@ -847,5 +853,13 @@ public override string ToString() return this.Name; } } + + private class MyToStringMethodThrowsAnException + { + public override string ToString() + { + throw new Exception("Nope."); + } + } } } diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/JaegerExporterTests.cs b/test/OpenTelemetry.Exporter.Jaeger.Tests/JaegerExporterTests.cs index 82096ba587c..5d018bcf8c1 100644 --- a/test/OpenTelemetry.Exporter.Jaeger.Tests/JaegerExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/JaegerExporterTests.cs @@ -201,7 +201,8 @@ public void JaegerTraceExporter_SetResource_CombinesTags() using var jaegerTraceExporter = new JaegerExporter(new JaegerExporterOptions()); var process = jaegerTraceExporter.Process; - process.Tags = new Dictionary { ["Tag1"] = new KeyValuePair("Tag1", "value1").ToJaegerTag() }; + JaegerTagTransformer.Instance.TryTransformTag(new KeyValuePair("Tag1", "value1"), out var result); + process.Tags = new Dictionary { ["Tag1"] = result }; jaegerTraceExporter.SetResourceAndInitializeBatch(ResourceBuilder.CreateEmpty().AddAttributes(new Dictionary { diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs index 60652ec0957..8b97444dee1 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpAttributeTests.cs @@ -29,22 +29,19 @@ public class OtlpAttributeTests public void NullValueAttribute() { var kvp = new KeyValuePair("key", null); - var attribute = kvp.ToOtlpAttribute(); - Assert.Null(attribute); + Assert.False(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out var _)); } [Fact] public void EmptyArrays() { - var kvp = new KeyValuePair("key", new int[] { }); - var attribute = kvp.ToOtlpAttribute(); - Assert.NotNull(attribute); + var kvp = new KeyValuePair("key", Array.Empty()); + Assert.True(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out var attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); Assert.Empty(attribute.Value.ArrayValue.Values); - kvp = new KeyValuePair("key", new object[] { }); - attribute = kvp.ToOtlpAttribute(); - Assert.NotNull(attribute); + kvp = new KeyValuePair("key", Array.Empty()); + Assert.True(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); Assert.Empty(attribute.Value.ArrayValue.Values); } @@ -67,8 +64,7 @@ public void EmptyArrays() public void IntegralTypesSupported(object value) { var kvp = new KeyValuePair("key", value); - var attribute = kvp.ToOtlpAttribute(); - Assert.NotNull(attribute); + Assert.True(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out var attribute)); switch (value) { @@ -97,8 +93,7 @@ public void IntegralTypesSupported(object value) public void FloatingPointTypesSupported(object value) { var kvp = new KeyValuePair("key", value); - var attribute = kvp.ToOtlpAttribute(); - Assert.NotNull(attribute); + Assert.True(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out var attribute)); switch (value) { @@ -125,8 +120,7 @@ public void FloatingPointTypesSupported(object value) public void BooleanTypeSupported(object value) { var kvp = new KeyValuePair("key", value); - var attribute = kvp.ToOtlpAttribute(); - Assert.NotNull(attribute); + Assert.True(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out var attribute)); switch (value) { @@ -153,8 +147,7 @@ public void BooleanTypeSupported(object value) public void StringTypesSupported(object value) { var kvp = new KeyValuePair("key", value); - var attribute = kvp.ToOtlpAttribute(); - Assert.NotNull(attribute); + Assert.True(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out var attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.StringValue, attribute.Value.ValueCase); Assert.Equal(Convert.ToString(value), attribute.Value.StringValue); } @@ -166,14 +159,12 @@ public void StringArrayTypesSupported() var stringArray = new string[] { "a", "b", "c", string.Empty, null }; var kvp = new KeyValuePair("key", charArray); - var attribute = kvp.ToOtlpAttribute(); - Assert.NotNull(attribute); + Assert.True(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out var attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); Assert.Equal(charArray.Select(x => x.ToString()), attribute.Value.ArrayValue.Values.Select(x => x.StringValue)); kvp = new KeyValuePair("key", stringArray); - attribute = kvp.ToOtlpAttribute(); - Assert.NotNull(attribute); + Assert.True(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); for (var i = 0; i < stringArray.Length; ++i) @@ -213,8 +204,7 @@ public void ToStringIsCalledForAllOtherTypes() foreach (var value in testValues) { var kvp = new KeyValuePair("key", value); - var attribute = kvp.ToOtlpAttribute(); - Assert.NotNull(attribute); + Assert.True(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out var attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.StringValue, attribute.Value.ValueCase); Assert.Equal(value.ToString(), attribute.Value.StringValue); } @@ -222,8 +212,7 @@ public void ToStringIsCalledForAllOtherTypes() foreach (var value in testArrayValues) { var kvp = new KeyValuePair("key", value); - var attribute = kvp.ToOtlpAttribute(); - Assert.NotNull(attribute); + Assert.True(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out var attribute)); Assert.Equal(OtlpCommon.AnyValue.ValueOneofCase.ArrayValue, attribute.Value.ValueCase); var array = value as Array; @@ -247,12 +236,10 @@ public void ToStringIsCalledForAllOtherTypes() public void ExceptionInToStringIsCaught() { var kvp = new KeyValuePair("key", new MyToStringMethodThrowsAnException()); - var attribute = kvp.ToOtlpAttribute(); - Assert.Null(attribute); + Assert.False(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out var _)); kvp = new KeyValuePair("key", new object[] { 1, false, new MyToStringMethodThrowsAnException() }); - attribute = kvp.ToOtlpAttribute(); - Assert.Null(attribute); + Assert.False(OtlpKeyValueTransformer.Instance.TryTransformTag(kvp, out var _)); } private class MyToStringMethodThrowsAnException diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs index 2e34b9d465e..37b715501a6 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs @@ -359,7 +359,7 @@ public void IntegrationTest( } Assert.Equal( - $@"[{{""traceId"":""{traceId}"",""name"":""Name"",{parentId}""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""{serviceName}""{ipInformation}}},""remoteEndpoint"":{{""serviceName"":""http://localhost:44312/""}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{{resourceTags}""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""longArrayKey"":""1,2"",""boolKey"":""true"",""boolArrayKey"":""true,false"",""http.host"":""http://localhost:44312/"",{statusTag}{errorTag}""otel.library.name"":""CreateTestActivity"",""peer.service"":""http://localhost:44312/""}}}}]", + $@"[{{""traceId"":""{traceId}"",""name"":""Name"",{parentId}""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""{serviceName}""{ipInformation}}},""remoteEndpoint"":{{""serviceName"":""http://localhost:44312/""}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{{resourceTags}""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""longArrayKey"":""[1,2]"",""boolKey"":""true"",""boolArrayKey"":""[true,false]"",""http.host"":""http://localhost:44312/"",{statusTag}{errorTag}""otel.library.name"":""CreateTestActivity"",""peer.service"":""http://localhost:44312/""}}}}]", Responses[requestId]); }