From d9d5df2a3dcb1aaa1d63183f97704f5810391ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vandon?= Date: Mon, 24 Nov 2025 15:30:46 +0100 Subject: [PATCH 1/4] process tags : extra-careful normalization --- tracer/src/Datadog.Trace/ProcessTags.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tracer/src/Datadog.Trace/ProcessTags.cs b/tracer/src/Datadog.Trace/ProcessTags.cs index 938233bbc109..2716479b1a96 100644 --- a/tracer/src/Datadog.Trace/ProcessTags.cs +++ b/tracer/src/Datadog.Trace/ProcessTags.cs @@ -64,8 +64,16 @@ private static string GetSerializedTags() private static string NormalizeTagValue(string tagValue) { - // TraceUtil.NormalizeTag does almost exactly what we want, except it allows ':', - // which we don't want because we use it as a key/value separator. - return TraceUtil.NormalizeTag(tagValue).Replace(oldChar: ':', newChar: '_'); + // TraceUtil.NormalizeTag does almost exactly what we want, except it allows ':', which we don't want because we use it as a key/value separator. + // We need to replace ':' before calling NormalizeTag because there is a logic to remove duplicate underscores. + var normalized = TraceUtil.NormalizeTag(tagValue.Replace(oldChar: ':', newChar: '_')); + + // truncate to 100 char, which the max allowed for a service name, and this is the only usage for those tags + if (normalized.Length > 100) + { + return normalized.Substring(startIndex: 0, length: 100); + } + + return normalized; } } From a5aff3770414feab7cc4eba3cb82d6a7c1065241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vandon?= Date: Mon, 1 Dec 2025 11:22:08 +0100 Subject: [PATCH 2/4] make method visible for tests Co-authored-by: Andrew Lock --- tracer/src/Datadog.Trace/ProcessTags.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tracer/src/Datadog.Trace/ProcessTags.cs b/tracer/src/Datadog.Trace/ProcessTags.cs index 2716479b1a96..25c50849ddd2 100644 --- a/tracer/src/Datadog.Trace/ProcessTags.cs +++ b/tracer/src/Datadog.Trace/ProcessTags.cs @@ -62,7 +62,8 @@ private static string GetSerializedTags() return EntryAssemblyLocator.GetEntryAssembly()?.EntryPoint?.DeclaringType?.FullName; } - private static string NormalizeTagValue(string tagValue) + [TestingAndPrivateOnly] + internal static string NormalizeTagValue(string tagValue) { // TraceUtil.NormalizeTag does almost exactly what we want, except it allows ':', which we don't want because we use it as a key/value separator. // We need to replace ':' before calling NormalizeTag because there is a logic to remove duplicate underscores. From 57438c26c1662999d2c77cc2a1533a14b0c2aa30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vandon?= Date: Mon, 1 Dec 2025 12:37:18 +0100 Subject: [PATCH 3/4] add tests on normalization --- tracer/src/Datadog.Trace/ProcessTags.cs | 1 + .../Datadog.Trace.Tests/ProcessTagsTests.cs | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/tracer/src/Datadog.Trace/ProcessTags.cs b/tracer/src/Datadog.Trace/ProcessTags.cs index 25c50849ddd2..b8dc2b2496c1 100644 --- a/tracer/src/Datadog.Trace/ProcessTags.cs +++ b/tracer/src/Datadog.Trace/ProcessTags.cs @@ -10,6 +10,7 @@ using System.IO; using Datadog.Trace.Configuration; using Datadog.Trace.Processors; +using Datadog.Trace.SourceGenerators; using Datadog.Trace.Util; namespace Datadog.Trace; diff --git a/tracer/test/Datadog.Trace.Tests/ProcessTagsTests.cs b/tracer/test/Datadog.Trace.Tests/ProcessTagsTests.cs index 669825df92e1..1af15ece3160 100644 --- a/tracer/test/Datadog.Trace.Tests/ProcessTagsTests.cs +++ b/tracer/test/Datadog.Trace.Tests/ProcessTagsTests.cs @@ -30,4 +30,61 @@ public void TagsPresentWhenEnabled() }); // cannot really assert on content because it depends on how the tests are run. } + + [Theory] + [InlineData("#test_starting_hash", "test_starting_hash")] + [InlineData("TestCAPSandSuch", "testcapsandsuch")] + [InlineData("Test Conversion Of Weird !@#$%^&**() Characters", "test_conversion_of_weird_characters")] + [InlineData("$#weird_starting", "weird_starting")] + [InlineData("disallowed:c0l0ns", "disallowed_c0l0ns")] + [InlineData("1love", "love")] + [InlineData("123456", "")] + [InlineData("7.0", "")] // this is not ideal + [InlineData("ünicöde", "ünicöde")] + [InlineData("ünicöde:metäl", "ünicöde_metäl")] + [InlineData("Data🐨dog🐶 繋がっ⛰てて", "data_dog_繋がっ_てて")] + [InlineData(" spaces ", "spaces")] + [InlineData(" #hashtag!@#spaces #__<># ", "hashtag_spaces")] + [InlineData(":testing", "testing")] + [InlineData("_foo", "foo")] + [InlineData(":::test", "test")] + [InlineData("contiguous_____underscores", "contiguous_underscores")] + [InlineData("foo_", "foo")] + [InlineData("", "")] + [InlineData(" ", "")] + [InlineData("ok", "ok")] + [InlineData("AlsO:ök", "also_ök")] + [InlineData(":still_ok", "still_ok")] + [InlineData("___trim", "trim")] + [InlineData("fun:ky__tag/1", "fun_ky_tag/1")] + [InlineData("fun:ky@tag/2", "fun_ky_tag/2")] + [InlineData("fun:ky@@@tag/3", "fun_ky_tag/3")] + [InlineData("tag:1/2.3", "tag_1/2.3")] + [InlineData("---fun:k####y_ta@#g/1_@@#", "fun_k_y_ta_g/1")] + [InlineData("AlsO:œ#@ö))œk", "also_œ_ö_œk")] + [InlineData("test\x99\x008faaa", "test_aaa")] + [InlineData("test\x99\x8f", "test")] + [InlineData(" regulartag ", "regulartag")] + [InlineData("\u017Fodd_\u017Fcase\u017F", "\u017Fodd_\u017Fcase\u017F")] + [InlineData("™Ö™Ö™™Ö™", "ö_ö_ö")] + [InlineData("a�", "a")] + [InlineData("a��", "a")] + [InlineData("a��b", "a_b")] + public void TestNormalization(string tagValue, string expectedValue) + { + ProcessTags.NormalizeTagValue(tagValue).Should().Be(expectedValue); + } + + [Fact] + public void TestNormalizationsTruncation() + { + // cannot write those as `Theory` because the parameters need to be constant values + var tagValue = new string(c: 'a', count: 888); + var expected = new string(c: 'a', count: 100); + ProcessTags.NormalizeTagValue(tagValue).Should().Be(expected); + + tagValue = "a" + new string(c: '➰', count: 799); + expected = "a"; + ProcessTags.NormalizeTagValue(tagValue).Should().Be(expected); + } } From 656be3b8cf5a1130cca0a6012461d6ace3b28aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vandon?= Date: Mon, 1 Dec 2025 14:58:03 +0100 Subject: [PATCH 4/4] fix --- tracer/src/Datadog.Trace/ProcessTags.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tracer/src/Datadog.Trace/ProcessTags.cs b/tracer/src/Datadog.Trace/ProcessTags.cs index c9d812f6c312..ea4ad8ef6922 100644 --- a/tracer/src/Datadog.Trace/ProcessTags.cs +++ b/tracer/src/Datadog.Trace/ProcessTags.cs @@ -11,7 +11,6 @@ using Datadog.Trace.Configuration; using Datadog.Trace.Processors; using Datadog.Trace.SourceGenerators; -using Datadog.Trace.Util; namespace Datadog.Trace; @@ -43,13 +42,14 @@ private static List GetTagsList() /// private static void AddNormalizedTag(this List tags, string key, string? value) { - if (string.IsNullOrEmpty(value)) + if (StringUtil.IsNullOrWhiteSpace(value)) { return; } var normalizedValue = NormalizeTagValue(value); - if (normalizedValue.Length > 0) // normalization can squish the string to nothing + // check length because normalization can squish the string to nothing + if (normalizedValue.Length > 0) { tags.Add($"{key}:{normalizedValue}"); }