From dc79c61edfa080b9cfe7fdd05a268b44f6a87bd1 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Wed, 25 Mar 2026 11:19:56 -0400 Subject: [PATCH 01/15] feat: add DD-Session-ID and DD-Root-Session-ID telemetry headers Implements the Stable Service Instance Identifier RFC for .NET telemetry. - DD-Session-ID (= runtime_id) added to every telemetry request - DD-Root-Session-ID added only when the process inherited _DD_ROOT_DOTNET_SESSION_ID from a parent (child process scenario) - Root session ID auto-propagates to child processes via Environment.SetEnvironmentVariable() - _DD_ROOT_DOTNET_SESSION_ID registered in supported-configurations.yaml as ConfigurationKeys.Telemetry.RootSessionId Co-Authored-By: Claude Sonnet 4.6 --- .../supported-configurations.yaml | 9 ++++++++ .../ConfigurationKeys.Telemetry.g.cs | 6 +++++ .../ConfigurationKeys.Telemetry.g.cs | 6 +++++ .../ConfigurationKeys.Telemetry.g.cs | 6 +++++ .../ConfigurationKeys.Telemetry.g.cs | 6 +++++ .../Telemetry/TelemetryConstants.cs | 3 +++ .../Transports/JsonTelemetryTransport.cs | 9 ++++++++ tracer/src/Datadog.Trace/Util/RuntimeId.cs | 22 +++++++++++++++++-- .../TelemetryTransportTests.cs | 13 +++++++++++ .../Telemetry/ConfigurationTests.cs | 1 + .../Transports/JsonTelemetryTransportTests.cs | 4 +++- .../test/Datadog.Trace.Tests/TracerTests.cs | 15 +++++++++++++ 12 files changed, 97 insertions(+), 3 deletions(-) diff --git a/tracer/src/Datadog.Trace/Configuration/supported-configurations.yaml b/tracer/src/Datadog.Trace/Configuration/supported-configurations.yaml index 2e28bea6e6c2..e5a3d55d54bb 100644 --- a/tracer/src/Datadog.Trace/Configuration/supported-configurations.yaml +++ b/tracer/src/Datadog.Trace/Configuration/supported-configurations.yaml @@ -1571,6 +1571,15 @@ supportedConfigurations: documentation: |- Configuration key for whether telemetry metrics should be sent. + _DD_ROOT_DOTNET_SESSION_ID: + - implementation: A + type: string + default: null + product: Telemetry + const_name: RootSessionId + documentation: |- + Internal env var for propagating the root session ID to child processes. + Set automatically by the tracer at init time; not user-configurable. DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES: - implementation: A type: int diff --git a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs index b62c847ae1be..2a3de1cdbd1b 100644 --- a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs @@ -17,6 +17,12 @@ internal static partial class ConfigurationKeys { internal static class Telemetry { + /// + /// Internal env var for propagating the root session ID to child processes. + /// Set automatically by the tracer at init time; not user-configurable. + /// + public const string RootSessionId = "_DD_ROOT_DOTNET_SESSION_ID"; + /// /// SSI variable that provides a unique identifier for the instrumentation installation. /// Used for tracking and correlation purposes in telemetry. diff --git a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs index b62c847ae1be..2a3de1cdbd1b 100644 --- a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs @@ -17,6 +17,12 @@ internal static partial class ConfigurationKeys { internal static class Telemetry { + /// + /// Internal env var for propagating the root session ID to child processes. + /// Set automatically by the tracer at init time; not user-configurable. + /// + public const string RootSessionId = "_DD_ROOT_DOTNET_SESSION_ID"; + /// /// SSI variable that provides a unique identifier for the instrumentation installation. /// Used for tracking and correlation purposes in telemetry. diff --git a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs index b62c847ae1be..2a3de1cdbd1b 100644 --- a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs @@ -17,6 +17,12 @@ internal static partial class ConfigurationKeys { internal static class Telemetry { + /// + /// Internal env var for propagating the root session ID to child processes. + /// Set automatically by the tracer at init time; not user-configurable. + /// + public const string RootSessionId = "_DD_ROOT_DOTNET_SESSION_ID"; + /// /// SSI variable that provides a unique identifier for the instrumentation installation. /// Used for tracking and correlation purposes in telemetry. diff --git a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs index b62c847ae1be..2a3de1cdbd1b 100644 --- a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/ConfigurationKeysGenerator/ConfigurationKeys.Telemetry.g.cs @@ -17,6 +17,12 @@ internal static partial class ConfigurationKeys { internal static class Telemetry { + /// + /// Internal env var for propagating the root session ID to child processes. + /// Set automatically by the tracer at init time; not user-configurable. + /// + public const string RootSessionId = "_DD_ROOT_DOTNET_SESSION_ID"; + /// /// SSI variable that provides a unique identifier for the instrumentation installation. /// Used for tracking and correlation purposes in telemetry. diff --git a/tracer/src/Datadog.Trace/Telemetry/TelemetryConstants.cs b/tracer/src/Datadog.Trace/Telemetry/TelemetryConstants.cs index 1dceed6c98b9..4ac0c7d26120 100644 --- a/tracer/src/Datadog.Trace/Telemetry/TelemetryConstants.cs +++ b/tracer/src/Datadog.Trace/Telemetry/TelemetryConstants.cs @@ -21,6 +21,9 @@ internal static class TelemetryConstants public const string ClientLibraryLanguageHeader = "DD-Client-Library-Language"; public const string ClientLibraryVersionHeader = "DD-Client-Library-Version"; + public const string SessionIdHeader = "DD-Session-ID"; + public const string RootSessionIdHeader = "DD-Root-Session-ID"; + public const string CloudProviderHeader = "DD-Cloud-Provider"; public const string CloudResourceTypeHeader = "DD-Cloud-Resource-Type"; public const string CloudResourceIdentifierHeader = "DD-Cloud-Resource-Identifier"; diff --git a/tracer/src/Datadog.Trace/Telemetry/Transports/JsonTelemetryTransport.cs b/tracer/src/Datadog.Trace/Telemetry/Transports/JsonTelemetryTransport.cs index 75b48f90ec2b..6347e00d3345 100644 --- a/tracer/src/Datadog.Trace/Telemetry/Transports/JsonTelemetryTransport.cs +++ b/tracer/src/Datadog.Trace/Telemetry/Transports/JsonTelemetryTransport.cs @@ -13,6 +13,7 @@ using Datadog.Trace.Logging; using Datadog.Trace.PlatformHelpers; using Datadog.Trace.Telemetry.Metrics; +using Datadog.Trace.Util; using Datadog.Trace.Util.Http; using Datadog.Trace.Vendors.Newtonsoft.Json; using Datadog.Trace.Vendors.Newtonsoft.Json.Serialization; @@ -56,6 +57,14 @@ public async Task PushTelemetry(TelemetryData data) request.AddHeader(TelemetryConstants.DebugHeader, "true"); } + var sessionId = RuntimeId.Get(); + request.AddHeader(TelemetryConstants.SessionIdHeader, sessionId); + var rootSessionId = RuntimeId.GetRootSessionId(); + if (rootSessionId != sessionId) + { + request.AddHeader(TelemetryConstants.RootSessionIdHeader, rootSessionId); + } + request.AddContainerMetadataHeaders(_containerMetadata); TelemetryFactory.Metrics.RecordCountTelemetryApiRequests(endpointMetricTag); diff --git a/tracer/src/Datadog.Trace/Util/RuntimeId.cs b/tracer/src/Datadog.Trace/Util/RuntimeId.cs index bd941132adc6..a5451b52a8ad 100644 --- a/tracer/src/Datadog.Trace/Util/RuntimeId.cs +++ b/tracer/src/Datadog.Trace/Util/RuntimeId.cs @@ -5,6 +5,7 @@ using System; using System.Threading; +using Datadog.Trace.Configuration; using Datadog.Trace.Logging; namespace Datadog.Trace.Util @@ -13,10 +14,13 @@ internal static class RuntimeId { private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(RuntimeId)); private static string _runtimeId; + private static string _rootSessionId; - public static string Get() => LazyInitializer.EnsureInitialized(ref _runtimeId, () => GetImpl()); + public static string Get() => LazyInitializer.EnsureInitialized(ref _runtimeId, () => GetRuntimeIdImpl()); - private static string GetImpl() + public static string GetRootSessionId() => LazyInitializer.EnsureInitialized(ref _rootSessionId, () => GetRootSessionIdImpl()); + + private static string GetRuntimeIdImpl() { if (NativeLoader.TryGetRuntimeIdFromNative(out var runtimeId)) { @@ -29,5 +33,19 @@ private static string GetImpl() return guid; } + + private static string GetRootSessionIdImpl() + { + var inherited = EnvironmentHelpers.GetEnvironmentVariable(ConfigurationKeys.Telemetry.RootSessionId); + if (!string.IsNullOrEmpty(inherited)) + { + Log.Debug("Inherited root session ID from parent: {RootSessionId}", inherited); + return inherited; + } + + var rootId = Get(); + EnvironmentHelpers.SetEnvironmentVariable(ConfigurationKeys.Telemetry.RootSessionId, rootId); + return rootId; + } } } diff --git a/tracer/test/Datadog.Trace.IntegrationTests/TelemetryTransportTests.cs b/tracer/test/Datadog.Trace.IntegrationTests/TelemetryTransportTests.cs index f0c449eaf693..5287eac5fb45 100644 --- a/tracer/test/Datadog.Trace.IntegrationTests/TelemetryTransportTests.cs +++ b/tracer/test/Datadog.Trace.IntegrationTests/TelemetryTransportTests.cs @@ -14,6 +14,7 @@ using Datadog.Trace.Telemetry; using Datadog.Trace.Telemetry.Transports; using Datadog.Trace.TestHelpers; +using Datadog.Trace.Util; using FluentAssertions; using FluentAssertions.Execution; using Xunit; @@ -116,6 +117,12 @@ public async Task SetsRequiredHeaders(bool agentless, bool useCloudAgentless) { "DD-Client-Library-Version", TracerConstants.AssemblyVersion }, }; + // DD-Session-ID is always present and equals the runtime ID + allExpected.Add(TelemetryConstants.SessionIdHeader, RuntimeId.Get()); + + // DD-Root-Session-ID is absent when rootSessionId == runtimeId (normal process) + // We can't assert absence in the loop below, so we check it separately after + if (ContainerMetadata.Instance.ContainerId is { } containerId) { allExpected.Add(AgentHttpHeaderNames.ContainerId, containerId); @@ -149,6 +156,12 @@ public async Task SetsRequiredHeaders(bool agentless, bool useCloudAgentless) } } + // DD-Root-Session-ID should be absent in a normal (non-child) process + if (RuntimeId.GetRootSessionId() == RuntimeId.Get()) + { + headers.AllKeys.Should().NotContain(TelemetryConstants.RootSessionIdHeader); + } + // should have either content-length or chunked encoding headers.AllKeys.Should() .Contain(s => s.Equals("Content-Type", StringComparison.OrdinalIgnoreCase) diff --git a/tracer/test/Datadog.Trace.Tests/Telemetry/ConfigurationTests.cs b/tracer/test/Datadog.Trace.Tests/Telemetry/ConfigurationTests.cs index f9d1c994b6e2..77df9cee720f 100644 --- a/tracer/test/Datadog.Trace.Tests/Telemetry/ConfigurationTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Telemetry/ConfigurationTests.cs @@ -52,6 +52,7 @@ public class ConfigurationTests "DD_CIVISIBILITY_CODE_COVERAGE_MODE", "DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER", // Internal env vars that we only ever read from environment + "_DD_ROOT_DOTNET_SESSION_ID", "DD_INTERNAL_TRACE_NATIVE_ENGINE_PATH", "DD_INTERNAL_PROFILING_NATIVE_ENGINE_PATH", "DD_DOTNET_TRACER_HOME", diff --git a/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs b/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs index 4b7ce405aa63..5b524e95be34 100644 --- a/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs @@ -11,6 +11,7 @@ using Datadog.Trace.Agent.Transports; using Datadog.Trace.PlatformHelpers; using Datadog.Trace.Telemetry; +using Datadog.Trace.Util; using Datadog.Trace.Telemetry.Transports; using Datadog.Trace.TestHelpers.FluentAssertionsExtensions.Json; using Datadog.Trace.TestHelpers.TransportHelpers; @@ -74,7 +75,8 @@ public async Task ShouldContainRequiredHeaders(bool debugEnabled, [Combinatorial { "DD-Telemetry-API-Version", TelemetryConstants.ApiVersionV2 }, { "DD-Telemetry-Request-Type", "my-request-type" }, { "Datadog-Container-ID", "my-container-id" }, - { "Datadog-Entity-ID", "my-entity-id" } + { "Datadog-Entity-ID", "my-entity-id" }, + { TelemetryConstants.SessionIdHeader, RuntimeId.Get() }, }; if (debugEnabled) { diff --git a/tracer/test/Datadog.Trace.Tests/TracerTests.cs b/tracer/test/Datadog.Trace.Tests/TracerTests.cs index f247dd81289c..bc3d64bb7e96 100644 --- a/tracer/test/Datadog.Trace.Tests/TracerTests.cs +++ b/tracer/test/Datadog.Trace.Tests/TracerTests.cs @@ -561,6 +561,21 @@ public void RuntimeId() Guid.TryParse(runtimeId, out _).Should().BeTrue(); } + [Fact] + public void RootSessionId_DefaultsToRuntimeId() + { + var rootSessionId = Datadog.Trace.Util.RuntimeId.GetRootSessionId(); + rootSessionId.Should().Be(Datadog.Trace.Util.RuntimeId.Get()); + } + + [Fact] + public void RootSessionId_SetsEnvVar() + { + var rootSessionId = Datadog.Trace.Util.RuntimeId.GetRootSessionId(); + Environment.GetEnvironmentVariable(ConfigurationKeys.Telemetry.RootSessionId) + .Should().Be(rootSessionId); + } + [Fact] public async Task ForceFlush() { From d47e79789866dbbd025e9edbdbd2e241c2dc86a8 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Wed, 25 Mar 2026 12:39:05 -0400 Subject: [PATCH 02/15] fix: sort using directives alphabetically (SA1210) Co-Authored-By: Claude Sonnet 4.6 --- .../Telemetry/Transports/JsonTelemetryTransportTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs b/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs index 5b524e95be34..2ebac87b2469 100644 --- a/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs @@ -11,8 +11,8 @@ using Datadog.Trace.Agent.Transports; using Datadog.Trace.PlatformHelpers; using Datadog.Trace.Telemetry; -using Datadog.Trace.Util; using Datadog.Trace.Telemetry.Transports; +using Datadog.Trace.Util; using Datadog.Trace.TestHelpers.FluentAssertionsExtensions.Json; using Datadog.Trace.TestHelpers.TransportHelpers; using Datadog.Trace.Vendors.Newtonsoft.Json; From 2804636736c5f96562b9c013e04cec32f311d5b6 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Wed, 25 Mar 2026 13:41:27 -0400 Subject: [PATCH 03/15] fix: correct using directive order (SA1210) - TestHelpers before Util Co-Authored-By: Claude Sonnet 4.6 --- .../Telemetry/Transports/JsonTelemetryTransportTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs b/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs index 2ebac87b2469..bacdd6ffb96c 100644 --- a/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs @@ -12,9 +12,9 @@ using Datadog.Trace.PlatformHelpers; using Datadog.Trace.Telemetry; using Datadog.Trace.Telemetry.Transports; -using Datadog.Trace.Util; using Datadog.Trace.TestHelpers.FluentAssertionsExtensions.Json; using Datadog.Trace.TestHelpers.TransportHelpers; +using Datadog.Trace.Util; using Datadog.Trace.Vendors.Newtonsoft.Json; using Datadog.Trace.Vendors.Newtonsoft.Json.Linq; using FluentAssertions; From 642b76b3701858e2cc57722d1d3d477eab824532 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Thu, 26 Mar 2026 14:47:03 -0400 Subject: [PATCH 04/15] Initialize root session ID at tracer startup Eagerly set _DD_ROOT_DOTNET_SESSION_ID in TracerManager constructor so child processes spawned before the first telemetry flush inherit it. Co-Authored-By: Claude Opus 4.6 (1M context) --- tracer/src/Datadog.Trace/TracerManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tracer/src/Datadog.Trace/TracerManager.cs b/tracer/src/Datadog.Trace/TracerManager.cs index 7965ce96ee7d..57839178cb0d 100644 --- a/tracer/src/Datadog.Trace/TracerManager.cs +++ b/tracer/src/Datadog.Trace/TracerManager.cs @@ -113,6 +113,11 @@ public TracerManager( ServiceRemappingHash = serviceRemappingHash; SpanContextPropagator = SpanContextPropagatorFactory.GetSpanContextPropagator(settings.PropagationStyleInject, settings.PropagationStyleExtract, settings.PropagationExtractFirstOnly, settings.PropagationBehaviorExtract); + + // Eagerly initialize the root session ID so child processes inherit it + // even if spawned before the first telemetry flush. + _ = RuntimeId.GetRootSessionId(); + UpdatePerTraceSettings(settings.Manager.InitialMutableSettings); _settingSubscription = settings.Manager.SubscribeToChanges(changes => { From d7ec96323944d02175b198f3f9b16aea264547c0 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Fri, 27 Mar 2026 11:56:21 -0400 Subject: [PATCH 05/15] Address PR review comments - Rename private GetRuntimeIdImpl() back to GetImpl() to preserve naming convention - Move session ID headers from per-request in JsonTelemetryTransport to TelemetryHttpHeaderNames default headers (constant for process lifetime) - Move tests from TracerTests.cs to Util/RuntimeIdTests.cs to match source layout - Move eager root session ID init from TracerManager to Instrumentation.InitializeNoNativeParts Co-Authored-By: Claude Opus 4.6 (1M context) --- .../ClrProfiler/Instrumentation.cs | 4 + .../Telemetry/TelemetryHttpHeaderNames.cs | 78 ++++++++++++++----- .../Transports/JsonTelemetryTransport.cs | 9 --- tracer/src/Datadog.Trace/TracerManager.cs | 4 - tracer/src/Datadog.Trace/Util/RuntimeId.cs | 4 +- .../Transports/JsonTelemetryTransportTests.cs | 2 - .../test/Datadog.Trace.Tests/TracerTests.cs | 15 ---- .../Util/RuntimeIdTests.cs | 31 ++++++++ 8 files changed, 94 insertions(+), 53 deletions(-) create mode 100644 tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs diff --git a/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs b/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs index 7357442df9f4..4871ac3ddd97 100644 --- a/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs +++ b/tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs @@ -306,6 +306,10 @@ internal static void InitializeNoNativeParts(ref RefStopwatch sw) } #endif + // Eagerly initialize the root session ID so child processes + // inherit it even if spawned before the first telemetry flush. + _ = RuntimeId.GetRootSessionId(); + try { // ensure global instance is created if it's not already diff --git a/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs b/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs index ed54b9e6143a..bab03878d739 100644 --- a/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs +++ b/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs @@ -1,56 +1,92 @@ -// +// // Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // using System.Collections.Generic; using Datadog.Trace.HttpOverStreams; +using Datadog.Trace.Util; namespace Datadog.Trace.Telemetry { internal static class TelemetryHttpHeaderNames { /// - /// Returns , in the format Key: Value\r\n. For use in HTTP headers + /// Gets the default agent headers in the format Key: Value\r\n. For use in HTTP headers. + /// Not a const because session headers depend on runtime values. /// - internal const string HttpSerializedDefaultAgentHeaders = - $"{TelemetryConstants.ClientLibraryLanguageHeader}: {TracerConstants.Language}" + DatadogHttpValues.CrLf + - $"{TelemetryConstants.ClientLibraryVersionHeader}: {TracerConstants.AssemblyVersion}" + DatadogHttpValues.CrLf + - $"{HttpHeaderNames.TracingEnabled}: false" + DatadogHttpValues.CrLf; + internal static string HttpSerializedDefaultAgentHeaders { get; } = BuildSerializedAgentHeaders(); /// /// Gets the default constant headers that should be added to any request to the agent /// internal static KeyValuePair[] GetDefaultAgentHeaders() - => - [ + { + var headers = new List> + { new(TelemetryConstants.ClientLibraryLanguageHeader, TracerConstants.Language), new(TelemetryConstants.ClientLibraryVersionHeader, TracerConstants.AssemblyVersion), - new(HttpHeaderNames.TracingEnabled, "false") // don't add automatic instrumentation to requests directed to the agent - ]; + new(HttpHeaderNames.TracingEnabled, "false"), // don't add automatic instrumentation to requests directed to the agent + new(TelemetryConstants.SessionIdHeader, RuntimeId.Get()), + }; + + AddRootSessionIdHeader(headers); + + return headers.ToArray(); + } /// /// Gets the default constant headers that should be added to any request to the direct telemetry intake /// internal static KeyValuePair[] GetDefaultIntakeHeaders(TelemetrySettings.AgentlessSettings settings) { - var headerCount = settings.Cloud is null ? 4 : 7; + var headers = new List> + { + new(TelemetryConstants.ClientLibraryLanguageHeader, TracerConstants.Language), + new(TelemetryConstants.ClientLibraryVersionHeader, TracerConstants.AssemblyVersion), + new(HttpHeaderNames.TracingEnabled, "false"), + new(TelemetryConstants.ApiKeyHeader, settings.ApiKey), + new(TelemetryConstants.SessionIdHeader, RuntimeId.Get()), + }; - var headers = new KeyValuePair[headerCount]; + if (settings.Cloud is { } cloud) + { + headers.Add(new(TelemetryConstants.CloudProviderHeader, cloud.Provider)); + headers.Add(new(TelemetryConstants.CloudResourceTypeHeader, cloud.ResourceType)); + headers.Add(new(TelemetryConstants.CloudResourceIdentifierHeader, cloud.ResourceIdentifier)); + } - headers[0] = new(TelemetryConstants.ClientLibraryLanguageHeader, TracerConstants.Language); - headers[1] = new(TelemetryConstants.ClientLibraryVersionHeader, TracerConstants.AssemblyVersion); - headers[2] = new(HttpHeaderNames.TracingEnabled, "false"); // don't add automatic instrumentation to requests directed to the agent - headers[3] = new(TelemetryConstants.ApiKeyHeader, settings.ApiKey); + AddRootSessionIdHeader(headers); - if (settings.Cloud is { } cloud) + return headers.ToArray(); + } + + private static void AddRootSessionIdHeader(List> headers) + { + var sessionId = RuntimeId.Get(); + var rootSessionId = RuntimeId.GetRootSessionId(); + if (rootSessionId != sessionId) + { + headers.Add(new(TelemetryConstants.RootSessionIdHeader, rootSessionId)); + } + } + + private static string BuildSerializedAgentHeaders() + { + var serialized = + $"{TelemetryConstants.ClientLibraryLanguageHeader}: {TracerConstants.Language}" + DatadogHttpValues.CrLf + + $"{TelemetryConstants.ClientLibraryVersionHeader}: {TracerConstants.AssemblyVersion}" + DatadogHttpValues.CrLf + + $"{HttpHeaderNames.TracingEnabled}: false" + DatadogHttpValues.CrLf + + $"{TelemetryConstants.SessionIdHeader}: {RuntimeId.Get()}" + DatadogHttpValues.CrLf; + + var sessionId = RuntimeId.Get(); + var rootSessionId = RuntimeId.GetRootSessionId(); + if (rootSessionId != sessionId) { - headers[4] = new(TelemetryConstants.CloudProviderHeader, cloud.Provider); - headers[5] = new(TelemetryConstants.CloudResourceTypeHeader, cloud.ResourceType); - headers[6] = new(TelemetryConstants.CloudResourceIdentifierHeader, cloud.ResourceIdentifier); + serialized += $"{TelemetryConstants.RootSessionIdHeader}: {rootSessionId}" + DatadogHttpValues.CrLf; } - return headers; + return serialized; } } } diff --git a/tracer/src/Datadog.Trace/Telemetry/Transports/JsonTelemetryTransport.cs b/tracer/src/Datadog.Trace/Telemetry/Transports/JsonTelemetryTransport.cs index 6347e00d3345..75b48f90ec2b 100644 --- a/tracer/src/Datadog.Trace/Telemetry/Transports/JsonTelemetryTransport.cs +++ b/tracer/src/Datadog.Trace/Telemetry/Transports/JsonTelemetryTransport.cs @@ -13,7 +13,6 @@ using Datadog.Trace.Logging; using Datadog.Trace.PlatformHelpers; using Datadog.Trace.Telemetry.Metrics; -using Datadog.Trace.Util; using Datadog.Trace.Util.Http; using Datadog.Trace.Vendors.Newtonsoft.Json; using Datadog.Trace.Vendors.Newtonsoft.Json.Serialization; @@ -57,14 +56,6 @@ public async Task PushTelemetry(TelemetryData data) request.AddHeader(TelemetryConstants.DebugHeader, "true"); } - var sessionId = RuntimeId.Get(); - request.AddHeader(TelemetryConstants.SessionIdHeader, sessionId); - var rootSessionId = RuntimeId.GetRootSessionId(); - if (rootSessionId != sessionId) - { - request.AddHeader(TelemetryConstants.RootSessionIdHeader, rootSessionId); - } - request.AddContainerMetadataHeaders(_containerMetadata); TelemetryFactory.Metrics.RecordCountTelemetryApiRequests(endpointMetricTag); diff --git a/tracer/src/Datadog.Trace/TracerManager.cs b/tracer/src/Datadog.Trace/TracerManager.cs index 57839178cb0d..17616e3d5eec 100644 --- a/tracer/src/Datadog.Trace/TracerManager.cs +++ b/tracer/src/Datadog.Trace/TracerManager.cs @@ -114,10 +114,6 @@ public TracerManager( SpanContextPropagator = SpanContextPropagatorFactory.GetSpanContextPropagator(settings.PropagationStyleInject, settings.PropagationStyleExtract, settings.PropagationExtractFirstOnly, settings.PropagationBehaviorExtract); - // Eagerly initialize the root session ID so child processes inherit it - // even if spawned before the first telemetry flush. - _ = RuntimeId.GetRootSessionId(); - UpdatePerTraceSettings(settings.Manager.InitialMutableSettings); _settingSubscription = settings.Manager.SubscribeToChanges(changes => { diff --git a/tracer/src/Datadog.Trace/Util/RuntimeId.cs b/tracer/src/Datadog.Trace/Util/RuntimeId.cs index a5451b52a8ad..bc6b7c7b1ae9 100644 --- a/tracer/src/Datadog.Trace/Util/RuntimeId.cs +++ b/tracer/src/Datadog.Trace/Util/RuntimeId.cs @@ -16,11 +16,11 @@ internal static class RuntimeId private static string _runtimeId; private static string _rootSessionId; - public static string Get() => LazyInitializer.EnsureInitialized(ref _runtimeId, () => GetRuntimeIdImpl()); + public static string Get() => LazyInitializer.EnsureInitialized(ref _runtimeId, () => GetImpl()); public static string GetRootSessionId() => LazyInitializer.EnsureInitialized(ref _rootSessionId, () => GetRootSessionIdImpl()); - private static string GetRuntimeIdImpl() + private static string GetImpl() { if (NativeLoader.TryGetRuntimeIdFromNative(out var runtimeId)) { diff --git a/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs b/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs index bacdd6ffb96c..5813c1d32aa3 100644 --- a/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Telemetry/Transports/JsonTelemetryTransportTests.cs @@ -14,7 +14,6 @@ using Datadog.Trace.Telemetry.Transports; using Datadog.Trace.TestHelpers.FluentAssertionsExtensions.Json; using Datadog.Trace.TestHelpers.TransportHelpers; -using Datadog.Trace.Util; using Datadog.Trace.Vendors.Newtonsoft.Json; using Datadog.Trace.Vendors.Newtonsoft.Json.Linq; using FluentAssertions; @@ -76,7 +75,6 @@ public async Task ShouldContainRequiredHeaders(bool debugEnabled, [Combinatorial { "DD-Telemetry-Request-Type", "my-request-type" }, { "Datadog-Container-ID", "my-container-id" }, { "Datadog-Entity-ID", "my-entity-id" }, - { TelemetryConstants.SessionIdHeader, RuntimeId.Get() }, }; if (debugEnabled) { diff --git a/tracer/test/Datadog.Trace.Tests/TracerTests.cs b/tracer/test/Datadog.Trace.Tests/TracerTests.cs index bc3d64bb7e96..f247dd81289c 100644 --- a/tracer/test/Datadog.Trace.Tests/TracerTests.cs +++ b/tracer/test/Datadog.Trace.Tests/TracerTests.cs @@ -561,21 +561,6 @@ public void RuntimeId() Guid.TryParse(runtimeId, out _).Should().BeTrue(); } - [Fact] - public void RootSessionId_DefaultsToRuntimeId() - { - var rootSessionId = Datadog.Trace.Util.RuntimeId.GetRootSessionId(); - rootSessionId.Should().Be(Datadog.Trace.Util.RuntimeId.Get()); - } - - [Fact] - public void RootSessionId_SetsEnvVar() - { - var rootSessionId = Datadog.Trace.Util.RuntimeId.GetRootSessionId(); - Environment.GetEnvironmentVariable(ConfigurationKeys.Telemetry.RootSessionId) - .Should().Be(rootSessionId); - } - [Fact] public async Task ForceFlush() { diff --git a/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs b/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs new file mode 100644 index 000000000000..11ebaa0b4c2b --- /dev/null +++ b/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs @@ -0,0 +1,31 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +using System; +using Datadog.Trace.Configuration; +using Datadog.Trace.Util; +using FluentAssertions; +using Xunit; + +namespace Datadog.Trace.Tests.Util +{ + public class RuntimeIdTests + { + [Fact] + public void RootSessionId_DefaultsToRuntimeId() + { + var rootSessionId = RuntimeId.GetRootSessionId(); + rootSessionId.Should().Be(RuntimeId.Get()); + } + + [Fact] + public void RootSessionId_SetsEnvVar() + { + var rootSessionId = RuntimeId.GetRootSessionId(); + Environment.GetEnvironmentVariable(ConfigurationKeys.Telemetry.RootSessionId) + .Should().Be(rootSessionId); + } + } +} From 6385c4744ec3f81f5053e2246f3d502ba56259c8 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Mon, 30 Mar 2026 11:44:40 -0400 Subject: [PATCH 06/15] Optimize TelemetryHttpHeaderNames per review feedback - Use pre-sized arrays instead of List + ToArray() - Lazy-initialize HttpSerializedDefaultAgentHeaders - Use StringBuilderCache instead of string concatenation - Consolidate duplicate RuntimeId.Get() calls Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Telemetry/TelemetryHttpHeaderNames.cs | 92 ++++++++++--------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs b/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs index bab03878d739..3212a38ba448 100644 --- a/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs +++ b/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs @@ -3,7 +3,9 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +using System; using System.Collections.Generic; +using System.Threading; using Datadog.Trace.HttpOverStreams; using Datadog.Trace.Util; @@ -11,28 +13,37 @@ namespace Datadog.Trace.Telemetry { internal static class TelemetryHttpHeaderNames { + private static string _httpSerializedDefaultAgentHeaders; + /// /// Gets the default agent headers in the format Key: Value\r\n. For use in HTTP headers. - /// Not a const because session headers depend on runtime values. + /// Lazily initialized because session headers depend on runtime values. /// - internal static string HttpSerializedDefaultAgentHeaders { get; } = BuildSerializedAgentHeaders(); + internal static string HttpSerializedDefaultAgentHeaders => + LazyInitializer.EnsureInitialized(ref _httpSerializedDefaultAgentHeaders, BuildSerializedAgentHeaders); /// /// Gets the default constant headers that should be added to any request to the agent /// internal static KeyValuePair[] GetDefaultAgentHeaders() { - var headers = new List> - { - new(TelemetryConstants.ClientLibraryLanguageHeader, TracerConstants.Language), - new(TelemetryConstants.ClientLibraryVersionHeader, TracerConstants.AssemblyVersion), - new(HttpHeaderNames.TracingEnabled, "false"), // don't add automatic instrumentation to requests directed to the agent - new(TelemetryConstants.SessionIdHeader, RuntimeId.Get()), - }; + var sessionId = RuntimeId.Get(); + var rootSessionId = RuntimeId.GetRootSessionId(); + var includeRoot = rootSessionId != sessionId; + var headerCount = includeRoot ? 5 : 4; - AddRootSessionIdHeader(headers); + var headers = new KeyValuePair[headerCount]; + headers[0] = new(TelemetryConstants.ClientLibraryLanguageHeader, TracerConstants.Language); + headers[1] = new(TelemetryConstants.ClientLibraryVersionHeader, TracerConstants.AssemblyVersion); + headers[2] = new(HttpHeaderNames.TracingEnabled, "false"); // don't add automatic instrumentation to requests directed to the agent + headers[3] = new(TelemetryConstants.SessionIdHeader, sessionId); - return headers.ToArray(); + if (includeRoot) + { + headers[4] = new(TelemetryConstants.RootSessionIdHeader, rootSessionId); + } + + return headers; } /// @@ -40,53 +51,52 @@ internal static KeyValuePair[] GetDefaultAgentHeaders() /// internal static KeyValuePair[] GetDefaultIntakeHeaders(TelemetrySettings.AgentlessSettings settings) { - var headers = new List> - { - new(TelemetryConstants.ClientLibraryLanguageHeader, TracerConstants.Language), - new(TelemetryConstants.ClientLibraryVersionHeader, TracerConstants.AssemblyVersion), - new(HttpHeaderNames.TracingEnabled, "false"), - new(TelemetryConstants.ApiKeyHeader, settings.ApiKey), - new(TelemetryConstants.SessionIdHeader, RuntimeId.Get()), - }; + var sessionId = RuntimeId.Get(); + var rootSessionId = RuntimeId.GetRootSessionId(); + var includeRoot = rootSessionId != sessionId; + var baseCount = settings.Cloud is null ? 5 : 8; + var headerCount = includeRoot ? baseCount + 1 : baseCount; + + var headers = new KeyValuePair[headerCount]; + headers[0] = new(TelemetryConstants.ClientLibraryLanguageHeader, TracerConstants.Language); + headers[1] = new(TelemetryConstants.ClientLibraryVersionHeader, TracerConstants.AssemblyVersion); + headers[2] = new(HttpHeaderNames.TracingEnabled, "false"); + headers[3] = new(TelemetryConstants.ApiKeyHeader, settings.ApiKey); + headers[4] = new(TelemetryConstants.SessionIdHeader, sessionId); + var index = 5; if (settings.Cloud is { } cloud) { - headers.Add(new(TelemetryConstants.CloudProviderHeader, cloud.Provider)); - headers.Add(new(TelemetryConstants.CloudResourceTypeHeader, cloud.ResourceType)); - headers.Add(new(TelemetryConstants.CloudResourceIdentifierHeader, cloud.ResourceIdentifier)); + headers[index++] = new(TelemetryConstants.CloudProviderHeader, cloud.Provider); + headers[index++] = new(TelemetryConstants.CloudResourceTypeHeader, cloud.ResourceType); + headers[index++] = new(TelemetryConstants.CloudResourceIdentifierHeader, cloud.ResourceIdentifier); } - AddRootSessionIdHeader(headers); - - return headers.ToArray(); - } - - private static void AddRootSessionIdHeader(List> headers) - { - var sessionId = RuntimeId.Get(); - var rootSessionId = RuntimeId.GetRootSessionId(); - if (rootSessionId != sessionId) + if (includeRoot) { - headers.Add(new(TelemetryConstants.RootSessionIdHeader, rootSessionId)); + headers[index] = new(TelemetryConstants.RootSessionIdHeader, rootSessionId); } + + return headers; } private static string BuildSerializedAgentHeaders() { - var serialized = - $"{TelemetryConstants.ClientLibraryLanguageHeader}: {TracerConstants.Language}" + DatadogHttpValues.CrLf + - $"{TelemetryConstants.ClientLibraryVersionHeader}: {TracerConstants.AssemblyVersion}" + DatadogHttpValues.CrLf + - $"{HttpHeaderNames.TracingEnabled}: false" + DatadogHttpValues.CrLf + - $"{TelemetryConstants.SessionIdHeader}: {RuntimeId.Get()}" + DatadogHttpValues.CrLf; - var sessionId = RuntimeId.Get(); var rootSessionId = RuntimeId.GetRootSessionId(); + + var sb = StringBuilderCache.Acquire(); + sb.Append(TelemetryConstants.ClientLibraryLanguageHeader).Append(": ").Append(TracerConstants.Language).Append(DatadogHttpValues.CrLf); + sb.Append(TelemetryConstants.ClientLibraryVersionHeader).Append(": ").Append(TracerConstants.AssemblyVersion).Append(DatadogHttpValues.CrLf); + sb.Append(HttpHeaderNames.TracingEnabled).Append(": false").Append(DatadogHttpValues.CrLf); + sb.Append(TelemetryConstants.SessionIdHeader).Append(": ").Append(sessionId).Append(DatadogHttpValues.CrLf); + if (rootSessionId != sessionId) { - serialized += $"{TelemetryConstants.RootSessionIdHeader}: {rootSessionId}" + DatadogHttpValues.CrLf; + sb.Append(TelemetryConstants.RootSessionIdHeader).Append(": ").Append(rootSessionId).Append(DatadogHttpValues.CrLf); } - return serialized; + return StringBuilderCache.GetStringAndRelease(sb); } } } From 3406b39ec405196c502c173f7e1f2dea6728e511 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Mon, 30 Mar 2026 21:07:29 -0400 Subject: [PATCH 07/15] Add test for inherited root session ID - Add RuntimeId.ResetForTests() to allow resetting cached state - Add RootSessionId_InheritsFromEnvVar test Co-Authored-By: Claude Opus 4.6 (1M context) --- tracer/src/Datadog.Trace/Util/RuntimeId.cs | 2 ++ .../Util/RuntimeIdTests.cs | 34 +++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/tracer/src/Datadog.Trace/Util/RuntimeId.cs b/tracer/src/Datadog.Trace/Util/RuntimeId.cs index bc6b7c7b1ae9..78535e73fcab 100644 --- a/tracer/src/Datadog.Trace/Util/RuntimeId.cs +++ b/tracer/src/Datadog.Trace/Util/RuntimeId.cs @@ -20,6 +20,8 @@ internal static class RuntimeId public static string GetRootSessionId() => LazyInitializer.EnsureInitialized(ref _rootSessionId, () => GetRootSessionIdImpl()); + internal static void ResetForTests() => _rootSessionId = null; + private static string GetImpl() { if (NativeLoader.TryGetRuntimeIdFromNative(out var runtimeId)) diff --git a/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs b/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs index 11ebaa0b4c2b..6aca1cb72ad1 100644 --- a/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs @@ -14,18 +14,32 @@ namespace Datadog.Trace.Tests.Util public class RuntimeIdTests { [Fact] - public void RootSessionId_DefaultsToRuntimeId() + public void RootSessionId_UsesRuntimeIdWhenNotInherited_AndInheritsWhenSet() { - var rootSessionId = RuntimeId.GetRootSessionId(); - rootSessionId.Should().Be(RuntimeId.Get()); - } + try + { + // When no env var is set, root session ID should default to runtime ID + RuntimeId.ResetForTests(); + Environment.SetEnvironmentVariable(ConfigurationKeys.Telemetry.RootSessionId, null); - [Fact] - public void RootSessionId_SetsEnvVar() - { - var rootSessionId = RuntimeId.GetRootSessionId(); - Environment.GetEnvironmentVariable(ConfigurationKeys.Telemetry.RootSessionId) - .Should().Be(rootSessionId); + var rootSessionId = RuntimeId.GetRootSessionId(); + rootSessionId.Should().Be(RuntimeId.Get()); + + // When env var is pre-set (simulating a child process), root session ID + // should return the inherited value instead of the current runtime ID + var inherited = "inherited-root-session-id"; + RuntimeId.ResetForTests(); + Environment.SetEnvironmentVariable(ConfigurationKeys.Telemetry.RootSessionId, inherited); + + RuntimeId.GetRootSessionId().Should().Be(inherited); + RuntimeId.GetRootSessionId().Should().NotBe(RuntimeId.Get()); + } + finally + { + RuntimeId.ResetForTests(); + Environment.SetEnvironmentVariable(ConfigurationKeys.Telemetry.RootSessionId, null); + RuntimeId.GetRootSessionId(); + } } } } From 8f980886c1a98aa043cd3a3fb4bef6498590b48c Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Tue, 31 Mar 2026 09:19:02 -0400 Subject: [PATCH 08/15] Update tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs Co-authored-by: Andrew Lock --- .../Telemetry/TelemetryHttpHeaderNames.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs b/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs index 3212a38ba448..bab5fd8c073c 100644 --- a/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs +++ b/tracer/src/Datadog.Trace/Telemetry/TelemetryHttpHeaderNames.cs @@ -85,18 +85,19 @@ private static string BuildSerializedAgentHeaders() var sessionId = RuntimeId.Get(); var rootSessionId = RuntimeId.GetRootSessionId(); - var sb = StringBuilderCache.Acquire(); - sb.Append(TelemetryConstants.ClientLibraryLanguageHeader).Append(": ").Append(TracerConstants.Language).Append(DatadogHttpValues.CrLf); - sb.Append(TelemetryConstants.ClientLibraryVersionHeader).Append(": ").Append(TracerConstants.AssemblyVersion).Append(DatadogHttpValues.CrLf); - sb.Append(HttpHeaderNames.TracingEnabled).Append(": false").Append(DatadogHttpValues.CrLf); - sb.Append(TelemetryConstants.SessionIdHeader).Append(": ").Append(sessionId).Append(DatadogHttpValues.CrLf); - if (rootSessionId != sessionId) { - sb.Append(TelemetryConstants.RootSessionIdHeader).Append(": ").Append(rootSessionId).Append(DatadogHttpValues.CrLf); + return $"{TelemetryConstants.ClientLibraryLanguageHeader}: {TracerConstants.Language}" + DatadogHttpValues.CrLf + + $"{TelemetryConstants.ClientLibraryVersionHeader}: {TracerConstants.AssemblyVersion}" + DatadogHttpValues.CrLf + + $"{HttpHeaderNames.TracingEnabled}: false" + DatadogHttpValues.CrLf + + $"{TelemetryConstants.SessionIdHeader}: {sessionId}" + DatadogHttpValues.CrLf + + $"{TelemetryConstants.RootSessionIdHeader}: {rootSessionId}" + DatadogHttpValues.CrLf; } - return StringBuilderCache.GetStringAndRelease(sb); + return $"{TelemetryConstants.ClientLibraryLanguageHeader}: {TracerConstants.Language}" + DatadogHttpValues.CrLf + + $"{TelemetryConstants.ClientLibraryVersionHeader}: {TracerConstants.AssemblyVersion}" + DatadogHttpValues.CrLf + + $"{HttpHeaderNames.TracingEnabled}: false" + DatadogHttpValues.CrLf + + $"{TelemetryConstants.SessionIdHeader}: {sessionId}" + DatadogHttpValues.CrLf; } } } From 77826914055347c94e0f7b7b0aa023478e61f404 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Tue, 31 Mar 2026 09:19:46 -0400 Subject: [PATCH 09/15] Update tracer/src/Datadog.Trace/Util/RuntimeId.cs Co-authored-by: Andrew Lock --- tracer/src/Datadog.Trace/Util/RuntimeId.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tracer/src/Datadog.Trace/Util/RuntimeId.cs b/tracer/src/Datadog.Trace/Util/RuntimeId.cs index 78535e73fcab..828745925196 100644 --- a/tracer/src/Datadog.Trace/Util/RuntimeId.cs +++ b/tracer/src/Datadog.Trace/Util/RuntimeId.cs @@ -20,6 +20,7 @@ internal static class RuntimeId public static string GetRootSessionId() => LazyInitializer.EnsureInitialized(ref _rootSessionId, () => GetRootSessionIdImpl()); + [InternalForTesting] internal static void ResetForTests() => _rootSessionId = null; private static string GetImpl() From 38678c1e170f05b287d548314b522e6bd70fc225 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Tue, 31 Mar 2026 09:20:04 -0400 Subject: [PATCH 10/15] Update tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs Co-authored-by: Andrew Lock --- tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs b/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs index 6aca1cb72ad1..2bc1463865d4 100644 --- a/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs @@ -11,6 +11,7 @@ namespace Datadog.Trace.Tests.Util { + [EnvironmentRestorer(ConfigurationKeys.Telemetry.RootSessionId)] public class RuntimeIdTests { [Fact] From 56e6aea107135fdae8b5692fabce9ec4b5505335 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Mon, 30 Mar 2026 22:17:26 -0400 Subject: [PATCH 11/15] Revert stray blank line in TracerManager.cs Co-Authored-By: Claude Opus 4.6 (1M context) --- tracer/src/Datadog.Trace/TracerManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tracer/src/Datadog.Trace/TracerManager.cs b/tracer/src/Datadog.Trace/TracerManager.cs index 5cb89d65b4cd..d78a6cd72e0b 100644 --- a/tracer/src/Datadog.Trace/TracerManager.cs +++ b/tracer/src/Datadog.Trace/TracerManager.cs @@ -113,7 +113,6 @@ public TracerManager( ServiceRemappingHash = serviceRemappingHash; SpanContextPropagator = SpanContextPropagatorFactory.GetSpanContextPropagator(settings.PropagationStyleInject, settings.PropagationStyleExtract, settings.PropagationExtractFirstOnly, settings.PropagationBehaviorExtract); - UpdatePerTraceSettings(settings.Manager.InitialMutableSettings); _settingSubscription = settings.Manager.SubscribeToChanges(changes => { From 98cca069151a828d5d3c6431a809727b88518b03 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Tue, 31 Mar 2026 09:33:28 -0400 Subject: [PATCH 12/15] Use EnvironmentConfigurationSource to read root session ID Co-Authored-By: Claude Opus 4.6 (1M context) --- tracer/src/Datadog.Trace/Util/RuntimeId.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tracer/src/Datadog.Trace/Util/RuntimeId.cs b/tracer/src/Datadog.Trace/Util/RuntimeId.cs index 828745925196..b36f650415cb 100644 --- a/tracer/src/Datadog.Trace/Util/RuntimeId.cs +++ b/tracer/src/Datadog.Trace/Util/RuntimeId.cs @@ -6,7 +6,9 @@ using System; using System.Threading; using Datadog.Trace.Configuration; +using Datadog.Trace.Configuration.Telemetry; using Datadog.Trace.Logging; +using Datadog.Trace.Telemetry; namespace Datadog.Trace.Util { @@ -39,7 +41,8 @@ private static string GetImpl() private static string GetRootSessionIdImpl() { - var inherited = EnvironmentHelpers.GetEnvironmentVariable(ConfigurationKeys.Telemetry.RootSessionId); + var config = new ConfigurationBuilder(new EnvironmentConfigurationSource(), TelemetryFactory.Config); + var inherited = config.WithKeys(ConfigurationKeys.Telemetry.RootSessionId).AsString(); if (!string.IsNullOrEmpty(inherited)) { Log.Debug("Inherited root session ID from parent: {RootSessionId}", inherited); From 604f9baf466f47953c14fee5f5dd7d02a2d54989 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Tue, 31 Mar 2026 10:50:16 -0400 Subject: [PATCH 13/15] fix: use TestingAndPrivateOnly attribute instead of InternalForTesting Co-Authored-By: Claude Opus 4.6 (1M context) --- tracer/src/Datadog.Trace/Util/RuntimeId.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracer/src/Datadog.Trace/Util/RuntimeId.cs b/tracer/src/Datadog.Trace/Util/RuntimeId.cs index b36f650415cb..9eac21f84d5c 100644 --- a/tracer/src/Datadog.Trace/Util/RuntimeId.cs +++ b/tracer/src/Datadog.Trace/Util/RuntimeId.cs @@ -22,7 +22,7 @@ internal static class RuntimeId public static string GetRootSessionId() => LazyInitializer.EnsureInitialized(ref _rootSessionId, () => GetRootSessionIdImpl()); - [InternalForTesting] + [TestingAndPrivateOnly] internal static void ResetForTests() => _rootSessionId = null; private static string GetImpl() From 4f3274a314297c9885af21ef1325f8fa12e99d90 Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Tue, 31 Mar 2026 11:04:53 -0400 Subject: [PATCH 14/15] fix: add missing using for TestingAndPrivateOnlyAttribute Co-Authored-By: Claude Opus 4.6 (1M context) --- tracer/src/Datadog.Trace/Util/RuntimeId.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tracer/src/Datadog.Trace/Util/RuntimeId.cs b/tracer/src/Datadog.Trace/Util/RuntimeId.cs index 9eac21f84d5c..29855cda0c05 100644 --- a/tracer/src/Datadog.Trace/Util/RuntimeId.cs +++ b/tracer/src/Datadog.Trace/Util/RuntimeId.cs @@ -8,6 +8,7 @@ using Datadog.Trace.Configuration; using Datadog.Trace.Configuration.Telemetry; using Datadog.Trace.Logging; +using Datadog.Trace.SourceGenerators; using Datadog.Trace.Telemetry; namespace Datadog.Trace.Util From fbb50f61bf3af25b0c5075e4abfddb1b3bca42bc Mon Sep 17 00:00:00 2001 From: Ayan Khan Date: Tue, 31 Mar 2026 12:31:40 -0400 Subject: [PATCH 15/15] fix: add missing using for EnvironmentRestorerAttribute Co-Authored-By: Claude Opus 4.6 (1M context) --- tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs b/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs index 2bc1463865d4..f104f6f4bb18 100644 --- a/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Util/RuntimeIdTests.cs @@ -5,6 +5,7 @@ using System; using Datadog.Trace.Configuration; +using Datadog.Trace.TestHelpers; using Datadog.Trace.Util; using FluentAssertions; using Xunit;