From 4af3df9b81dbf78f3abbbd5d764573db8d54d373 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Tue, 18 Jun 2024 12:56:00 -0700 Subject: [PATCH] [shared] Use CultureInfo.InvariantCulture in TagWriter (#5700) --- .../CHANGELOG.md | 5 +++ .../CHANGELOG.md | 4 ++ src/Shared/TagWriter/TagWriter.cs | 5 ++- .../OtlpTestHelpers.cs | 3 +- .../ZipkinExporterTests.cs | 41 +++++++++++++++++-- 5 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index e8866580e55..6c5f736426a 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +* **Breaking change**: Non-primitive attribute (logs) and tag (traces) values + converted using `Convert.ToString` will now format using + `CultureInfo.InvariantCulture`. + ([#5700](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5700)) + ## 1.9.0 Released 2024-Jun-14 diff --git a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md index 28869770365..dedce653992 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +* **Breaking change**: Non-primitive tag values converted using + `Convert.ToString` will now format using `CultureInfo.InvariantCulture`. + ([#5700](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5700)) + ## 1.9.0 Released 2024-Jun-14 diff --git a/src/Shared/TagWriter/TagWriter.cs b/src/Shared/TagWriter/TagWriter.cs index fb56903363a..9594382df6f 100644 --- a/src/Shared/TagWriter/TagWriter.cs +++ b/src/Shared/TagWriter/TagWriter.cs @@ -4,6 +4,7 @@ #nullable enable using System.Diagnostics; +using System.Globalization; namespace OpenTelemetry.Internal; @@ -82,7 +83,7 @@ public bool TryWriteTag( default: try { - var stringValue = Convert.ToString(tag.Value/*TODO: , CultureInfo.InvariantCulture*/); + var stringValue = Convert.ToString(tag.Value, CultureInfo.InvariantCulture); if (stringValue == null) { return this.LogUnsupportedTagTypeAndReturnFalse(tag.Key, tag.Value); @@ -247,7 +248,7 @@ private void WriteToArrayTypeChecked(ref TArrayState arrayState, Array array, in // 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*/); + var stringValue = Convert.ToString(item, CultureInfo.InvariantCulture); if (stringValue == null) { this.arrayWriter.WriteNullValue(ref arrayState); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs index f6011fed01c..ec9dbbe4312 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTestHelpers.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Globalization; using Google.Protobuf.Collections; using Xunit; using OtlpCommon = OpenTelemetry.Proto.Common.V1; @@ -110,7 +111,7 @@ private static void AssertOtlpAttributeValue(object expected, OtlpCommon.AnyValu Assert.Equal(i, actual.IntValue); break; default: - Assert.Equal(expected.ToString(), actual.StringValue); + Assert.Equal(Convert.ToString(expected, CultureInfo.InvariantCulture), actual.StringValue); break; } } diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs index 308f2e6715a..01e40b66b16 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Diagnostics; +using System.Globalization; using System.Net; #if NETFRAMEWORK using System.Net.Http; @@ -358,7 +359,8 @@ public void IntegrationTest( var serviceName = (string)exporter.ParentProvider.GetDefaultResource().Attributes .Where(pair => pair.Key == ResourceSemanticConventions.AttributeServiceName).FirstOrDefault().Value; var resourceTags = string.Empty; - var activity = CreateTestActivity(isRootSpan: isRootSpan, status: status); + var dateTime = DateTime.UtcNow; + var activity = CreateTestActivity(isRootSpan: isRootSpan, status: status, dateTime: dateTime); if (useTestResource) { serviceName = "MyService"; @@ -422,7 +424,35 @@ 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.scope.name"":""CreateTestActivity"",""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/""," + + $@"""dateTimeKey"":""{Convert.ToString(dateTime, CultureInfo.InvariantCulture)}""," + + $@"""dateTimeArrayKey"":""[\u0022{Convert.ToString(dateTime, CultureInfo.InvariantCulture)}\u0022]""," + + statusTag + + errorTag + + @"""otel.scope.name"":""CreateTestActivity""," + + @"""otel.library.name"":""CreateTestActivity""," + + @"""peer.service"":""http://localhost:44312/""" + + "}}]", Responses[requestId]); } @@ -434,13 +464,16 @@ internal static Activity CreateTestActivity( bool addLinks = true, Resource resource = null, ActivityKind kind = ActivityKind.Client, - Status? status = null) + Status? status = null, + DateTime? dateTime = null) { var startTimestamp = DateTime.UtcNow; var endTimestamp = startTimestamp.AddSeconds(60); var eventTimestamp = DateTime.UtcNow; var traceId = ActivityTraceId.CreateFromString("e8ea7e9ac72de94e91fabc613f9686b2".AsSpan()); + dateTime ??= DateTime.UtcNow; + var parentSpanId = isRootSpan ? default : ActivitySpanId.CreateFromBytes(new byte[] { 12, 23, 34, 45, 56, 67, 78, 89 }); var attributes = new Dictionary @@ -454,6 +487,8 @@ internal static Activity CreateTestActivity( { "boolKey", true }, { "boolArrayKey", new bool[] { true, false } }, { "http.host", "http://localhost:44312/" }, // simulating instrumentation tag adding http.host + { "dateTimeKey", dateTime.Value }, + { "dateTimeArrayKey", new DateTime[] { dateTime.Value } }, }; if (additionalAttributes != null) {