Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>2020-09-01 23:32:36 +0300
committerGitHub <noreply@github.com>2020-09-01 23:32:36 +0300
commit1186d1fb809aeee07c6922ef5fd3ddc4bfd82ed8 (patch)
treed8fa4b0e4df4bbd07702dd16c47949a0dcfa59ba
parent67deb1d9bea3b98e1f21711c2b014240f86cec41 (diff)
[release/5.0] Add Support to ActivitySource events in DiagnosticSourceEventSource (#41681)
Co-authored-by: Tarek Mahmoud Sayed <tarekms@microsoft.com>
-rw-r--r--src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs2
-rw-r--r--src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs486
-rw-r--r--src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs383
3 files changed, 827 insertions, 44 deletions
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 d1b4d63d911..8d3f3e99366 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs
@@ -44,6 +44,8 @@ namespace System.Diagnostics
}
}, this);
}
+
+ GC.KeepAlive(DiagnosticSourceEventSource.Logger);
}
/// <summary>
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs
index fdfbd90c723..b38829b6ad0 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs
@@ -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
}
}
diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs
index 83fddd0e48f..6e4301e6ed8 100644
--- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs
+++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceEventSourceBridgeTests.cs
@@ -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();