diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md index 30df46c5e3ccb..d4955741de97f 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md @@ -40,6 +40,16 @@ * In case of duplicate keys within the scopes, only the first entry is retained, while all subsequent duplicate entries are discarded. +* Resolved an issue where activity tags of various object types, including + double, float, and others, were previously formatted using + `CultureInfo.CurrentCulture`. This behavior caused inconsistencies in tag + value formatting depending on the regional settings of the machine where the + application was running. Such inconsistencies could lead to challenges in data + analysis and cause test failures in environments with differing cultural + settings. The fix ensures uniform and culture-independent formatting of + activity tag values, aligning with consistent data representation. + ([#39470](https://github.com/Azure/azure-sdk-for-net/issues/39470)) + ### Other Changes * Update OpenTelemetry dependencies diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ArrayExtensions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ArrayExtensions.cs index 1ec790acb12e3..d8b5c4871afc9 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ArrayExtensions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ArrayExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Globalization; using System.Text; namespace Azure.Monitor.OpenTelemetry.Exporter.Internals @@ -18,6 +19,12 @@ internal static class ArrayExtensions /// A comma delimited string of the components of the input array. [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(input))] public static string? ToCommaDelimitedString(this Array? input) + { + return ToCommaDelimitedString(input, CultureInfo.InvariantCulture); + } + + // This overload is used for testing purposes only. + internal static string? ToCommaDelimitedString(this Array? input, CultureInfo cultureInfo) { if (input == null) { @@ -29,7 +36,7 @@ internal static class ArrayExtensions { if (item != null) { - sb.Append(item); + sb.Append(Convert.ToString(item, cultureInfo)); sb.Append(','); } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs index 59d3569905c45..47443475c4d3b 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Text; @@ -85,7 +86,7 @@ internal static void AddPropertiesToTelemetry(IDictionary destin { // Note: if Key exceeds MaxLength or if Value is null, the entire KVP will be dropped. - destination.Add(tag.Key, tag.Value.ToString().Truncate(SchemaConstants.KVP_MaxValueLength) ?? "null"); + destination.Add(tag.Key, Convert.ToString(tag.Value, CultureInfo.InvariantCulture).Truncate(SchemaConstants.KVP_MaxValueLength) ?? "null"); } } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TagsTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TagsTests.cs index e997870763ad2..acea80dc5e756 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TagsTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TagsTests.cs @@ -4,7 +4,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; - +using System.Globalization; +using System.Threading; using Azure.Monitor.OpenTelemetry.Exporter.Internals; using Xunit; @@ -81,7 +82,7 @@ public void TagObjects_UnMapped() { var activityTagsProcessor = new ActivityTagsProcessor(); - IEnumerable> tagObjects = new Dictionary { ["somekey"] = "value" }; ; + IEnumerable> tagObjects = new Dictionary { ["somekey"] = "value" }; using var activity = CreateTestActivity(tagObjects); activityTagsProcessor.CategorizeTags(activity); @@ -344,6 +345,42 @@ public void ActivityTagsProcessor_CategorizeTags_ExtractsAuthUserId(ActivityKind Assert.Equal("TestUser", activityTagsProcessor.EndUserId); } + [Theory] + [InlineData("fr-FR")] // French culture + [InlineData("de-DE")] // German culture + public void TagObjects_TestCulture(string cultureName) + { + var activityTagsProcessor = new ActivityTagsProcessor(); + var originalCulture = Thread.CurrentThread.CurrentCulture; + var cultureInfo = new CultureInfo(cultureName); + Thread.CurrentThread.CurrentCulture = cultureInfo; + IDictionary properties = new Dictionary(); + + var doubleArray = new double[] { 1.1, 2.2, 3.3 }; + var doubleValue = 123.45; + + IEnumerable> tagObjects = new Dictionary + { + ["doubleArray"] = doubleArray, + ["double"] = doubleValue, + }; + + using var activity = CreateTestActivity(tagObjects); + activityTagsProcessor.CategorizeTags(activity); + TraceHelper.AddPropertiesToTelemetry(properties, ref activityTagsProcessor.UnMappedTags); + + // Asserting Culture Behavior + Assert.NotEqual("1.1,2.2,3.3", doubleArray.ToCommaDelimitedString(cultureInfo)); + Assert.NotEqual("123.45", doubleValue.ToString(cultureInfo)); + + // Asserting CultureInvariant Behavior + Assert.Equal("1.1,2.2,3.3", properties["doubleArray"]); + Assert.Equal("123.45", properties["double"]); + + // Cleanup: Revert to the original culture + Thread.CurrentThread.CurrentCulture = originalCulture; + } + private static Activity CreateTestActivity(IEnumerable>? additionalAttributes = null, ActivityKind activityKind = ActivityKind.Server) { var startTimestamp = DateTime.UtcNow;