From 79fb48886d258e19de9fb129200b0d20a8c0b7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Kie=C5=82kowicz?= Date: Wed, 25 Feb 2026 11:45:33 +0100 Subject: [PATCH] Add ActivityTraceFlags.RandomTraceId flag --- ...em.Diagnostics.DiagnosticSourceActivity.cs | 2 + .../src/System/Diagnostics/Activity.cs | 21 +++- .../tests/ActivityTests.cs | 117 ++++++++++++++++-- 3 files changed, 129 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs index 991f4048a9da81..9fff77545b2d1f 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs @@ -36,6 +36,7 @@ public string? Id public System.Diagnostics.Activity? Parent { get { throw null; } } public string? ParentId { get { throw null; } } public System.Diagnostics.ActivitySpanId ParentSpanId { get { throw null; } } + public bool RandomizedTraceId { get { throw null; } } public bool Recorded { get { throw null; } } public string? RootId { get { throw null; } } public System.Diagnostics.ActivitySpanId SpanId { get { throw null; } } @@ -184,6 +185,7 @@ public enum ActivityTraceFlags { None = 0, Recorded = 1, + RandomTraceId = 2, } public readonly partial struct ActivityTraceId : System.IEquatable { diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs index cd6da7a53f818d..c98b563dca2571 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs @@ -939,6 +939,11 @@ public ActivityTraceId TraceId } } + /// + /// True if the W3CIdFlags.RandomTraceId flag is set. + /// + public bool RandomizedTraceId { get => (ActivityTraceFlags & ActivityTraceFlags.RandomTraceId) != 0; } + /// /// True if the W3CIdFlags.Recorded flag is set. /// @@ -1289,7 +1294,20 @@ private void GenerateW3CId() if (!TrySetTraceIdFromParent()) { Func? traceIdGenerator = TraceIdGenerator; - ActivityTraceId id = traceIdGenerator == null ? ActivityTraceId.CreateRandom() : traceIdGenerator(); + ActivityTraceId id; + + if (traceIdGenerator == null) + { + id = ActivityTraceId.CreateRandom(); + // Set RandomTraceId flag when using the default random generator + ActivityTraceFlags |= ActivityTraceFlags.RandomTraceId; + } + else + { + // Using custom generator + id = traceIdGenerator(); + } + _traceId = id.ToHexString(); } } @@ -1872,6 +1890,7 @@ public enum ActivityTraceFlags { None = 0b_0_0000000, Recorded = 0b_0_0000001, // The Activity (or more likely its parents) has been marked as useful to record + RandomTraceId = 0b_0_0000010, // The Activity has a randomized TraceId } /// diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs index af9540f0203e18..c035f2111bb22a 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs @@ -1151,10 +1151,39 @@ public void ActivityTraceFlagsTests() Assert.Equal($"00-0123456789abcdef0123456789abcdef-{activity.SpanId.ToHexString()}-01", activity.Id); Assert.Equal(ActivityTraceFlags.Recorded, activity.ActivityTraceFlags); Assert.True(activity.Recorded); + Assert.False(activity.RandomizedTraceId); activity.Stop(); - // Set the 'Recorded' bit by using SetParentId by using the TraceId, SpanId, ActivityTraceFlags overload + // Set the 'RandomTraceId' bit by using SetParentId with a -02 flags. activity = new Activity("activity2"); + activity.SetParentId("00-0123456789abcdef0123456789abcdef-0123456789abcdef-02"); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(IdIsW3CFormat(activity.Id)); + Assert.Equal($"00-0123456789abcdef0123456789abcdef-{activity.SpanId.ToHexString()}-02", activity.Id); + Assert.Equal(ActivityTraceFlags.RandomTraceId, activity.ActivityTraceFlags); + Assert.False(activity.Recorded); + Assert.True(activity.RandomizedTraceId); + activity.Stop(); + + // Set the 'Recorded' and 'RandomTraceId' bits by using SetParentId with a -03 flags. + activity = new Activity("activity3"); + activity.SetParentId("00-0123456789abcdef0123456789abcdef-0123456789abcdef-03"); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); + Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); + Assert.True(IdIsW3CFormat(activity.Id)); + Assert.Equal($"00-0123456789abcdef0123456789abcdef-{activity.SpanId.ToHexString()}-03", activity.Id); + Assert.Equal(ActivityTraceFlags.Recorded | ActivityTraceFlags.RandomTraceId, activity.ActivityTraceFlags); + Assert.True(activity.Recorded); + Assert.True(activity.RandomizedTraceId); + activity.Stop(); + + // Set the 'Recorded' bit by using SetParentId by using the TraceId, SpanId, ActivityTraceFlags overload + activity = new Activity("activity4"); ActivityTraceId activityTraceId = ActivityTraceId.CreateRandom(); activity.SetParentId(activityTraceId, ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded); activity.Start(); @@ -1164,11 +1193,41 @@ public void ActivityTraceFlagsTests() Assert.Equal($"00-{activity.TraceId.ToHexString()}-{activity.SpanId.ToHexString()}-01", activity.Id); Assert.Equal(ActivityTraceFlags.Recorded, activity.ActivityTraceFlags); Assert.True(activity.Recorded); + Assert.False(activity.RandomizedTraceId); + activity.Stop(); + + // Set the 'RandomTraceId' bit by using SetParentId by using the TraceId, SpanId, ActivityTraceFlags overload + activity = new Activity("activity5"); + activityTraceId = ActivityTraceId.CreateRandom(); + activity.SetParentId(activityTraceId, ActivitySpanId.CreateRandom(), ActivityTraceFlags.RandomTraceId); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal(activityTraceId.ToHexString(), activity.TraceId.ToHexString()); + Assert.True(IdIsW3CFormat(activity.Id)); + Assert.Equal($"00-{activity.TraceId.ToHexString()}-{activity.SpanId.ToHexString()}-02", activity.Id); + Assert.Equal(ActivityTraceFlags.RandomTraceId, activity.ActivityTraceFlags); + Assert.False(activity.Recorded); + Assert.True(activity.RandomizedTraceId); + activity.Stop(); + + + // Set the 'Recorded' and 'RandomTraceId' bits by using SetParentId by using the TraceId, SpanId, ActivityTraceFlags overload + activity = new Activity("activity6"); + activityTraceId = ActivityTraceId.CreateRandom(); + activity.SetParentId(activityTraceId, ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded | ActivityTraceFlags.RandomTraceId); + activity.Start(); + Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); + Assert.Equal(activityTraceId.ToHexString(), activity.TraceId.ToHexString()); + Assert.True(IdIsW3CFormat(activity.Id)); + Assert.Equal($"00-{activity.TraceId.ToHexString()}-{activity.SpanId.ToHexString()}-03", activity.Id); + Assert.Equal(ActivityTraceFlags.Recorded | ActivityTraceFlags.RandomTraceId, activity.ActivityTraceFlags); + Assert.True(activity.Recorded); + Assert.True(activity.RandomizedTraceId); activity.Stop(); /****************************************************/ - // Set the 'Recorded' bit explicitly after the fact. - activity = new Activity("activity3"); + // Set the 'Recorded' and 'RandomTraceId' bits explicitly after the fact. + activity = new Activity("activity7"); activity.SetParentId("00-0123456789abcdef0123456789abcdef-0123456789abcdef-00"); activity.Start(); Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); @@ -1178,37 +1237,52 @@ public void ActivityTraceFlagsTests() Assert.Equal($"00-{activity.TraceId.ToHexString()}-{activity.SpanId.ToHexString()}-00", activity.Id); Assert.Equal(ActivityTraceFlags.None, activity.ActivityTraceFlags); Assert.False(activity.Recorded); + Assert.False(activity.RandomizedTraceId); activity.ActivityTraceFlags = ActivityTraceFlags.Recorded; Assert.Equal(ActivityTraceFlags.Recorded, activity.ActivityTraceFlags); Assert.True(activity.Recorded); + Assert.False(activity.RandomizedTraceId); + + activity.ActivityTraceFlags = ActivityTraceFlags.RandomTraceId; + Assert.Equal(ActivityTraceFlags.RandomTraceId, activity.ActivityTraceFlags); + Assert.False(activity.Recorded); + Assert.True(activity.RandomizedTraceId); + + activity.ActivityTraceFlags = ActivityTraceFlags.Recorded | ActivityTraceFlags.RandomTraceId; + Assert.Equal(ActivityTraceFlags.Recorded | ActivityTraceFlags.RandomTraceId, activity.ActivityTraceFlags); + Assert.True(activity.Recorded); + Assert.True(activity.RandomizedTraceId); + activity.Stop(); /****************************************************/ // Confirm that the flags are propagated to children. - activity = new Activity("activity4"); - activity.SetParentId("00-0123456789abcdef0123456789abcdef-0123456789abcdef-01"); + activity = new Activity("activity8"); + activity.SetParentId("00-0123456789abcdef0123456789abcdef-0123456789abcdef-03"); activity.Start(); Assert.Equal(activity, Activity.Current); Assert.Equal(ActivityIdFormat.W3C, activity.IdFormat); Assert.Equal("0123456789abcdef0123456789abcdef", activity.TraceId.ToHexString()); Assert.Equal("0123456789abcdef", activity.ParentSpanId.ToHexString()); Assert.True(IdIsW3CFormat(activity.Id)); - Assert.Equal($"00-{activity.TraceId.ToHexString()}-{activity.SpanId.ToHexString()}-01", activity.Id); - Assert.Equal(ActivityTraceFlags.Recorded, activity.ActivityTraceFlags); + Assert.Equal($"00-{activity.TraceId.ToHexString()}-{activity.SpanId.ToHexString()}-03", activity.Id); + Assert.Equal(ActivityTraceFlags.Recorded | ActivityTraceFlags.RandomTraceId, activity.ActivityTraceFlags); Assert.True(activity.Recorded); + Assert.True(activity.RandomizedTraceId); // create a child - var childActivity = new Activity("activity4Child"); + var childActivity = new Activity("activity8Child"); childActivity.Start(); Assert.Equal(childActivity, Activity.Current); Assert.Equal("0123456789abcdef0123456789abcdef", childActivity.TraceId.ToHexString()); Assert.NotEqual(activity.SpanId.ToHexString(), childActivity.SpanId.ToHexString()); Assert.True(IdIsW3CFormat(childActivity.Id)); - Assert.Equal($"00-{childActivity.TraceId.ToHexString()}-{childActivity.SpanId.ToHexString()}-01", childActivity.Id); - Assert.Equal(ActivityTraceFlags.Recorded, childActivity.ActivityTraceFlags); + Assert.Equal($"00-{childActivity.TraceId.ToHexString()}-{childActivity.SpanId.ToHexString()}-03", childActivity.Id); + Assert.Equal(ActivityTraceFlags.Recorded | ActivityTraceFlags.RandomTraceId, childActivity.ActivityTraceFlags); Assert.True(childActivity.Recorded); + Assert.True(childActivity.RandomizedTraceId); childActivity.Stop(); activity.Stop(); @@ -2190,12 +2264,35 @@ public void TraceIdCustomGenerationTest() a.Start(); Assert.Equal(ActivityTraceId.CreateFromBytes(traceIdBytes), a.TraceId); + Assert.False(a.RandomizedTraceId); a.Stop(); } }).Dispose(); } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TraceIdDefaultGenerationSetsRandomFlag() + { + RemoteExecutor.Invoke(() => + { + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + Activity.TraceIdGenerator = null; // Ensure we're using the default generator + + Activity a = new Activity("DefaultRandomTraceId"); + a.Start(); + + // Default random generator should set RandomTraceId flag + Assert.True(a.RandomizedTraceId); + Assert.Equal(ActivityTraceFlags.RandomTraceId, a.ActivityTraceFlags & ActivityTraceFlags.RandomTraceId); + + // Verify TraceId is not all zeros + Assert.NotEqual("00000000000000000000000000000000", a.TraceId.ToHexString()); + + a.Stop(); + }).Dispose(); + } + [Fact] public void EnumerateTagObjectsTest() {