Add ActivityContext parsing APIs (#40183)
authorTarek Mahmoud Sayed <tarekms@microsoft.com>
Mon, 3 Aug 2020 21:03:42 +0000 (14:03 -0700)
committerGitHub <noreply@github.com>
Mon, 3 Aug 2020 21:03:42 +0000 (14:03 -0700)
* Add ActivityContext parsing APIs

* Address the feedback

src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/Resources/Strings.resx
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivityContext.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs
src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs

index f0b93c8..8e0f944 100644 (file)
@@ -210,6 +210,8 @@ namespace System.Diagnostics
         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; }
index 52df21c..7624230 100644 (file)
   <data name="KeyAlreadyExist" xml:space="preserve">
     <value>"The collection already contains item with same key '{0}''"</value>
   </data>
+  <data name="InvalidTraceParent" xml:space="preserve">
+    <value>"Invalid trace parent."</value>
+  </data>
 </root>
\ No newline at end of file
index d842a0c..b5cd641 100644 (file)
@@ -839,24 +839,29 @@ namespace System.Diagnostics
 #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<char> traceIdSpan = id.AsSpan(3,  32);
-            ReadOnlySpan<char> spanIdSpan  = id.AsSpan(36, 16);
+            ReadOnlySpan<char> traceIdSpan = traceParent.AsSpan(3,  32);
+            ReadOnlySpan<char> 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;
         }
 
index 454eba3..d94640a 100644 (file)
@@ -59,6 +59,40 @@ namespace System.Diagnostics
         /// </remarks>
         public bool IsRemote { get; }
 
+        /// <summary>
+        /// Parse W3C trace context headers to ActivityContext object.
+        /// </summary>
+        /// <param name="traceParent">W3C trace parent header.</param>
+        /// <param name="traceState">W3C trace state.</param>
+        /// <param name="context">The ActivityContext object created from the parsing operation.</param>
+        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);
+        }
+
+        /// <summary>
+        /// Parse W3C trace context headers to ActivityContext object.
+        /// </summary>
+        /// <param name="traceParent">W3C trace parent header.</param>
+        /// <param name="traceState">Trace state.</param>
+        /// <returns>
+        /// The ActivityContext object created from the parsing operation.
+        /// </returns>
+        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;
index a89068f..ac110fd 100644 (file)
@@ -145,7 +145,7 @@ namespace System.Diagnostics
                         {
                             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<ActivityContext>(data.Source, data.Name, ctx, data.Kind, data.Tags, data.Links);
index 5b22f16..99bafcd 100644 (file)
@@ -382,6 +382,32 @@ namespace System.Diagnostics.Tests
             }).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<ArgumentNullException>(() => ActivityContext.TryParse(null, "k=v", out context));
+            Assert.Throws<ArgumentNullException>(() => ActivityContext.Parse(null, null));
+            Assert.Throws<ArgumentException>(() => 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()