diff --git a/tracer/src/Datadog.Trace/ProcessTags.cs b/tracer/src/Datadog.Trace/ProcessTags.cs index 938233bbc109..9e65d4a37425 100644 --- a/tracer/src/Datadog.Trace/ProcessTags.cs +++ b/tracer/src/Datadog.Trace/ProcessTags.cs @@ -20,52 +20,56 @@ internal static class ProcessTags public const string EntrypointBasedir = "entrypoint.basedir"; public const string EntrypointWorkdir = "entrypoint.workdir"; - public static readonly string SerializedTags = GetSerializedTags(); + // two views on the same data + public static readonly List TagsList = GetTagsList(); + public static readonly string SerializedTags = GetSerializedTagsFromList(TagsList); - /// - /// From the full path of a directory, get the name of the leaf directory. - /// - private static string GetLastPathSegment(string directoryPath) + private static List GetTagsList() { - // Path.GetFileName returns an empty string if the path ends with a '/'. - // We could use Path.TrimEndingDirectorySeparator instead of the trim here, but it's not available on .NET Framework - return Path.GetFileName(directoryPath.TrimEnd('\\').TrimEnd('/')); + // ⚠️ make sure entries are added in alphabetical order of keys + var tags = new List(3); // Update if you add more entries below + tags.AddNormalizedTag(EntrypointBasedir, GetLastPathSegment(AppContext.BaseDirectory)); + tags.AddNormalizedTag(EntrypointName, GetEntryPointName()); + // workdir can be changed by the code, but we consider that capturing the value when this is called is good enough + tags.AddNormalizedTag(EntrypointWorkdir, GetLastPathSegment(Environment.CurrentDirectory)); + + return tags; } - private static string GetSerializedTags() + /// + /// normalizes the tag value (keys are hardcoded so they don't need that) + /// and adds it to the list iff not null or empty + /// + private static void AddNormalizedTag(this List tags, string key, string? value) { - // ⚠️ make sure entries are added in alphabetical order of keys - List> tags = - [ - new(EntrypointBasedir, GetLastPathSegment(AppContext.BaseDirectory)), - new(EntrypointName, GetEntryPointName()), - // workdir can be changed by the code, but we consider that capturing the value when this is called is good enough - new(EntrypointWorkdir, GetLastPathSegment(Environment.CurrentDirectory)) - ]; - - // then normalize values and put all tags in a string - var serializedTags = StringBuilderCache.Acquire(); - foreach (var kvp in tags) + if (string.IsNullOrEmpty(value)) { - if (!string.IsNullOrEmpty(kvp.Value)) - { - serializedTags.Append($"{kvp.Key}:{NormalizeTagValue(kvp.Value!)},"); - } + return; } - serializedTags.Remove(serializedTags.Length - 1, length: 1); // remove last comma - return StringBuilderCache.GetStringAndRelease(serializedTags); + // 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. + var normalizedValue = TraceUtil.NormalizeTag(value).Replace(oldChar: ':', newChar: '_'); + tags.Add($"{key}:{normalizedValue}"); } - private static string? GetEntryPointName() + private static string GetSerializedTagsFromList(List tags) { - return EntryAssemblyLocator.GetEntryAssembly()?.EntryPoint?.DeclaringType?.FullName; + return string.Join(",", tags); } - private static string NormalizeTagValue(string tagValue) + /// + /// From the full path of a directory, get the name of the leaf directory. + /// + private static string GetLastPathSegment(string directoryPath) { - // 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: '_'); + // Path.GetFileName returns an empty string if the path ends with a '/'. + // We could use Path.TrimEndingDirectorySeparator instead of the trim here, but it's not available on .NET Framework + return Path.GetFileName(directoryPath.TrimEnd('\\').TrimEnd('/')); + } + + private static string? GetEntryPointName() + { + return EntryAssemblyLocator.GetEntryAssembly()?.EntryPoint?.DeclaringType?.FullName; } } diff --git a/tracer/src/Datadog.Trace/RemoteConfigurationManagement/Protocol/RcmClientTracer.cs b/tracer/src/Datadog.Trace/RemoteConfigurationManagement/Protocol/RcmClientTracer.cs index 0d0343723362..30b77081a667 100644 --- a/tracer/src/Datadog.Trace/RemoteConfigurationManagement/Protocol/RcmClientTracer.cs +++ b/tracer/src/Datadog.Trace/RemoteConfigurationManagement/Protocol/RcmClientTracer.cs @@ -12,12 +12,13 @@ namespace Datadog.Trace.RemoteConfigurationManagement.Protocol { internal class RcmClientTracer { - public RcmClientTracer(string runtimeId, string tracerVersion, string service, string env, string? appVersion, List tags) + public RcmClientTracer(string runtimeId, string tracerVersion, string service, string env, string? appVersion, List tags, List? processTags) { RuntimeId = runtimeId; Language = TracerConstants.Language; TracerVersion = tracerVersion; Service = service; + ProcessTags = processTags; Env = env; AppVersion = appVersion; Tags = tags; @@ -35,6 +36,9 @@ public RcmClientTracer(string runtimeId, string tracerVersion, string service, s [JsonProperty("service")] public string Service { get; } + [JsonProperty("process_tags")] + public List? ProcessTags { get; } + [JsonProperty("extra_services")] public string[]? ExtraServices { get; set; } diff --git a/tracer/src/Datadog.Trace/RemoteConfigurationManagement/RemoteConfigurationManager.cs b/tracer/src/Datadog.Trace/RemoteConfigurationManagement/RemoteConfigurationManager.cs index 610784c9d09a..626a0b162f70 100644 --- a/tracer/src/Datadog.Trace/RemoteConfigurationManagement/RemoteConfigurationManager.cs +++ b/tracer/src/Datadog.Trace/RemoteConfigurationManagement/RemoteConfigurationManager.cs @@ -69,7 +69,7 @@ public static RemoteConfigurationManager Create( return new RemoteConfigurationManager( discoveryService, remoteConfigurationApi, - rcmTracer: new RcmClientTracer(settings.RuntimeId, settings.TracerVersion, serviceName, TraceUtil.NormalizeTag(tracerSettings.Environment), tracerSettings.ServiceVersion, tags), + new RcmClientTracer(settings.RuntimeId, settings.TracerVersion, serviceName, TraceUtil.NormalizeTag(tracerSettings.Environment), tracerSettings.ServiceVersion, tags, tracerSettings.PropagateProcessTags ? ProcessTags.TagsList : null), pollInterval: settings.PollInterval, gitMetadataTagsProvider, subscriptionManager); diff --git a/tracer/test/Datadog.Trace.Tests/RemoteConfigurationManagement/RemoteConfigurationApiTests.cs b/tracer/test/Datadog.Trace.Tests/RemoteConfigurationManagement/RemoteConfigurationApiTests.cs index 7578e1ea504b..93e5e5de2b5a 100644 --- a/tracer/test/Datadog.Trace.Tests/RemoteConfigurationManagement/RemoteConfigurationApiTests.cs +++ b/tracer/test/Datadog.Trace.Tests/RemoteConfigurationManagement/RemoteConfigurationApiTests.cs @@ -243,7 +243,8 @@ private static GetRcmRequest GetRequest(string backendClientStage = null) service: nameof(RemoteConfigurationApiTests), env: "RCM Test", appVersion: "1.0.0", - tags: []); + tags: [], + processTags: ["a.b:c", "x.y:z"]); var state = new RcmClientState( rootVersion: 1,