Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AzureMonitorExporter] Fix Culture-Specific Formatting of Activity Tags #40000

Merged
merged 7 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Globalization;
using System.Text;

namespace Azure.Monitor.OpenTelemetry.Exporter.Internals
Expand All @@ -18,6 +19,12 @@ internal static class ArrayExtensions
/// <returns>A comma delimited string of the components of the input array.</returns>
[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)
{
Expand All @@ -29,7 +36,7 @@ internal static class ArrayExtensions
{
if (item != null)
{
sb.Append(item);
sb.Append(Convert.ToString(item, cultureInfo));
sb.Append(',');
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -85,7 +86,7 @@ internal static void AddPropertiesToTelemetry(IDictionary<string, string> 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");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -81,7 +82,7 @@ public void TagObjects_UnMapped()
{
var activityTagsProcessor = new ActivityTagsProcessor();

IEnumerable<KeyValuePair<string, object?>> tagObjects = new Dictionary<string, object?> { ["somekey"] = "value" }; ;
IEnumerable<KeyValuePair<string, object?>> tagObjects = new Dictionary<string, object?> { ["somekey"] = "value" };
using var activity = CreateTestActivity(tagObjects);
activityTagsProcessor.CategorizeTags(activity);

Expand Down Expand Up @@ -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<string, string> properties = new Dictionary<string, string>();

var doubleArray = new double[] { 1.1, 2.2, 3.3 };
var doubleValue = 123.45;

IEnumerable<KeyValuePair<string, object?>> tagObjects = new Dictionary<string, object?>
{
["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<KeyValuePair<string, object?>>? additionalAttributes = null, ActivityKind activityKind = ActivityKind.Server)
{
var startTimestamp = DateTime.UtcNow;
Expand Down