-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
DiagnosticSourceEventSource.cs
1501 lines (1347 loc) · 79.6 KB
/
DiagnosticSourceEventSource.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
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
/// 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 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
/// 'Events' keyword (for diagnostics purposes, you should also turn on the 'Messages' keyword.
///
/// This EventSource defines a EventSource argument called 'FilterAndPayloadSpecs' that defines
/// 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
/// (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.
///
/// Once you have the particular values you desire, the implicit payload elements are typically not needed
/// anymore and you can prefix the Transform specification with a '-' which suppresses the implicit
/// transform (you only get the values of the properties you specifically ask for.
///
/// Logically a transform specification is simply a fetching specification X.Y.Z along with a name to give
/// it in the output (which defaults to the last name in the fetch specification).
///
/// The FilterAndPayloadSpecs is one long string with the following structures
///
/// * It is a newline separated list of FILTER_AND_PAYLOAD_SPEC
/// * a FILTER_AND_PAYLOAD_SPEC can be
/// * EVENT_NAME : TRANSFORM_SPECS
/// * EMPTY - turns on all sources with implicit payload elements.
/// * an EVENTNAME can be
/// * 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
/// * PROPERTY_SPEC - This is a shortcut where VARIABLE_NAME is the LAST property name
/// * a PROPERTY_SPEC is basically a list of names separated by '.'
/// * PROPERTY_NAME - fetches a property from the DiagnosticSource payload object
/// * PROPERTY_NAME . PROPERTY NAME - fetches a sub-property of the object.
///
/// * *Activity - fetches Activity.Current
/// * *Enumerate - enumerates all the items in an IEnumerable, calls ToString() on them, and joins the
/// strings in a comma separated list.
/// Example1:
///
/// "BridgeTestSource1/TestEvent1:cls_Point_X=cls.Point.X;cls_Point_Y=cls.Point.Y\r\n" +
/// "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
/// 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
/// 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
/// generated, but if there is a property call 'cls' which has a property 'Url' then that will be placed in
/// the output with the name 'Url' (since that was the last property name used and no Variable= clause was
/// specified.
///
/// Example:
///
/// "BridgeTestSource1\r\n" +
/// "BridgeTestSource2"
///
/// 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:
///
/// ""
///
/// 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
/// 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 DiagnosticEventSource event called 'Event'
/// which has three fields
///
/// string SourceName,
/// string EventName,
/// IEnumerable[KeyValuePair[string, string]] Argument
///
/// However to support start-stop activity tracking, there are six other events that can be used
///
/// Activity1Start
/// Activity1Stop
/// Activity2Start
/// Activity2Stop
/// RecursiveActivity1Start
/// 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
/// 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/)
///
/// For example the specification
///
/// "MyDiagnosticSource/RequestStart@Activity1Start\r\n" +
/// "MyDiagnosticSource/RequestStop@Activity1Stop\r\n" +
/// "MyDiagnosticSource/SecurityStart@Activity2Start\r\n" +
/// "MyDiagnosticSource/SecurityStop@Activity2Stop\r\n"
///
/// Defines that RequestStart will be logged with the EventSource Event Activity1Start (and the corresponding stop) which
/// 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 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
/// 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
/// activity NEVER ends).
///
/// See the DiagnosticSourceEventSourceBridgeTest.cs for more explicit examples of using this bridge.
/// </summary>
[EventSource(Name = "Microsoft-Diagnostics-DiagnosticSource")]
// These suppressions can go away with https://github.com/mono/linker/issues/2175
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2113:ReflectionToRequiresUnreferencedCode",
Justification = "In EventSource, EnsureDescriptorsInitialized's use of GetType preserves methods on Delegate and MulticastDelegate " +
"because the nested type OverrideEventProvider's base type EventProvider defines a delegate. " +
"This includes Delegate and MulticastDelegate methods which require unreferenced code, but " +
"EnsureDescriptorsInitialized does not access these members and is safe to call.")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2115:ReflectionToDynamicallyAccessedMembers",
Justification = "In EventSource, EnsureDescriptorsInitialized's use of GetType preserves methods on Delegate and MulticastDelegate " +
"because the nested type OverrideEventProvider's base type EventProvider defines a delegate. " +
"This includes Delegate and MulticastDelegate methods which have dynamically accessed members requirements, but " +
"EnsureDescriptorsInitialized does not access these members and is safe to call.")]
internal sealed class DiagnosticSourceEventSource : EventSource
{
public static DiagnosticSourceEventSource Log = new DiagnosticSourceEventSource();
public static class Keywords
{
/// <summary>
/// Indicates diagnostics messages from DiagnosticSourceEventSource should be included.
/// </summary>
public const EventKeywords Messages = (EventKeywords)0x1;
/// <summary>
/// Indicates that all events from all diagnostic sources should be forwarded to the EventSource using the 'Event' event.
/// </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 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
// 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.
public const EventKeywords IgnoreShortCutKeywords = (EventKeywords)0x0800;
public const EventKeywords AspNetCoreHosting = (EventKeywords)0x1000;
public const EventKeywords EntityFrameworkCoreCommands = (EventKeywords)0x2000;
};
// Setting AspNetCoreHosting is like having this in the FilterAndPayloadSpecs string
// It turns on basic hosting events.
private readonly string AspNetCoreHostingKeywordValue =
"Microsoft.AspNetCore/Microsoft.AspNetCore.Hosting.BeginRequest@Activity1Start:-" +
"httpContext.Request.Method;" +
"httpContext.Request.Host;" +
"httpContext.Request.Path;" +
"httpContext.Request.QueryString" +
"\n" +
"Microsoft.AspNetCore/Microsoft.AspNetCore.Hosting.EndRequest@Activity1Stop:-" +
"httpContext.TraceIdentifier;" +
"httpContext.Response.StatusCode";
// Setting EntityFrameworkCoreCommands is like having this in the FilterAndPayloadSpecs string
// It turns on basic SQL commands.
private readonly string EntityFrameworkCoreCommandsKeywordValue =
"Microsoft.EntityFrameworkCore/Microsoft.EntityFrameworkCore.BeforeExecuteCommand@Activity2Start:-" +
"Command.Connection.DataSource;" +
"Command.Connection.Database;" +
"Command.CommandText" +
"\n" +
"Microsoft.EntityFrameworkCore/Microsoft.EntityFrameworkCore.AfterExecuteCommand@Activity2Stop:-";
/// <summary>
/// Used to send ad-hoc diagnostics to humans.
/// </summary>
[Event(1, Keywords = Keywords.Messages)]
public void Message(string? Message)
{
WriteEvent(1, Message);
}
/// <summary>
/// Events from DiagnosticSource can be forwarded to EventSource using this event.
/// </summary>
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Arguments parameter is trimmer safe")]
#endif
[Event(2, Keywords = Keywords.Events)]
private void Event(string SourceName, string EventName, IEnumerable<KeyValuePair<string, string?>>? Arguments)
{
WriteEvent(2, SourceName, EventName, Arguments);
}
/// <summary>
/// This is only used on V4.5 systems that don't have the ability to log KeyValuePairs directly.
/// It will eventually go away, but we should always reserve the ID for this.
/// </summary>
[Event(3, Keywords = Keywords.Events)]
private void EventJson(string SourceName, string EventName, string ArgmentsJson)
{
WriteEvent(3, SourceName, EventName, ArgmentsJson);
}
/// <summary>
/// Used to mark the beginning of an activity
/// </summary>
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Arguments parameter is trimmer safe")]
#endif
[Event(4, Keywords = Keywords.Events)]
private void Activity1Start(string SourceName, string EventName, IEnumerable<KeyValuePair<string, string?>> Arguments)
{
WriteEvent(4, SourceName, EventName, Arguments);
}
/// <summary>
/// Used to mark the end of an activity
/// </summary>
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Arguments parameter is trimmer safe")]
#endif
[Event(5, Keywords = Keywords.Events)]
private void Activity1Stop(string SourceName, string EventName, IEnumerable<KeyValuePair<string, string?>> Arguments)
{
WriteEvent(5, SourceName, EventName, Arguments);
}
/// <summary>
/// Used to mark the beginning of an activity
/// </summary>
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Arguments parameter is trimmer safe")]
#endif
[Event(6, Keywords = Keywords.Events)]
private void Activity2Start(string SourceName, string EventName, IEnumerable<KeyValuePair<string, string?>> Arguments)
{
WriteEvent(6, SourceName, EventName, Arguments);
}
/// <summary>
/// Used to mark the end of an activity that can be recursive.
/// </summary>
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Arguments parameter is trimmer safe")]
#endif
[Event(7, Keywords = Keywords.Events)]
private void Activity2Stop(string SourceName, string EventName, IEnumerable<KeyValuePair<string, string?>> Arguments)
{
WriteEvent(7, SourceName, EventName, Arguments);
}
/// <summary>
/// Used to mark the beginning of an activity
/// </summary>
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Arguments parameter is trimmer safe")]
#endif
[Event(8, Keywords = Keywords.Events, ActivityOptions = EventActivityOptions.Recursive)]
private void RecursiveActivity1Start(string SourceName, string EventName, IEnumerable<KeyValuePair<string, string?>> Arguments)
{
WriteEvent(8, SourceName, EventName, Arguments);
}
/// <summary>
/// Used to mark the end of an activity that can be recursive.
/// </summary>
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Arguments parameter is trimmer safe")]
#endif
[Event(9, Keywords = Keywords.Events, ActivityOptions = EventActivityOptions.Recursive)]
private void RecursiveActivity1Stop(string SourceName, string EventName, IEnumerable<KeyValuePair<string, string?>> Arguments)
{
WriteEvent(9, SourceName, EventName, Arguments);
}
/// <summary>
/// Fires when a new DiagnosticSource becomes available.
/// </summary>
/// <param name="SourceName"></param>
[Event(10, Keywords = Keywords.Events)]
private void NewDiagnosticListener(string SourceName)
{
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 !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Arguments parameter is trimmer safe")]
#endif
[Event(11, Keywords = Keywords.Events, ActivityOptions = EventActivityOptions.Recursive)]
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 !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Arguments parameter is trimmer safe")]
#endif
[Event(12, Keywords = Keywords.Events, ActivityOptions = EventActivityOptions.Recursive)]
private void ActivityStop(string SourceName, string ActivityName, IEnumerable<KeyValuePair<string, string?>> Arguments) =>
WriteEvent(12, SourceName, ActivityName, Arguments);
#region private
private DiagnosticSourceEventSource()
// This constructor uses EventSourceSettings which is only available on V4.6 and above
// Use the EventSourceSettings to turn on support for complex types, if available (v4.6 and above).
: base(EventSourceSettings.EtwSelfDescribingEventFormat)
{
}
/// <summary>
/// Called when the EventSource gets a command from a EventListener or ETW.
/// </summary>
[NonEvent]
protected override void OnEventCommand(EventCommandEventArgs command)
{
// On every command (which the debugger can force by turning on this EventSource with ETW)
// call a function that the debugger can hook to do an arbitrary func evaluation.
BreakPointWithDebuggerFuncEval();
lock (this)
{
if ((command.Command == EventCommand.Update || command.Command == EventCommand.Enable) &&
IsEnabled(EventLevel.Informational, Keywords.Events))
{
string? filterAndPayloadSpecs = null;
command.Arguments!.TryGetValue("FilterAndPayloadSpecs", out filterAndPayloadSpecs);
if (!IsEnabled(EventLevel.Informational, Keywords.IgnoreShortCutKeywords))
{
if (IsEnabled(EventLevel.Informational, Keywords.AspNetCoreHosting))
filterAndPayloadSpecs = NewLineSeparate(filterAndPayloadSpecs, AspNetCoreHostingKeywordValue);
if (IsEnabled(EventLevel.Informational, Keywords.EntityFrameworkCoreCommands))
filterAndPayloadSpecs = NewLineSeparate(filterAndPayloadSpecs, EntityFrameworkCoreCommandsKeywordValue);
}
FilterAndTransform.CreateFilterAndTransformList(ref _specs, filterAndPayloadSpecs, this);
}
else if (command.Command == EventCommand.Update || command.Command == EventCommand.Disable)
{
FilterAndTransform.DestroyFilterAndTransformList(ref _specs, this);
}
}
}
// trivial helper to allow you to join two strings the first of which can be null.
private static string NewLineSeparate(string? str1, string str2)
{
Debug.Assert(str2 != null);
if (string.IsNullOrEmpty(str1))
return str2;
return str1 + "\n" + str2;
}
#region debugger hooks
private volatile bool _false; // A value that is always false but the compiler does not know this.
/// <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
/// for the debugger to place a breakpoint where it can inject code with function evaluation
/// </summary>
[NonEvent, MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private void BreakPointWithDebuggerFuncEval()
{
new object(); // This is only here because it helps old .NET Framework runtimes emit a GC safe point at the start of the method
while (_false)
{
_false = false;
}
}
#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).
/// Its main method is 'Morph' which takes a DiagnosticSource object and morphs it into
/// a list of string,string key value pairs.
///
/// This method also contains that static 'Create/Destroy FilterAndTransformList, which
/// simply parse a series of transformation specifications.
/// </summary>
internal sealed class FilterAndTransform
{
/// <summary>
/// Parses filterAndPayloadSpecs which is a list of lines each of which has the from
///
/// DiagnosticSourceName/EventName:PAYLOAD_SPEC
///
/// where PAYLOADSPEC is a semicolon separated list of specifications of the form
///
/// 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
/// (destroying anything that was there previously).
///
/// By default any serializable properties of the payload object are also included
/// in the output payload, however this feature and be tuned off by prefixing the
/// PAYLOADSPEC with a '-'.
/// </summary>
public static void CreateFilterAndTransformList(ref FilterAndTransform? specList, string? filterAndPayloadSpecs, DiagnosticSourceEventSource eventSource)
{
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.
int endIdx = filterAndPayloadSpecs.Length;
while (true)
{
// Skip trailing whitespace.
while (0 < endIdx && char.IsWhiteSpace(filterAndPayloadSpecs[endIdx - 1]))
--endIdx;
int newlineIdx = filterAndPayloadSpecs.LastIndexOf('\n', endIdx - 1, endIdx);
int startIdx = 0;
if (0 <= newlineIdx)
startIdx = newlineIdx + 1; // starts after the newline, or zero if we don't find one.
// Skip leading whitespace
while (startIdx < endIdx && char.IsWhiteSpace(filterAndPayloadSpecs[startIdx]))
startIdx++;
if (IsActivitySourceEntry(filterAndPayloadSpecs, startIdx, endIdx))
{
AddNewActivitySourceTransform(filterAndPayloadSpecs, startIdx, endIdx, eventSource);
}
else
{
specList = new FilterAndTransform(filterAndPayloadSpecs, startIdx, endIdx, eventSource, specList);
}
endIdx = newlineIdx;
if (endIdx < 0)
break;
}
if (eventSource._activitySourceSpecs != null)
{
NormalizeActivitySourceSpecsList(eventSource);
CreateActivityListener(eventSource);
}
}
/// <summary>
/// This destroys (turns off) the FilterAndTransform stopping the forwarding started with CreateFilterAndTransformList
/// </summary>
/// <param name="specList"></param>
/// <param name="eventSource"></param>
public static void DestroyFilterAndTransformList(ref FilterAndTransform? specList, DiagnosticSourceEventSource eventSource)
{
eventSource._activityListener?.Dispose();
eventSource._activityListener = null;
eventSource._activitySourceSpecs = null; // nothing to dispose inside this list.
var curSpec = specList;
specList = null; // Null out the list
while (curSpec != null) // Dispose everything in the list.
{
curSpec.Dispose();
curSpec = curSpec.Next;
}
}
/// <summary>
/// Creates one FilterAndTransform specification from filterAndPayloadSpec starting at 'startIdx' and ending just before 'endIdx'.
/// This FilterAndTransform will subscribe to DiagnosticSources specified by the specification and forward them to 'eventSource.
/// For convenience, the 'Next' field is set to the 'next' parameter, so you can easily form linked lists.
/// </summary>
public FilterAndTransform(string filterAndPayloadSpec, int startIdx, int endIdx, DiagnosticSourceEventSource eventSource, FilterAndTransform? next)
{
Debug.Assert(filterAndPayloadSpec != null && startIdx >= 0 && startIdx <= endIdx && endIdx <= filterAndPayloadSpec.Length);
Next = next;
_eventSource = eventSource;
string? listenerNameFilter = null; // Means WildCard.
string? eventNameFilter = null; // Means WildCard.
string? activityName = null;
var startTransformIdx = startIdx;
var endEventNameIdx = endIdx;
var colonIdx = filterAndPayloadSpec.IndexOf(':', startIdx, endIdx - startIdx);
if (0 <= colonIdx)
{
endEventNameIdx = colonIdx;
startTransformIdx = colonIdx + 1;
}
// Parse the Source/Event name into listenerNameFilter and eventNameFilter
var slashIdx = filterAndPayloadSpec.IndexOf('/', startIdx, endEventNameIdx - startIdx);
if (0 <= slashIdx)
{
listenerNameFilter = filterAndPayloadSpec.Substring(startIdx, slashIdx - startIdx);
var atIdx = filterAndPayloadSpec.IndexOf('@', slashIdx + 1, endEventNameIdx - slashIdx - 1);
if (0 <= atIdx)
{
activityName = filterAndPayloadSpec.Substring(atIdx + 1, endEventNameIdx - atIdx - 1);
eventNameFilter = filterAndPayloadSpec.Substring(slashIdx + 1, atIdx - slashIdx - 1);
}
else
{
eventNameFilter = filterAndPayloadSpec.Substring(slashIdx + 1, endEventNameIdx - slashIdx - 1);
}
}
else if (startIdx < endEventNameIdx)
{
listenerNameFilter = filterAndPayloadSpec.Substring(startIdx, endEventNameIdx - startIdx);
}
_eventSource.Message("DiagnosticSource: Enabling '" + (listenerNameFilter ?? "*") + "/" + (eventNameFilter ?? "*") + "'");
// 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;
}
}
Action<string, string, IEnumerable<KeyValuePair<string, string?>>>? writeEvent = null;
if (activityName != null && activityName.Contains("Activity"))
{
writeEvent = activityName switch
{
nameof(Activity1Start) => _eventSource.Activity1Start,
nameof(Activity1Stop) => _eventSource.Activity1Stop,
nameof(Activity2Start) => _eventSource.Activity2Start,
nameof(Activity2Stop) => _eventSource.Activity2Stop,
nameof(RecursiveActivity1Start) => _eventSource.RecursiveActivity1Start,
nameof(RecursiveActivity1Stop) => _eventSource.RecursiveActivity1Stop,
_ => null
};
if (writeEvent == null)
_eventSource.Message("DiagnosticSource: Could not find Event to log Activity " + activityName);
}
if (writeEvent == null)
{
writeEvent = _eventSource.Event;
}
// Set up a subscription that watches for the given Diagnostic Sources and events which will call back
// to the EventSource.
_diagnosticsListenersSubscription = DiagnosticListener.AllListeners.Subscribe(new CallbackObserver<DiagnosticListener>(delegate (DiagnosticListener newListener)
{
if (listenerNameFilter == null || listenerNameFilter == newListener.Name)
{
_eventSource.NewDiagnosticListener(newListener.Name);
Predicate<string>? eventNameFilterPredicate = null;
if (eventNameFilter != null)
eventNameFilterPredicate = (string eventName) => eventNameFilter == eventName;
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "DiagnosticSource.Write is marked with RequiresUnreferencedCode.")]
void OnEventWritten(KeyValuePair<string, object?> evnt)
{
// The filter given to the DiagnosticSource may not work if users don't is 'IsEnabled' as expected.
// Thus we look for any events that may have snuck through and filter them out before forwarding.
if (eventNameFilter != null && eventNameFilter != evnt.Key)
return;
var outputArgs = this.Morph(evnt.Value);
var eventName = evnt.Key;
writeEvent(newListener.Name, eventName, outputArgs);
}
var subscription = newListener.Subscribe(new CallbackObserver<KeyValuePair<string, object?>>(OnEventWritten), eventNameFilterPredicate);
_liveSubscriptions = new Subscriptions(subscription, _liveSubscriptions);
}
}));
}
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();
}
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 => OnActivityStarted(eventSource, activity);
eventSource._activityListener.ActivityStopped = activity => OnActivityStopped(eventSource, activity);
ActivitySource.AddActivityListener(eventSource._activityListener);
}
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(Activity))]
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(ActivityContext))]
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(ActivityEvent))]
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(ActivityLink))]
[DynamicDependency(nameof(DateTime.Ticks), typeof(DateTime))]
[DynamicDependency(nameof(TimeSpan.Ticks), typeof(TimeSpan))]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Activity's properties are being preserved with the DynamicDependencies on OnActivityStarted.")]
private static void OnActivityStarted(DiagnosticSourceEventSource eventSource, Activity 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;
}
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "Activity's properties are being preserved with the DynamicDependencies on OnActivityStarted.")]
private static void OnActivityStopped(DiagnosticSourceEventSource eventSource, Activity 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;
}
}
// 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);