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 f0b93c82b4075a..8e0f944e09cf6f 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs @@ -210,6 +210,8 @@ public readonly struct ActivityEvent public System.Diagnostics.ActivityTraceFlags TraceFlags { get { throw null; } } public string? TraceState { get { throw null; } } public bool IsRemote { get { throw null; } } + public static bool TryParse(string traceParent, string? traceState, out System.Diagnostics.ActivityContext context) { throw null; } + public static System.Diagnostics.ActivityContext Parse(string traceParent, string? traceState) { throw null; } public static bool operator ==(System.Diagnostics.ActivityContext left, System.Diagnostics.ActivityContext right) { throw null; } public static bool operator !=(System.Diagnostics.ActivityContext left, System.Diagnostics.ActivityContext right) { throw null; } public bool Equals(System.Diagnostics.ActivityContext value) { throw null; } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx b/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx index 52df21c79f1eea..76242301beb3db 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx @@ -156,4 +156,7 @@ "The collection already contains item with same key '{0}''" + + "Invalid trace parent." + \ No newline at end of file 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 d842a0c7dabe3b..b5cd641c3eea42 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs @@ -839,24 +839,29 @@ private static bool IsW3CId(string id) #if ALLOW_PARTIALLY_TRUSTED_CALLERS [System.Security.SecuritySafeCriticalAttribute] #endif - internal static bool TryConvertIdToContext(string id, out ActivityContext context) + internal static bool TryConvertIdToContext(string traceParent, string? traceState, out ActivityContext context) { context = default; - if (!IsW3CId(id)) + if (!IsW3CId(traceParent)) { return false; } - ReadOnlySpan traceIdSpan = id.AsSpan(3, 32); - ReadOnlySpan spanIdSpan = id.AsSpan(36, 16); + ReadOnlySpan traceIdSpan = traceParent.AsSpan(3, 32); + ReadOnlySpan spanIdSpan = traceParent.AsSpan(36, 16); if (!ActivityTraceId.IsLowerCaseHexAndNotAllZeros(traceIdSpan) || !ActivityTraceId.IsLowerCaseHexAndNotAllZeros(spanIdSpan) || - !HexConverter.IsHexLowerChar(id[53]) || !HexConverter.IsHexLowerChar(id[54])) + !HexConverter.IsHexLowerChar(traceParent[53]) || !HexConverter.IsHexLowerChar(traceParent[54])) { return false; } - context = new ActivityContext(new ActivityTraceId(traceIdSpan.ToString()), new ActivitySpanId(spanIdSpan.ToString()), (ActivityTraceFlags) ActivityTraceId.HexByteFromChars(id[53], id[54])); + context = new ActivityContext( + new ActivityTraceId(traceIdSpan.ToString()), + new ActivitySpanId(spanIdSpan.ToString()), + (ActivityTraceFlags) ActivityTraceId.HexByteFromChars(traceParent[53], traceParent[54]), + traceState); + return true; } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.cs index 454eba3ab0c60c..d94640ad23fa07 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.cs @@ -59,6 +59,40 @@ public ActivityContext(ActivityTraceId traceId, ActivitySpanId spanId, ActivityT /// public bool IsRemote { get; } + /// + /// Parse W3C trace context headers to ActivityContext object. + /// + /// W3C trace parent header. + /// W3C trace state. + /// The ActivityContext object created from the parsing operation. + public static bool TryParse(string traceParent, string? traceState, out ActivityContext context) + { + if (traceParent == null) + { + throw new ArgumentNullException(nameof(traceParent)); + } + + return Activity.TryConvertIdToContext(traceParent, traceState, out context); + } + + /// + /// Parse W3C trace context headers to ActivityContext object. + /// + /// W3C trace parent header. + /// Trace state. + /// + /// The ActivityContext object created from the parsing operation. + /// + public static ActivityContext Parse(string traceParent, string? traceState) + { + if (!TryParse(traceParent, traceState, out ActivityContext context)) + { + throw new ArgumentException(SR.InvalidTraceParent); + } + + return context; + } + public bool Equals(ActivityContext value) => SpanId.Equals(value.SpanId) && TraceId.Equals(value.TraceId) && TraceFlags == value.TraceFlags && TraceState == value.TraceState; public override bool Equals(object? obj) => (obj is ActivityContext context) ? Equals(context) : false; diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs index a89068f6c11bb2..ac110fd590e524 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs @@ -145,7 +145,7 @@ public bool HasListeners() { if (!canUseContext.HasValue) { - canUseContext = Activity.TryConvertIdToContext(parentId, out ActivityContext ctx); + canUseContext = Activity.TryConvertIdToContext(parentId, traceState: null, out ActivityContext ctx); if (canUseContext.Value) { dataWithContext = new ActivityCreationOptions(data.Source, data.Name, ctx, data.Kind, data.Tags, data.Links); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs index 5b22f1638161a0..99bafcd319d504 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs @@ -382,6 +382,32 @@ public void TestDefaultParentContext() }).Dispose(); } + [Fact] + public void TestActivityContextParsing() + { + const string w3cId = "00-99d43cb30a4cdb4fbeee3a19c29201b0-e82825765f051b47-01"; + Assert.True(ActivityContext.TryParse(w3cId, "k=v", out ActivityContext context)); + Assert.Equal("99d43cb30a4cdb4fbeee3a19c29201b0", context.TraceId.ToHexString()); + Assert.Equal("e82825765f051b47", context.SpanId.ToHexString()); + Assert.Equal(ActivityTraceFlags.Recorded, context.TraceFlags); + Assert.Equal("k=v", context.TraceState); + + context = ActivityContext.Parse(w3cId, "k=v"); + Assert.Equal("99d43cb30a4cdb4fbeee3a19c29201b0", context.TraceId.ToHexString()); + Assert.Equal("e82825765f051b47", context.SpanId.ToHexString()); + Assert.Equal(ActivityTraceFlags.Recorded, context.TraceFlags); + Assert.Equal("k=v", context.TraceState); + + context = ActivityContext.Parse(w3cId, null); + Assert.Null(context.TraceState); + + Assert.Throws(() => ActivityContext.TryParse(null, "k=v", out context)); + Assert.Throws(() => ActivityContext.Parse(null, null)); + Assert.Throws(() => ActivityContext.Parse("BadW3C", null)); + + const string invalidW3CContext = "00-Z9d43cb30a4cdb4fbeee3a19c29201b0-e82825765f051b47-01"; + Assert.False(ActivityContext.TryParse(invalidW3CContext, null, out context)); + } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void TestCreatingActivityUsingDifferentParentIds()