Add Support to ActivitySource events in DiagnosticSourceEventSource (#41641)
authorTarek Mahmoud Sayed <tarekms@microsoft.com>
Tue, 1 Sep 2020 16:38:28 +0000 (09:38 -0700)
committerGitHub <noreply@github.com>
Tue, 1 Sep 2020 16:38:28 +0000 (09:38 -0700)
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs
src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs
src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs

index d1b4d63..8d3f3e9 100644 (file)
@@ -44,6 +44,8 @@ namespace System.Diagnostics
                     }
                 }, this);
             }
+
+            GC.KeepAlive(DiagnosticSourceEventSource.Logger);
         }
 
         /// <summary>
index fdfbd90..b38829b 100644 (file)
@@ -14,25 +14,25 @@ namespace System.Diagnostics
     /// <summary>
     /// DiagnosticSourceEventSource serves two purposes
     ///
-    ///   1) It allows debuggers to inject code via Function evaluation.  This is the purpose of the
-    ///   BreakPointWithDebuggerFuncEval function in the 'OnEventCommand' method.   Basically even in
+    ///   1) It allows debuggers to inject code via Function evaluation. This is the purpose of the
+    ///   BreakPointWithDebuggerFuncEval function in the 'OnEventCommand' method. Basically even in
     ///   release code, debuggers can place a breakpoint in this method and then trigger the
-    ///   DiagnosticSourceEventSource via ETW.  Thus from outside the process you can get a hook that
-    ///   is guaranteed to happen BEFORE any DiangosticSource events (if the process is just starting)
+    ///   DiagnosticSourceEventSource via ETW. Thus from outside the process you can get a hook that
+    ///   is guaranteed to happen BEFORE any DiagnosticSource events (if the process is just starting)
     ///   or as soon as possible afterward if it is on attach.
     ///
     ///   2) It provides a 'bridge' that allows DiagnosticSource messages to be forwarded to EventListers
-    ///   or ETW.    You can do this by enabling the Microsoft-Diagnostics-DiagnosticSource with the
+    ///   or ETW. You can do this by enabling the Microsoft-Diagnostics-DiagnosticSource with the
     ///   'Events' keyword (for diagnostics purposes, you should also turn on the 'Messages' keyword.
     ///
     ///   This EventSource defines a EventSource argument called 'FilterAndPayloadSpecs' that defines
-    ///   what DiagnsoticSources to enable and what parts of the payload to serialize into the key-value
-    ///   list that will be forwarded to the EventSource.    If it is empty, values of properties of the
+    ///   what DiagnosticSources to enable and what parts of the payload to serialize into the key-value
+    ///   list that will be forwarded to the EventSource. If it is empty, values of properties of the
     ///   diagnostic source payload are dumped as strings (using ToString()) and forwarded to the EventSource.
     ///   For what people think of as serializable object strings, primitives this gives you want you want.
     ///   (the value of the property in string form) for what people think of as non-serializable objects
     ///   (e.g. HttpContext) the ToString() method is typically not defined, so you get the Object.ToString()
-    ///   implementation that prints the type name.  This is useful since this is the information you need
+    ///   implementation that prints the type name. This is useful since this is the information you need
     ///   (the type of the property) to discover the field names so you can create a transform specification
     ///   that will pick off the properties you desire.
     ///
@@ -50,10 +50,17 @@ namespace System.Diagnostics
     ///       * EVENT_NAME : TRANSFORM_SPECS
     ///       * EMPTY - turns on all sources with implicit payload elements.
     ///   * an EVENTNAME can be
-    ///       * DIAGNOSTIC_SOURCE_NAME / DIAGNOSTC_EVENT_NAME @ EVENT_SOURCE_EVENTNAME  - give the name as well as the EventSource event to log it under.
-    ///       * DIAGNOSTIC_SOURCE_NAME / DIAGNOSTC_EVENT_NAME
+    ///       * DIAGNOSTIC_SOURCE_NAME / DIAGNOSTIC_EVENT_NAME @ EVENT_SOURCE_EVENTNAME - give the name as well as the EventSource event to log it under.
+    ///       * DIAGNOSTIC_SOURCE_NAME / DIAGNOSTIC_EVENT_NAME
     ///       * DIAGNOSTIC_SOURCE_NAME    - which wildcards every event in the Diagnostic source or
     ///       * EMPTY                     - which turns on all sources
+    ///     Or it can be "[AS] ACTIVITY_SOURCE_NAME + ACTIVITY_NAME / ACTIVITY_EVENT_NAME - SAMPLING_RESULT"
+    ///       * All parts are optional and can be empty string.
+    ///       * ACTIVITY_SOURCE_NAME can be "*" to listen to all ActivitySources
+    ///       * ACTIVITY_SOURCE_NAME can be empty string which will listen to ActivitySource that create Activities using "new Activity(...)"
+    ///       * ACTIVITY_NAME is the activity operation name to filter with.
+    ///       * ACTIVITY_EVENT_NAME either "Start" to listen to Activity Start event, or "Stop" to listen to Activity Stop event, or empty string to listen to both Start and Stop Activity events.
+    ///       * SAMPLING_RESULT either "Propagate" to create the Activity with PropagationData, or "Record" to create the Activity with AllData, or empty string to create the Activity with AllDataAndRecorded
     ///   * TRANSFORM_SPEC is a semicolon separated list of TRANSFORM_SPEC, which can be
     ///       * - TRANSFORM_SPEC               - the '-' indicates that implicit payload elements should be suppressed
     ///       * VARIABLE_NAME = PROPERTY_SPEC  - indicates that a payload element 'VARIABLE_NAME' is created from PROPERTY_SPEC
@@ -71,10 +78,10 @@ namespace System.Diagnostics
     ///    "BridgeTestSource2/TestEvent2:-cls.Url"
     ///
     /// This indicates that two events should be turned on, The 'TestEvent1' event in BridgeTestSource1 and the
-    /// 'TestEvent2' in BridgeTestSource2.   In the first case, because the transform did not begin with a -
-    /// any primitive type/string of 'TestEvent1's payload will be serialized into the output.  In addition if
+    /// 'TestEvent2' in BridgeTestSource2. In the first case, because the transform did not begin with a -
+    /// any primitive type/string of 'TestEvent1's payload will be serialized into the output. In addition if
     /// there a property of the payload object called 'cls' which in turn has a property 'Point' which in turn
-    /// has a property 'X' then that data is also put in the output with the name cls_Point_X.   Similarly
+    /// has a property 'X' then that data is also put in the output with the name cls_Point_X. Similarly
     /// if cls.Point.Y exists, then that value will also be put in the output with the name cls_Point_Y.
     ///
     /// For the 'BridgeTestSource2/TestEvent2' event, because the - was specified NO implicit fields will be
@@ -87,7 +94,7 @@ namespace System.Diagnostics
     ///     "BridgeTestSource1\r\n" +
     ///     "BridgeTestSource2"
     ///
-    /// This will enable all events for the BridgeTestSource1 and BridgeTestSource2 sources.   Any string/primitive
+    /// This will enable all events for the BridgeTestSource1 and BridgeTestSource2 sources. Any string/primitive
     /// properties of any of the events will be serialized into the output.
     ///
     /// Example:
@@ -95,13 +102,21 @@ namespace System.Diagnostics
     ///     ""
     ///
     /// This turns on all DiagnosticSources Any string/primitive properties of any of the events will be serialized
-    /// into the output.   This is not likely to be a good idea as it will be very verbose, but is useful to quickly
+    /// into the output. This is not likely to be a good idea as it will be very verbose, but is useful to quickly
     /// discover what is available.
     ///
+    /// Example:
+    ///     "[AS]*"                      listen to all ActivitySources and all Activities events (Start/Stop). Activities will be created with AllDataAndRecorded sampling.
+    ///     "[AS]"                       listen to default ActivitySource and Activities events (Start/Stop) while the Activity is created using "new Activity(...)". Such Activities will be created with AllDataAndRecorded sampling.
+    ///     "[AS]MyLibrary/Start"        listen to `MyLibrary` ActivitySource and the 'Start' Activity event. The Activities will be created with AllDataAndRecorded sampling.
+    ///     "[AS]MyLibrary/-Propagate"   listen to `MyLibrary` ActivitySource and the 'Start and Stop' Activity events. The Activities will be created with PropagationData sampling.
+    ///     "[AS]MyLibrary/Stop-Record"  listen to `MyLibrary` ActivitySource and the 'Stop' Activity event. The Activities will be created with AllData sampling.
+    ///     "[AS]*/-"                    listen to all ActivitySources and the Start and Stop Activity events. Activities will be created with AllDataAndRecorded sampling. this equivalent to "[AS]*" too.
+    ///     "[AS]*+MyActivity"           listen to all activity sources when creating Activity with the operation name "MyActivity".
     ///
     /// * How data is logged in the EventSource
     ///
-    /// By default all data from DiagnosticSources is logged to the DiagnosticEventSouce event called 'Event'
+    /// By default all data from DiagnosticSources is logged to the DiagnosticEventSource event called 'Event'
     /// which has three fields
     ///
     ///     string SourceName,
@@ -118,7 +133,7 @@ namespace System.Diagnostics
     ///     RecursiveActivity1Stop
     ///
     /// By using the SourceName/EventName@EventSourceName syntax, you can force particular DiagnosticSource events to
-    /// be logged with one of these EventSource events.   This is useful because the events above have start-stop semantics
+    /// be logged with one of these EventSource events. This is useful because the events above have start-stop semantics
     /// which means that they create activity IDs that are attached to all logging messages between the start and
     /// the stop (see https://blogs.msdn.microsoft.com/vancem/2015/09/14/exploring-eventsource-activity-correlation-and-causation-features/)
     ///
@@ -133,13 +148,13 @@ namespace System.Diagnostics
     /// means that all events caused between these two markers will have an activity ID associated with this start event.
     /// Similarly SecurityStart is mapped to Activity2Start.
     ///
-    /// Note you can map many DiangosticSource events to the same EventSource Event (e.g. Activity1Start).  As long as the
+    /// Note you can map many DiagnosticSource events to the same EventSource Event (e.g. Activity1Start). As long as the
     /// activities don't nest, you can reuse the same event name (since the payloads have the DiagnosticSource name which can
-    /// disambiguate).   However if they nest you need to use another EventSource event because the rules of EventSource
+    /// disambiguate). However if they nest you need to use another EventSource event because the rules of EventSource
     /// activities state that a start of the same event terminates any existing activity of the same name.
     ///
     /// As its name suggests RecursiveActivity1Start, is marked as recursive and thus can be used when the activity can nest with
-    /// itself.   This should not be a 'top most' activity because it is not 'self healing' (if you miss a stop, then the
+    /// itself. This should not be a 'top most' activity because it is not 'self healing' (if you miss a stop, then the
     /// activity NEVER ends).
     ///
     /// See the DiagnosticSourceEventSourceBridgeTest.cs for more explicit examples of using this bridge.
@@ -160,10 +175,10 @@ namespace System.Diagnostics
             /// </summary>
             public const EventKeywords Events = (EventKeywords)0x2;
 
-            // Some ETW logic does not support passing arguments to the EventProvider.   To get around
-            // this in common cases, we define some keywords that basically stand in for particular common argumnents
+            // Some ETW logic does not support passing arguments to the EventProvider. To get around
+            // this in common cases, we define some keywords that basically stand in for particular common arguments
             // That way at least the common cases can be used by everyone (and it also compresses things).
-            // We start these keywords at 0x1000.   See below for the values these keywords represent
+            // We start these keywords at 0x1000. See below for the values these keywords represent
             // Because we want all keywords on to still mean 'dump everything by default' we have another keyword
             // IgnoreShorcutKeywords which must be OFF in order for the shortcuts to work thus the all 1s keyword
             // still means what you expect.
@@ -173,7 +188,7 @@ namespace System.Diagnostics
         };
 
         // Setting AspNetCoreHosting is like having this in the FilterAndPayloadSpecs string
-        // It turns on basic hostig events.
+        // It turns on basic hosting events.
         private readonly string AspNetCoreHostingKeywordValue =
             "Microsoft.AspNetCore/Microsoft.AspNetCore.Hosting.BeginRequest@Activity1Start:-" +
                 "httpContext.Request.Method;" +
@@ -290,11 +305,39 @@ namespace System.Diagnostics
             WriteEvent(10, SourceName);
         }
 
+        /// <summary>
+        /// Fires when the Activity start.
+        /// </summary>
+        /// <param name="SourceName">The ActivitySource name</param>
+        /// <param name="ActivityName">The Activity name</param>
+        /// <param name="Arguments">Name and value pairs of the Activity properties</param>
+#if NO_EVENTSOURCE_COMPLEX_TYPE_SUPPORT
+        [Event(11, Keywords = Keywords.Events)]
+#else
+        [Event(11, Keywords = Keywords.Events, ActivityOptions = EventActivityOptions.Recursive)]
+#endif
+        private void ActivityStart(string SourceName, string ActivityName, IEnumerable<KeyValuePair<string, string?>> Arguments) =>
+            WriteEvent(11, SourceName, ActivityName, Arguments);
+
+        /// <summary>
+        /// Fires when the Activity stop.
+        /// </summary>
+        /// <param name="SourceName">The ActivitySource name</param>
+        /// <param name="ActivityName">The Activity name</param>
+        /// <param name="Arguments">Name and value pairs of the Activity properties</param>
+#if NO_EVENTSOURCE_COMPLEX_TYPE_SUPPORT
+        [Event(12, Keywords = Keywords.Events)]
+#else
+        [Event(12, Keywords = Keywords.Events, ActivityOptions = EventActivityOptions.Recursive)]
+#endif
+        private void ActivityStop(string SourceName, string ActivityName, IEnumerable<KeyValuePair<string, string?>> Arguments) =>
+            WriteEvent(12, SourceName, ActivityName, Arguments);
+
         #region private
 
 #if NO_EVENTSOURCE_COMPLEX_TYPE_SUPPORT
         /// <summary>
-        /// Converts a keyvalue bag to JSON.  Only used on V4.5 EventSources.
+        /// Converts a keyvalue bag to JSON. Only used on V4.5 EventSources.
         /// </summary>
         private static string ToJson(IEnumerable<KeyValuePair<string, string>> keyValues)
         {
@@ -373,7 +416,7 @@ namespace System.Diagnostics
                 }
                 else if (command.Command == EventCommand.Update || command.Command == EventCommand.Disable)
                 {
-                    FilterAndTransform.DestroyFilterAndTransformList(ref _specs);
+                    FilterAndTransform.DestroyFilterAndTransformList(ref _specs, this);
                 }
             }
         }
@@ -392,7 +435,7 @@ namespace System.Diagnostics
 
         /// <summary>
         /// A function which is fully interruptible even in release code so we can stop here and
-        /// do function evaluation in the debugger.   Thus this is just a place that is useful
+        /// do function evaluation in the debugger. Thus this is just a place that is useful
         /// for the debugger to place a breakpoint where it can inject code with function evaluation
         /// </summary>
         [NonEvent, MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
@@ -406,11 +449,21 @@ namespace System.Diagnostics
         }
         #endregion
 
+
+        [Flags]
+        internal enum ActivityEvents
+        {
+            None          = 0x00,
+            ActivityStart = 0x01,
+            ActivityStop  = 0x02,
+            All           = ActivityStart | ActivityStop,
+        }
+
         #region EventSource hooks
 
         /// <summary>
         /// FilterAndTransform represents on transformation specification from a DiagnosticsSource
-        /// to EventSource's 'Event' method.    (e.g.  MySource/MyEvent:out=prop1.prop2.prop3).
+        /// to EventSource's 'Event' method. (e.g. MySource/MyEvent:out=prop1.prop2.prop3).
         /// Its main method is 'Morph' which takes a DiagnosticSource object and morphs it into
         /// a list of string,string key value pairs.
         ///
@@ -429,7 +482,7 @@ namespace System.Diagnostics
             ///    OutputName=Prop1.Prop2.PropN
             ///
             /// Into linked list of FilterAndTransform that together forward events from the given
-            /// DiagnosticSource's to 'eventSource'.   Sets the 'specList' variable to this value
+            /// DiagnosticSource's to 'eventSource'. Sets the 'specList' variable to this value
             /// (destroying anything that was there previously).
             ///
             /// By default any serializable properties of the payload object are also included
@@ -438,11 +491,11 @@ namespace System.Diagnostics
             /// </summary>
             public static void CreateFilterAndTransformList(ref FilterAndTransform? specList, string? filterAndPayloadSpecs, DiagnosticSourceEventSource eventSource)
             {
-                DestroyFilterAndTransformList(ref specList);        // Stop anything that was on before.
+                DestroyFilterAndTransformList(ref specList, eventSource);        // Stop anything that was on before.
                 if (filterAndPayloadSpecs == null)
                     filterAndPayloadSpecs = "";
 
-                // Points just beyond the last point in the string that has yet to be parsed.   Thus we start with the whole string.
+                // Points just beyond the last point in the string that has yet to be parsed. Thus we start with the whole string.
                 int endIdx = filterAndPayloadSpecs.Length;
                 while (true)
                 {
@@ -459,19 +512,43 @@ namespace System.Diagnostics
                     while (startIdx < endIdx && char.IsWhiteSpace(filterAndPayloadSpecs[startIdx]))
                         startIdx++;
 
-                    specList = new FilterAndTransform(filterAndPayloadSpecs, startIdx, endIdx, eventSource, specList);
+#if EVENTSOURCE_ACTIVITY_SUPPORT
+                    if (IsActivitySourceEntry(filterAndPayloadSpecs, startIdx, endIdx))
+                    {
+                        AddNewActivitySourceTransform(filterAndPayloadSpecs, startIdx, endIdx, eventSource);
+                    }
+                    else
+#endif // EVENTSOURCE_ACTIVITY_SUPPORT
+                    {
+                        specList = new FilterAndTransform(filterAndPayloadSpecs, startIdx, endIdx, eventSource, specList);
+                    }
+
                     endIdx = newlineIdx;
                     if (endIdx < 0)
                         break;
                 }
+#if EVENTSOURCE_ACTIVITY_SUPPORT
+                if (eventSource._activitySourceSpecs != null)
+                {
+                    NormalizeActivitySourceSpecsList(eventSource);
+                    CreateActivityListener(eventSource);
+                }
+#endif // EVENTSOURCE_ACTIVITY_SUPPORT
             }
 
             /// <summary>
             /// This destroys (turns off) the FilterAndTransform stopping the forwarding started with CreateFilterAndTransformList
             /// </summary>
             /// <param name="specList"></param>
-            public static void DestroyFilterAndTransformList(ref FilterAndTransform? specList)
+            /// <param name="eventSource"></param>
+            public static void DestroyFilterAndTransformList(ref FilterAndTransform? specList, DiagnosticSourceEventSource eventSource)
             {
+#if EVENTSOURCE_ACTIVITY_SUPPORT
+                eventSource._activityListener?.Dispose();
+                eventSource._activityListener = null;
+                eventSource._activitySourceSpecs = null; // nothing to dispose inside this list.
+#endif // EVENTSOURCE_ACTIVITY_SUPPORT
+
                 var curSpec = specList;
                 specList = null;            // Null out the list
                 while (curSpec != null)     // Dispose everything in the list.
@@ -620,6 +697,320 @@ namespace System.Diagnostics
                 }));
             }
 
+#if EVENTSOURCE_ACTIVITY_SUPPORT
+            internal FilterAndTransform(string filterAndPayloadSpec, int endIdx, int colonIdx, string activitySourceName, string? activityName, ActivityEvents events, ActivitySamplingResult samplingResult, DiagnosticSourceEventSource eventSource)
+            {
+                _eventSource = eventSource;
+
+                Next = _eventSource._activitySourceSpecs;
+                _eventSource._activitySourceSpecs = this;
+
+                SourceName = activitySourceName;
+                ActivityName = activityName;
+                Events = events;
+                SamplingResult = samplingResult;
+
+                if (colonIdx >= 0)
+                {
+                    int startTransformIdx = colonIdx + 1;
+
+                    // If the transform spec begins with a - it means you don't want implicit transforms.
+                    if (startTransformIdx < endIdx && filterAndPayloadSpec[startTransformIdx] == '-')
+                    {
+                        _eventSource.Message("DiagnosticSource: suppressing implicit transforms.");
+                        _noImplicitTransforms = true;
+                        startTransformIdx++;
+                    }
+
+                    // Parse all the explicit transforms, if present
+                    if (startTransformIdx < endIdx)
+                    {
+                        while (true)
+                        {
+                            int specStartIdx = startTransformIdx;
+                            int semiColonIdx = filterAndPayloadSpec.LastIndexOf(';', endIdx - 1, endIdx - startTransformIdx);
+                            if (0 <= semiColonIdx)
+                                specStartIdx = semiColonIdx + 1;
+
+                            // Ignore empty specifications.
+                            if (specStartIdx < endIdx)
+                            {
+                                if (_eventSource.IsEnabled(EventLevel.Informational, Keywords.Messages))
+                                    _eventSource.Message("DiagnosticSource: Parsing Explicit Transform '" + filterAndPayloadSpec.Substring(specStartIdx, endIdx - specStartIdx) + "'");
+
+                                _explicitTransforms = new TransformSpec(filterAndPayloadSpec, specStartIdx, endIdx, _explicitTransforms);
+                            }
+                            if (startTransformIdx == specStartIdx)
+                                break;
+                            endIdx = semiColonIdx;
+                        }
+                    }
+                }
+            }
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            internal static bool IsActivitySourceEntry(string filterAndPayloadSpec, int startIdx, int endIdx) =>
+                            filterAndPayloadSpec.AsSpan(startIdx, endIdx - startIdx).StartsWith(c_ActivitySourcePrefix.AsSpan(), StringComparison.Ordinal);
+
+            internal static void AddNewActivitySourceTransform(string filterAndPayloadSpec, int startIdx, int endIdx, DiagnosticSourceEventSource eventSource)
+            {
+                Debug.Assert(endIdx - startIdx >= 4);
+                Debug.Assert(IsActivitySourceEntry(filterAndPayloadSpec, startIdx, endIdx));
+
+                ReadOnlySpan<char> eventName;
+                ReadOnlySpan<char> activitySourceName;
+
+                ActivityEvents supportedEvent = ActivityEvents.All; // Default events
+                ActivitySamplingResult samplingResult = ActivitySamplingResult.AllDataAndRecorded; // Default sampling results
+
+                int colonIdx = filterAndPayloadSpec.IndexOf(':', startIdx + c_ActivitySourcePrefix.Length, endIdx - startIdx - c_ActivitySourcePrefix.Length);
+
+                ReadOnlySpan<char> entry = filterAndPayloadSpec.AsSpan(
+                                                startIdx + c_ActivitySourcePrefix.Length,
+                                                (colonIdx >= 0 ? colonIdx : endIdx) - startIdx - c_ActivitySourcePrefix.Length)
+                                                .Trim();
+
+                int eventNameIndex = entry.IndexOf('/');
+                if (eventNameIndex >= 0)
+                {
+                    activitySourceName = entry.Slice(0, eventNameIndex).Trim();
+
+                    ReadOnlySpan<char> suffixPart = entry.Slice(eventNameIndex + 1, entry.Length - eventNameIndex - 1).Trim();
+                    int samplingResultIndex = suffixPart.IndexOf('-');
+                    if (samplingResultIndex >= 0)
+                    {
+                        // We have the format "[AS]SourceName/[EventName]-[SamplingResult]
+                        eventName = suffixPart.Slice(0, samplingResultIndex).Trim();
+                        suffixPart = suffixPart.Slice(samplingResultIndex + 1, suffixPart.Length - samplingResultIndex - 1).Trim();
+
+                        if (suffixPart.Length > 0)
+                        {
+                            if (suffixPart.Equals("Propagate".AsSpan(), StringComparison.OrdinalIgnoreCase))
+                            {
+                                samplingResult = ActivitySamplingResult.PropagationData;
+                            }
+                            else if (suffixPart.Equals("Record".AsSpan(), StringComparison.OrdinalIgnoreCase))
+                            {
+                                samplingResult = ActivitySamplingResult.AllData;
+                            }
+                            else
+                            {
+                                // Invalid format
+                                return;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        // We have the format "[AS]SourceName/[EventName]
+                        eventName = suffixPart;
+                    }
+
+                    if (eventName.Length > 0)
+                    {
+                        if (eventName.Equals("Start".AsSpan(), StringComparison.OrdinalIgnoreCase))
+                        {
+                            supportedEvent = ActivityEvents.ActivityStart;
+                        }
+                        else if (eventName.Equals("Stop".AsSpan(), StringComparison.OrdinalIgnoreCase))
+                        {
+                            supportedEvent = ActivityEvents.ActivityStop;
+                        }
+                        else
+                        {
+                            // Invalid format
+                            return;
+                        }
+                    }
+                }
+                else
+                {
+                    // We have the format "[AS]SourceName"
+                    activitySourceName = entry;
+                }
+
+                string? activityName = null;
+                int plusSignIndex = activitySourceName.IndexOf('+');
+                if (plusSignIndex >= 0)
+                {
+                    activityName = activitySourceName.Slice(plusSignIndex + 1).Trim().ToString();
+                    activitySourceName = activitySourceName.Slice(0, plusSignIndex).Trim();
+                }
+
+                var transform = new FilterAndTransform(filterAndPayloadSpec, endIdx, colonIdx, activitySourceName.ToString(), activityName, supportedEvent, samplingResult, eventSource);
+            }
+
+            // Check if we are interested to listen to such ActivitySource
+            private static ActivitySamplingResult Sample(string activitySourceName, string activityName, DiagnosticSourceEventSource eventSource)
+            {
+                FilterAndTransform? list = eventSource._activitySourceSpecs;
+                ActivitySamplingResult specificResult = ActivitySamplingResult.None;
+                ActivitySamplingResult wildResult = ActivitySamplingResult.None;
+
+                while (list != null)
+                {
+                    if (list.ActivityName == null || list.ActivityName == activityName)
+                    {
+                        if (activitySourceName == list.SourceName)
+                        {
+                                if (list.SamplingResult > specificResult)
+                                {
+                                    specificResult = list.SamplingResult;
+                                }
+
+                                if (specificResult >= ActivitySamplingResult.AllDataAndRecorded)
+                                {
+                                    return specificResult; // highest possible value
+                                }
+                                // We don't break here as we can have more than one entry with the same source name.
+                            }
+                        else if (list.SourceName == "*")
+                        {
+                            if (specificResult != ActivitySamplingResult.None)
+                            {
+                                // We reached the '*' nodes which means there is no more specific source names in the list.
+                                // If we encountered any specific node before, then return that value.
+                                return specificResult;
+                            }
+
+                            if (list.SamplingResult > wildResult)
+                            {
+                                wildResult = list.SamplingResult;
+                            }
+                        }
+                    }
+                    list = list.Next;
+                }
+
+                // We can return None in case there is no '*' nor any entry match the source name.
+                return specificResult != ActivitySamplingResult.None ? specificResult : wildResult;
+            }
+
+            internal static void CreateActivityListener(DiagnosticSourceEventSource eventSource)
+            {
+                Debug.Assert(eventSource._activityListener == null);
+                Debug.Assert(eventSource._activitySourceSpecs != null);
+
+                eventSource._activityListener = new ActivityListener();
+
+                eventSource._activityListener.SampleUsingParentId = (ref ActivityCreationOptions<string> activityOptions) => Sample(activityOptions.Source.Name, activityOptions.Name, eventSource);
+                eventSource._activityListener.Sample = (ref ActivityCreationOptions<ActivityContext> activityOptions) => Sample(activityOptions.Source.Name, activityOptions.Name, eventSource);
+
+                eventSource._activityListener.ShouldListenTo = (activitySource) =>
+                {
+                    FilterAndTransform? list = eventSource._activitySourceSpecs;
+                    while (list != null)
+                    {
+                        if (activitySource.Name == list.SourceName || list.SourceName == "*")
+                        {
+                            return true;
+                        }
+
+                        list = list.Next;
+                    }
+
+                    return false;
+                };
+
+                eventSource._activityListener.ActivityStarted = activity =>
+                {
+                    FilterAndTransform? list = eventSource._activitySourceSpecs;
+                    while (list != null)
+                    {
+                        if ((list.Events & ActivityEvents.ActivityStart) != 0 &&
+                            (activity.Source.Name == list.SourceName || list.SourceName == "*") &&
+                            (list.ActivityName == null || list.ActivityName == activity.OperationName))
+                        {
+                            eventSource.ActivityStart(activity.Source.Name, activity.OperationName, list.Morph(activity));
+                            return;
+                        }
+
+                        list = list.Next;
+                    }
+                };
+
+                eventSource._activityListener.ActivityStopped = activity =>
+                {
+                    FilterAndTransform? list = eventSource._activitySourceSpecs;
+                    while (list != null)
+                    {
+                        if ((list.Events & ActivityEvents.ActivityStop) != 0 &&
+                            (activity.Source.Name == list.SourceName || list.SourceName == "*") &&
+                            (list.ActivityName == null || list.ActivityName == activity.OperationName))
+                        {
+                            eventSource.ActivityStop(activity.Source.Name, activity.OperationName, list.Morph(activity));
+                            return;
+                        }
+
+                        list = list.Next;
+                    }
+                };
+
+                ActivitySource.AddActivityListener(eventSource._activityListener);
+            }
+
+            // Move all wildcard nodes at the end of the list.
+            // This will give more priority to the specific nodes over the wildcards.
+            internal static void NormalizeActivitySourceSpecsList(DiagnosticSourceEventSource eventSource)
+            {
+                Debug.Assert(eventSource._activityListener == null);
+                Debug.Assert(eventSource._activitySourceSpecs != null);
+
+                FilterAndTransform? list = eventSource._activitySourceSpecs;
+
+                FilterAndTransform? firstSpecificList = null;
+                FilterAndTransform? lastSpecificList = null;
+
+                FilterAndTransform? firstWildcardList = null;
+                FilterAndTransform? lastWildcardList = null;
+
+                while (list != null)
+                {
+                    if (list.SourceName == "*")
+                    {
+                        if (firstWildcardList == null)
+                        {
+                            firstWildcardList = lastWildcardList = list;
+                        }
+                        else
+                        {
+                            Debug.Assert(lastWildcardList != null);
+                            lastWildcardList.Next = list;
+                            lastWildcardList = list;
+                        }
+                    }
+                    else
+                    {
+                        if (firstSpecificList == null)
+                        {
+                            firstSpecificList = lastSpecificList = list;
+                        }
+                        else
+                        {
+                            Debug.Assert(lastSpecificList != null);
+                            lastSpecificList.Next = list;
+                            lastSpecificList = list;
+                        }
+                    }
+
+                    list = list.Next;
+                }
+
+                if (firstSpecificList == null || firstWildcardList == null)
+                {
+                    Debug.Assert(firstSpecificList != null || firstWildcardList != null);
+                    return; // list shouldn't be chanaged.
+                }
+
+                Debug.Assert(lastWildcardList != null && lastSpecificList != null);
+
+                lastSpecificList.Next = firstWildcardList;
+                lastWildcardList.Next = null;
+
+                eventSource._activitySourceSpecs = firstSpecificList;
+            }
+#endif // EVENTSOURCE_ACTIVITY_SUPPORT
+
             private void Dispose()
             {
                 if (_diagnosticsListenersSubscription != null)
@@ -704,7 +1095,18 @@ namespace System.Diagnostics
 
             public FilterAndTransform? Next;
 
+            // Specific ActivitySource Transforms information
+
+#if EVENTSOURCE_ACTIVITY_SUPPORT
+            internal const string c_ActivitySourcePrefix = "[AS]";
+            internal string? SourceName { get; set; }
+            internal string? ActivityName { get; set; }
+            internal DiagnosticSourceEventSource.ActivityEvents Events  { get; set; }
+            internal ActivitySamplingResult SamplingResult { get; set; }
+#endif // EVENTSOURCE_ACTIVITY_SUPPORT
+
             #region private
+
             // Given a type generate all the implicit transforms for type (that is for every field
             // generate the spec that fetches it).
             private static TransformSpec? MakeImplicitTransforms(Type type)
@@ -755,14 +1157,14 @@ namespace System.Diagnostics
 
         /// <summary>
         /// Transform spec represents a string that describes how to extract a piece of data from
-        /// the DiagnosticSource payload.   An example string is OUTSTR=EVENT_VALUE.PROP1.PROP2.PROP3
+        /// the DiagnosticSource payload. An example string is OUTSTR=EVENT_VALUE.PROP1.PROP2.PROP3
         /// It has a Next field so they can be chained together in a linked list.
         /// </summary>
         internal class TransformSpec
         {
             /// <summary>
             /// parse the strings 'spec' from startIdx to endIdx (points just beyond the last considered char)
-            /// The syntax is ID1=ID2.ID3.ID4 ....   Where ID1= is optional.
+            /// The syntax is ID1=ID2.ID3.ID4 .... Where ID1= is optional.
             /// </summary>
             public TransformSpec(string transformSpec, int startIdx, int endIdx, TransformSpec? next = null)
             {
@@ -797,7 +1199,7 @@ namespace System.Diagnostics
             }
 
             /// <summary>
-            /// Given the DiagnosticSourcePayload 'obj', compute a key-value pair from it.  For example
+            /// Given the DiagnosticSourcePayload 'obj', compute a key-value pair from it. For example
             /// if the spec is OUTSTR=EVENT_VALUE.PROP1.PROP2.PROP3 and the ultimate value of PROP3 is
             /// 10 then the return key value pair is  KeyValuePair("OUTSTR","10")
             /// </summary>
@@ -820,7 +1222,7 @@ namespace System.Diagnostics
             #region private
             /// <summary>
             /// A PropertySpec represents information needed to fetch a property from
-            /// and efficiently.   Thus it represents a '.PROP' in a TransformSpec
+            /// and efficiently. Thus it represents a '.PROP' in a TransformSpec
             /// (and a transformSpec has a list of these).
             /// </summary>
             internal class PropertySpec
@@ -871,7 +1273,7 @@ namespace System.Diagnostics
 
                 #region private
                 /// <summary>
-                /// PropertyFetch is a helper class.  It takes a PropertyInfo and then knows how
+                /// PropertyFetch is a helper class. It takes a PropertyInfo and then knows how
                 /// to efficiently fetch that property from a .NET object (See Fetch method).
                 /// It hides some slightly complex generic code.
                 /// </summary>
@@ -935,7 +1337,7 @@ namespace System.Diagnostics
                                 return (PropertyFetch)Activator.CreateInstance(instantiatedTypedPropertyFetcher, type)!;
                             }
 
-                            // no implemenation of IEnumerable<T> found, return a null fetcher
+                            // no implementation of IEnumerable<T> found, return a null fetcher
                             Logger.Message($"*Enumerate applied to non-enumerable type {type}");
                             return new PropertyFetch(type);
 #endif
@@ -1081,7 +1483,11 @@ namespace System.Diagnostics
 
         #endregion
 
-        private FilterAndTransform? _specs;      // Transformation specifications that indicate which sources/events are forwarded.
+        private FilterAndTransform? _specs;                 // Transformation specifications that indicate which sources/events are forwarded.
+#if EVENTSOURCE_ACTIVITY_SUPPORT
+        private FilterAndTransform? _activitySourceSpecs;   // ActivitySource Transformation specifications that indicate which sources/events are forwarded.
+        private ActivityListener? _activityListener;
+#endif // EVENTSOURCE_ACTIVITY_SUPPORT
         #endregion
     }
 }
index 83fddd0..6e4301e 100644 (file)
@@ -14,7 +14,7 @@ namespace System.Diagnostics.Tests
     public class DiagnosticSourceEventSourceBridgeTests
     {
         // To avoid interactions between tests when they are run in parallel, we run all these tests in their
-        // own sub-process using RemoteExecutor.Invoke()  However this makes it very inconvinient to debug the test.
+        // own sub-process using RemoteExecutor.Invoke()  However this makes it very inconvenient to debug the test.
         // By seting this #if to true you stub out RemoteInvoke and the code will run in-proc which is useful
         // in debugging.
 #if false
@@ -30,6 +30,381 @@ namespace System.Diagnostics.Tests
             return new NullDispose();
         }
 #endif
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void TestEnableAllActivitySourcesAllEvents()
+        {
+            RemoteExecutor.Invoke(() =>
+            {
+                using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
+                ActivitySource [] sources = new ActivitySource[10];
+                for (int i = 0; i < 10; i++)
+                {
+                    sources[i] = new ActivitySource($"Source{i}");
+                }
+
+                int eventsCount = 0;
+                Assert.Equal(eventsCount, eventSourceListener.EventCount);
+                eventSourceListener.Enable("  [AS]*  \r\n"); // All Sources + All Events
+
+                Activity [] activities = new Activity[10];
+
+                for (int i = 0; i < 10; i++)
+                {
+                    activities[i] = sources[i].StartActivity($"activity{i}");
+                    Assert.NotNull(activities[i]);
+                    Assert.Equal(++eventsCount, eventSourceListener.EventCount);
+                    ValidateActivityEvents(eventSourceListener, "ActivityStart", sources[i].Name, activities[i].OperationName);
+                    Assert.True(activities[i].IsAllDataRequested);
+                    Assert.Equal(ActivityTraceFlags.Recorded, activities[i].ActivityTraceFlags);
+                }
+
+                for (int i = 0; i < 10; i++)
+                {
+                    activities[i].Dispose();
+                    Assert.Equal(++eventsCount, eventSourceListener.EventCount);
+                    ValidateActivityEvents(eventSourceListener, "ActivityStop", sources[i].Name, activities[i].OperationName);
+                    sources[i].Dispose();
+                }
+
+            }).Dispose();
+        }
+
+        [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        [InlineData("Start")]
+        [InlineData("Stop")]
+        [InlineData("start")]
+        [InlineData("stop")]
+        public void TestEnableAllActivitySourcesWithOneEvent(string eventName)
+        {
+            RemoteExecutor.Invoke((eventname) =>
+            {
+                using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
+                ActivitySource [] sources = new ActivitySource[10];
+                for (int i = 0; i < 10; i++)
+                {
+                    sources[i] = new ActivitySource($"Source{i}");
+                }
+
+                int eventsCount = 0;
+                Assert.Equal(eventsCount, eventSourceListener.EventCount);
+                eventSourceListener.Enable($"  [AS]* / {eventname}  \r\n"); // All Sources + one Event
+
+                Activity [] activities = new Activity[10];
+
+                for (int i = 0; i < 10; i++)
+                {
+                    activities[i] = sources[i].StartActivity($"activity{i}");
+                    Assert.NotNull(activities[i]);
+
+                    if (eventname.Equals("Start", StringComparison.OrdinalIgnoreCase))
+                    {
+                        eventsCount++;
+                        ValidateActivityEvents(eventSourceListener, "ActivityStart", sources[i].Name, activities[i].OperationName);
+                    }
+                    Assert.Equal(eventsCount, eventSourceListener.EventCount);
+                    Assert.True(activities[i].IsAllDataRequested);
+                    Assert.Equal(ActivityTraceFlags.Recorded, activities[i].ActivityTraceFlags);
+                }
+
+                for (int i = 0; i < 10; i++)
+                {
+                    activities[i].Dispose();
+
+                    if (eventname.Equals("Stop", StringComparison.OrdinalIgnoreCase))
+                    {
+                        eventsCount++;
+                        ValidateActivityEvents(eventSourceListener, "ActivityStop", sources[i].Name, activities[i].OperationName);
+                    }
+
+                    Assert.Equal(eventsCount, eventSourceListener.EventCount);
+                    sources[i].Dispose();
+                }
+            }, eventName).Dispose();
+        }
+
+        [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        [InlineData("Propagate", false, ActivityTraceFlags.None)]
+        [InlineData("PROPAGATE", false, ActivityTraceFlags.None)]
+        [InlineData("Record",    true,  ActivityTraceFlags.None)]
+        [InlineData("recorD",    true,  ActivityTraceFlags.None)]
+        [InlineData("",          true,  ActivityTraceFlags.Recorded)]
+        public void TestEnableAllActivitySourcesWithSpeciifcSamplingResult(string samplingResult, bool alldataRequested, ActivityTraceFlags activityTraceFlags)
+        {
+            RemoteExecutor.Invoke((result, dataRequested, traceFlags) =>
+            {
+                using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
+                using ActivitySource source = new ActivitySource("SamplingSource");
+
+                Assert.Equal(0, eventSourceListener.EventCount);
+                eventSourceListener.Enable($"  [AS]* /- {result}  \r\n"); // All Sources + one Event
+
+
+                Activity activity = source.StartActivity($"samplingActivity");
+                Assert.NotNull(activity);
+                Assert.Equal(1, eventSourceListener.EventCount);
+                ValidateActivityEvents(eventSourceListener, "ActivityStart", source.Name, activity.OperationName);
+
+                Assert.Equal(bool.Parse(dataRequested), activity.IsAllDataRequested);
+                Assert.Equal(traceFlags, activity.ActivityTraceFlags.ToString());
+
+                activity.Dispose();
+
+                Assert.Equal(2, eventSourceListener.EventCount);
+                ValidateActivityEvents(eventSourceListener, "ActivityStop", source.Name, activity.OperationName);
+            }, samplingResult, alldataRequested.ToString(), activityTraceFlags.ToString()).Dispose();
+        }
+
+        [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        [InlineData("",      "",          true)]
+        [InlineData("Start", "",          true)]
+        [InlineData("stop",  "",          true)]
+        [InlineData("",      "Propagate", false)]
+        [InlineData("",      "Record",    true)]
+        [InlineData("Start", "Propagate", false)]
+        [InlineData("Stop",  "Record",    true)]
+        public void TestDefaultActivitySource(string eventName, string samplingResult, bool alldataRequested)
+        {
+            RemoteExecutor.Invoke((eventname, result, dataRequested) =>
+            {
+                using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
+                Activity a = new Activity(""); // we need this to ensure DiagnosticSourceEventSource.Logger creation.
+
+                Assert.Equal(0, eventSourceListener.EventCount);
+                eventSourceListener.Enable($"  [AS]/{eventname}-{result}\r\n");
+                Assert.Equal("", a.Source.Name);
+
+                a = a.Source.StartActivity("newOne");
+
+                Assert.Equal(bool.Parse(dataRequested), a.IsAllDataRequested);
+                // All Activities created with "new Activity(...)" will have ActivityTraceFlags is `None`;
+                Assert.Equal(result.Length == 0 ? ActivityTraceFlags.Recorded : ActivityTraceFlags.None, a.ActivityTraceFlags);
+
+                a.Dispose();
+
+                int eCount = eventname.Length == 0 ? 2 : 1;
+                Assert.Equal(eCount, eventSourceListener.EventCount);
+
+                // None Default Source
+                ActivitySource source = new ActivitySource("NoneDefault"); // not the default ActivitySource
+                Activity activity = source.StartActivity($"ActivityFromNoneDefault"); // Shouldn't fire any event
+                Assert.Equal(eCount, eventSourceListener.EventCount);
+                Assert.Null(activity);
+            }, eventName, samplingResult, alldataRequested.ToString()).Dispose();
+        }
+
+        [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        [InlineData("[AS]*\r\n [AS]Specific/-Propagate\r\n [AS]*/-Propagate\r\n", false, true)]
+        [InlineData("[AS]AnotherSource/-Propagate\r\n [AS]*/-Propagate\r\n [AS]Specific/-Record\r\n [AS]*\r\n", true, true)]
+        [InlineData("[AS]*/-Propagate\r\n  [AS]*/-Propagate\r\n [AS]Specific/-Propagate\r\n[AS]Specific/-Record\r\n", true, false)]
+        [InlineData("[AS]*/-Propagate\r\n", false, false)]
+        [InlineData("[AS]*/-Record\r\n", true, true)]
+        [InlineData("[AS]Specific/-Propagate\r\n [AS]NoneSpecific/-Record\r\n", false, true)]
+        [InlineData("[AS]Specific/-Record\r\n [AS]NoneSpecific/-Propagate\r\n", true, false)]
+        [InlineData("[AS]Specific\r\n [AS]NoneSpecific\r\n", true, true)]
+        public void TestMultipleSpecs(string spec, bool isAlldataRequestedFromSpecif, bool alldataRequestedFromNoneSpecific)
+        {
+            RemoteExecutor.Invoke((specString, specificAllData, noneSpecificAllData) =>
+            {
+                using TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener();
+                using ActivitySource aSource1 = new ActivitySource("Specific");
+                eventSourceListener.Enable(specString);
+
+                Assert.Equal(0, eventSourceListener.EventCount);
+
+                Activity a1 = aSource1.StartActivity("a1");
+                Assert.NotNull(a1);
+                Assert.Equal(1, eventSourceListener.EventCount);
+                Assert.Equal(bool.Parse(specificAllData), a1.IsAllDataRequested);
+                a1.Dispose();
+                Assert.Equal(2, eventSourceListener.EventCount);
+
+                using ActivitySource aSource2 = new ActivitySource("NoneSpecific");
+                Activity a2 = aSource2.StartActivity("a2");
+                Assert.NotNull(a2);
+                Assert.Equal(3, eventSourceListener.EventCount);
+                Assert.Equal(bool.Parse(noneSpecificAllData), a2.IsAllDataRequested);
+                a2.Dispose();
+                Assert.Equal(4, eventSourceListener.EventCount);
+
+            }, spec, isAlldataRequestedFromSpecif.ToString(), alldataRequestedFromNoneSpecific.ToString()).Dispose();
+        }
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void TestTransformSpecs()
+        {
+            RemoteExecutor.Invoke(() =>
+            {
+                using (TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener())
+                {
+                    using ActivitySource aSource1 = new ActivitySource("TransSpecsSource");
+                    eventSourceListener.Enable("[AS]*");
+
+                    Activity a = aSource1.StartActivity("TransSpecs");
+                    ValidateActivityEvents(eventSourceListener, "ActivityStart", "TransSpecsSource", "TransSpecs");
+
+                    var list1 = eventSourceListener.LastEvent.Arguments;
+                    Assert.Equal(a.OperationName, list1["OperationName"]);
+
+                    a.Dispose();
+                    ValidateActivityEvents(eventSourceListener, "ActivityStop", "TransSpecsSource", "TransSpecs");
+
+                    var list2 = eventSourceListener.LastEvent.Arguments;
+                    Assert.Equal(a.OperationName, list2["OperationName"]);
+
+                    Assert.Equal(list1.Count, list2.Count);
+                    foreach (string key in list1.Keys)
+                    {
+                        Assert.NotNull(list2[key]);
+                    }
+
+                    // Suppress all
+                    eventSourceListener.Enable("[AS]*:-");
+                    a = aSource1.StartActivity("TransSpecs");
+                    Assert.Equal(0, eventSourceListener.LastEvent.Arguments.Count);
+                    a.Stop();
+                    Assert.Equal(0, eventSourceListener.LastEvent.Arguments.Count);
+
+                    // Suppress all except ActivitySource name
+                    eventSourceListener.Enable("[AS]*:-ActivitySourceName=Source.Name");
+                    a = aSource1.StartActivity("TransSpecs");
+                    Assert.Equal(1, eventSourceListener.LastEvent.Arguments.Count);
+                    Assert.Equal(aSource1.Name, eventSourceListener.LastEvent.Arguments["ActivitySourceName"]);
+
+                    a.Stop();
+                    Assert.Equal(1, eventSourceListener.LastEvent.Arguments.Count);
+                    Assert.Equal(aSource1.Name, eventSourceListener.LastEvent.Arguments["ActivitySourceName"]);
+
+                    // Collect TraceId, SpanId, and ParentSpanId only
+                    eventSourceListener.Enable("[AS]*:-TraceId;SpanId;ParentSpanId");
+                    a = aSource1.StartActivity("ActivityData");
+                    Assert.Equal(3, eventSourceListener.LastEvent.Arguments.Count);
+
+                    Assert.Equal(a.SpanId.ToString(), eventSourceListener.LastEvent.Arguments["SpanId"]);
+                    Assert.Equal(a.TraceId.ToString(), eventSourceListener.LastEvent.Arguments["TraceId"]);
+                    Assert.Equal(a.ParentSpanId.ToString(), eventSourceListener.LastEvent.Arguments["ParentSpanId"]);
+
+                    a.Stop();
+                    Assert.Equal(3, eventSourceListener.LastEvent.Arguments.Count);
+                    Assert.Equal(a.SpanId.ToString(), eventSourceListener.LastEvent.Arguments["SpanId"]);
+                    Assert.Equal(a.TraceId.ToString(), eventSourceListener.LastEvent.Arguments["TraceId"]);
+                    Assert.Equal(a.ParentSpanId.ToString(), eventSourceListener.LastEvent.Arguments["ParentSpanId"]);
+                }
+
+            }).Dispose();
+        }
+
+
+        [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+        public void TestFilteringWithActivityName()
+        {
+            RemoteExecutor.Invoke(() =>
+            {
+                using (TestDiagnosticSourceEventListener eventSourceListener = new TestDiagnosticSourceEventListener())
+                {
+                    using ActivitySource aSource1 = new ActivitySource("Source1");
+                    using ActivitySource aSource2 = new ActivitySource("Source2");
+
+                    eventSourceListener.Enable("[AS]*+MyActivity");
+                    Assert.Equal(0, eventSourceListener.EventCount);
+
+                    Activity a = aSource1.StartActivity("NotMyActivity"); // Shouldn't get created because nt matching MyActivity
+                    Assert.Null(a);
+                    Assert.Equal(0, eventSourceListener.EventCount);
+
+                    a = aSource1.StartActivity("MyActivity");
+                    Assert.NotNull(a);
+                    Assert.Equal(1, eventSourceListener.EventCount);
+                    Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
+                    a.Stop();
+                    Assert.Equal(2, eventSourceListener.EventCount);
+                    Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
+
+                    a = aSource2.StartActivity("MyActivity");
+                    Assert.NotNull(a);
+                    Assert.Equal(3, eventSourceListener.EventCount);
+                    Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
+                    a.Stop();
+                    Assert.Equal(4, eventSourceListener.EventCount);
+                    Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
+
+                    //
+                    // Now test with specific ActivitySource and ActivityName
+                    //
+
+                    eventSourceListener.Enable("[AS]MySource+MyActivity");
+                    a = aSource1.StartActivity("MyActivity"); // Not from MySource
+                    Assert.Null(a);
+                    Assert.Equal(4, eventSourceListener.EventCount);
+                    using ActivitySource aSource3 = new ActivitySource("MySource");
+                    a = aSource3.StartActivity("NotMyActivity"); // from MySource but NoMyActivity
+                    Assert.Null(a);
+                    Assert.Equal(4, eventSourceListener.EventCount);
+
+                    a = aSource3.StartActivity("MyActivity"); // from MySource and MyActivity
+                    Assert.NotNull(a);
+                    Assert.Equal(5, eventSourceListener.EventCount);
+                    Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
+
+                    a.Stop();
+                    Assert.Equal(6, eventSourceListener.EventCount);
+                    Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
+
+                    //
+                    // test with full query
+                    //
+                    eventSourceListener.Enable("[AS]MySource+MyActivity/Start-Propagate");
+                    a = aSource3.StartActivity("MyActivity"); // from MySource and MyActivity
+                    Assert.NotNull(a);
+                    Assert.Equal(7, eventSourceListener.EventCount);
+                    Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
+                    Assert.False(a.IsAllDataRequested);
+
+                    a.Stop(); // shouldn't fire
+                    Assert.Equal(7, eventSourceListener.EventCount);
+
+                    //
+                    // test with default Source
+                    //
+                    eventSourceListener.Enable("[AS]+MyActivity");
+                    a = new Activity("MyActivity");
+                    a.Start();
+                    Assert.Equal(8, eventSourceListener.EventCount);
+                    Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
+                    Assert.True(a.IsAllDataRequested);
+
+                    a.Stop();
+                    Assert.Equal(9, eventSourceListener.EventCount);
+                    Assert.Equal(a.OperationName, eventSourceListener.LastEvent.Arguments["OperationName"]);
+
+                    a = new Activity("NotMyActivity");
+                    a.Start(); // nothing fire
+                    Assert.Equal(9, eventSourceListener.EventCount);
+
+                    //
+                    // Test with Empty ActivityName
+                    //
+                    eventSourceListener.Enable("[AS]+");
+                    a = new Activity("");
+                    a.Start();
+                    Assert.Equal(10, eventSourceListener.EventCount);
+                    a.Stop();
+                    Assert.Equal(11, eventSourceListener.EventCount);
+
+                    a = aSource3.StartActivity("");
+                    Assert.Null(a);
+                }
+
+            }).Dispose();
+        }
+
+        internal void ValidateActivityEvents(TestDiagnosticSourceEventListener eventSourceListener, string eventName, string sourceName, string activityName)
+        {
+            Assert.Equal(eventName, eventSourceListener.LastEvent.EventSourceEventName);
+            Assert.Equal(sourceName, eventSourceListener.LastEvent.SourceName);
+            Assert.Equal(activityName, eventSourceListener.LastEvent.EventName);
+        }
+
         /// <summary>
         /// Tests the basic functionality of turning on specific EventSources and specifying
         /// the events you want.
@@ -808,7 +1183,7 @@ namespace System.Diagnostics.Tests
                     diagnosticListener.StartActivity(activity1, new { DummyProp = "val" });
                     Assert.Equal(1, eventListener.EventCount);
                     AssertActivityMatchesEvent(activity1, eventListener.LastEvent, isStart: true);
-                    
+
                     Activity activity2 = new Activity("TestActivity2");
                     diagnosticListener.StartActivity(activity2, new { DummyProp = "val" });
                     Assert.Equal(2, eventListener.EventCount);
@@ -991,7 +1366,7 @@ namespace System.Diagnostics.Tests
         public Dictionary<string, string> Arguments;
 
         // Not typically important.
-        public string EventSourceEventName;    // This is the name of the EventSourceEvent that carried the data.   Only important for activities.
+        public string EventSourceEventName;    // This is the name of the EventSourceEvent that carried the data. Only important for activities.
 
         public override string ToString()
         {
@@ -1069,7 +1444,7 @@ namespace System.Diagnostics.Tests
                 if (eventData.Payload.Count == 3 && (eventData.EventName == "Event" || eventData.EventName.Contains("Activity")))
                 {
                     Debug.Assert(eventData.PayloadNames[0] == "SourceName");
-                    Debug.Assert(eventData.PayloadNames[1] == "EventName");
+                    Debug.Assert(eventData.PayloadNames[1] == "EventName" || eventData.PayloadNames[1] == "ActivityName");
                     Debug.Assert(eventData.PayloadNames[2] == "Arguments");
 
                     var anEvent = new DiagnosticSourceEvent();