-
Notifications
You must be signed in to change notification settings - Fork 4.6k
/
EventSource.cs
6245 lines (5694 loc) · 281 KB
/
EventSource.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.
// This program uses code hyperlinks available as part of the HyperAddin Visual Studio plug-in.
// It is available from http://www.codeplex.com/hyperAddin
#if ES_BUILD_STANDALONE
#define FEATURE_MANAGED_ETW_CHANNELS
// #define FEATURE_ADVANCED_MANAGED_ETW_CHANNELS
#endif
/* DESIGN NOTES DESIGN NOTES DESIGN NOTES DESIGN NOTES */
// DESIGN NOTES
// Over the years EventSource has become more complex and so it is important to understand
// the basic structure of the code to insure that it does not grow more complex.
//
// Basic Model
//
// PRINCIPLE: EventSource - ETW decoupling
//
// Conceptually an EventSouce is something that takes event logging data from the source methods
// to the EventListener that can subscribe to them. Note that CONCEPTUALLY EVENTSOURCES DON'T
// KNOW ABOUT ETW!. The MODEL of the system is that there is a special EventListener which
// we will call the EtwEventListener, that forwards commands from ETW to EventSources and
// listens to EventSources and forwards on those events to ETW. Thus the model should
// be that you DON'T NEED ETW.
//
// Now in actual practice, EventSouce have rather intimate knowledge of ETW and send events
// to it directly, but this can be VIEWED AS AN OPTIMIZATION.
//
// Basic Event Data Flow:
//
// There are two ways for event Data to enter the system
// 1) WriteEvent* and friends. This is called the 'contract' based approach because
// you write a method per event which forms a contract that is know at compile time.
// In this scheme each event is given an EVENTID (small integer), which is its identity
// 2) Write<T> methods. This is called the 'dynamic' approach because new events
// can be created on the fly. Event identity is determined by the event NAME, and these
// are not quite as efficient at runtime since you have at least a hash table lookup
// on every event write.
//
// EventSource-EventListener transfer fully supports both ways of writing events (either contract
// based (WriteEvent*) or dynamic (Write<T>)). Both ways fully support the same set of data
// types. It is recommended, however, that you use the contract based approach when the event scheme
// is known at compile time (that is whenever possible). It is more efficient, but more importantly
// it makes the contract very explicit, and centralizes all policy about logging. These are good
// things. The Write<T> API is really meant for more ad-hoc cases.
//
// Allowed Data:
//
// Note that EventSource-EventListeners have a conceptual serialization-deserialization that happens
// during the transfer. In particular object identity is not preserved, some objects are morphed,
// and not all data types are supported. In particular you can pass
//
// A Valid type to log to an EventSource include
// * Primitive data types
// * IEnumerable<T> of valid types T (this include arrays) (* New for V4.6)
// * Explicitly Opted in class or struct with public property Getters over Valid types. (* New for V4.6)
//
// This set of types is roughly a generalization of JSON support (basically primitives, bags, and arrays).
//
// Explicitly allowed structs include (* New for V4.6)
// * Marked with the EventData attribute
// * implicitly defined (e.g the C# new {x = 3, y = 5} syntax)
// * KeyValuePair<K,V> (thus dictionaries can be passed since they are an IEnumerable of KeyValuePair)
//
// When classes are returned in an EventListener, what is returned is something that implements
// IDictionary<string, T>. Thus when objects are passed to an EventSource they are transformed
// into a key-value bag (the IDictionary<string, T>) for consumption in the listener. These
// are obviously NOT the original objects.
//
// ETW serialization formats:
//
// As mentioned, conceptually EventSources send data to EventListeners and there is a conceptual
// copy/morph of that data as described above. In addition the .NET framework supports a conceptual
// ETWListener that will send the data to the ETW stream. If you use this feature, the data needs
// to be serialized in a way that ETW supports. ETW supports the following serialization formats
//
// 1) Manifest Based serialization.
// 2) SelfDescribing serialization (TraceLogging style in the TraceLogging directory)
//
// A key factor is that the Write<T> method, which supports on the fly definition of events, can't
// support the manifest based serialization because the manifest needs the schema of all events
// to be known before any events are emitted. This implies the following:
//
// If you use Write<T> and the output goes to ETW it will use the SelfDescribing format.
// If you use the EventSource(string) constructor for an eventSource (in which you don't
// create a subclass), the default is also to use Self-Describing serialization. In addition
// you can use the EventSoruce(EventSourceSettings) constructor to also explicitly specify
// Self-Describing serialization format. These affect the WriteEvent* APIs going to ETW.
//
// Note that none of this ETW serialization logic affects EventListeners. Only the ETW listener.
//
// *************************************************************************************
// *** INTERNALS: Event Propagation
//
// Data enters the system either though
//
// 1) A user defined method in the user defined subclass of EventSource which calls
// A) A typesafe type specific overload of WriteEvent(ID, ...) e.g. WriteEvent(ID, string, string)
// * which calls into the unsafe WriteEventCore(ID COUNT EventData*) WriteEventWithRelatedActivityIdCore()
// B) The typesafe overload WriteEvent(ID, object[]) which calls the private helper WriteEventVarargs(ID, Guid* object[])
// C) Directly into the unsafe WriteEventCore(ID, COUNT EventData*) or WriteEventWithRelatedActivityIdCore()
//
// All event data eventually flows to one of
// * WriteEventWithRelatedActivityIdCore(ID, Guid*, COUNT, EventData*)
// * WriteEventVarargs(ID, Guid*, object[])
//
// 2) A call to one of the overloads of Write<T>. All these overloads end up in
// * WriteImpl<T>(EventName, Options, Data, Guid*, Guid*)
//
// On output there are the following routines
// Writing to all listeners that are NOT ETW, we have the following routines
// * WriteToAllListeners(ID, Guid*, Guid*, COUNT, EventData*)
// * WriteToAllListeners(ID, Guid*, Guid*, object[])
// * WriteToAllListeners(NAME, Guid*, Guid*, EventPayload)
//
// EventPayload is the internal type that implements the IDictionary<string, object> interface
// The EventListeners will pass back for serialized classes for nested object, but
// WriteToAllListeners(NAME, Guid*, Guid*, EventPayload) unpacks this and uses the fields as if they
// were parameters to a method.
//
// The first two are used for the WriteEvent* case, and the later is used for the Write<T> case.
//
// Writing to ETW, Manifest Based
// EventProvider.WriteEvent(EventDescriptor, Guid*, COUNT, EventData*)
// EventProvider.WriteEvent(EventDescriptor, Guid*, object[])
// Writing to ETW, Self-Describing format
// WriteMultiMerge(NAME, Options, Types, EventData*)
// WriteMultiMerge(NAME, Options, Types, object[])
// WriteImpl<T> has logic that knows how to serialize (like WriteMultiMerge) but also knows
// where it will write it to
//
// All ETW writes eventually call
// EventWriteTransfer
// EventProvider.WriteEventRaw - sets last error
// EventSource.WriteEventRaw - Does EventSource exception handling logic
// WriteMultiMerge
// WriteImpl<T>
// EventProvider.WriteEvent(EventDescriptor, Guid*, COUNT, EventData*)
// EventProvider.WriteEvent(EventDescriptor, Guid*, object[])
//
// Serialization: We have a bit of a hodge-podge of serializers right now. Only the one for ETW knows
// how to deal with nested classes or arrays. I will call this serializer the 'TypeInfo' serializer
// since it is the TraceLoggingTypeInfo structure that knows how to do this. Effectively for a type you
// can call one of these
// WriteMetadata - transforms the type T into serialization meta data blob for that type
// WriteObjectData - transforms an object of T into serialization data blob for that instance
// GetData - transforms an object of T into its deserialized form suitable for passing to EventListener.
// The first two are used to serialize something for ETW. The second one is used to transform the object
// for use by the EventListener. We also have a 'DecodeObject' method that will take a EventData* and
// deserialize to pass to an EventListener, but it only works on primitive types (types supported in version V4.5).
//
// It is an important observation that while EventSource does support users directly calling with EventData*
// blobs, we ONLY support that for the primitive types (V4.5 level support). Thus while there is a EventData*
// path through the system it is only for some types. The object[] path is the more general (but less efficient) path.
//
// TODO There is cleanup needed There should be no divergence until WriteEventRaw.
//
// TODO: We should have a single choke point (right now we always have this parallel EventData* and object[] path. This
// was historical (at one point we tried to pass object directly from EventSoruce to EventListener. That was always
// fragile and a compatibility headache, but we have finally been forced into the idea that there is always a transformation.
// This allows us to use the EventData* form to be the canonical data format in the low level APIs. This also gives us the
// opportunity to expose this format to EventListeners in the future.
//
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
#if ES_BUILD_STANDALONE
using System.Security.Permissions;
#endif
#if ES_BUILD_STANDALONE
namespace Microsoft.Diagnostics.Tracing
{
#else
namespace System.Diagnostics.Tracing
{
[Conditional("NEEDED_FOR_SOURCE_GENERATOR_ONLY")]
[AttributeUsage(AttributeTargets.Class)]
internal sealed class EventSourceAutoGenerateAttribute : Attribute
{
}
#endif
/// <summary>
/// This class is meant to be inherited by a user-defined event source in order to define a managed
/// ETW provider. Please See DESIGN NOTES above for the internal architecture.
/// The minimal definition of an EventSource simply specifies a number of ETW event methods that
/// call one of the EventSource.WriteEvent overloads, <see cref="EventSource.WriteEventCore"/>,
/// or <see cref="EventSource.WriteEventWithRelatedActivityIdCore"/> to log them. This functionality
/// is sufficient for many users.
/// <para>
/// To achieve more control over the ETW provider manifest exposed by the event source type, the
/// [<see cref="EventAttribute"/>] attributes can be specified for the ETW event methods.
/// </para><para>
/// For very advanced EventSources, it is possible to intercept the commands being given to the
/// eventSource and change what filtering is done (see EventListener.EnableEvents and
/// <see cref="EventListener.DisableEvents"/>) or cause actions to be performed by the eventSource,
/// e.g. dumping a data structure (see EventSource.SendCommand and
/// <see cref="EventSource.OnEventCommand"/>).
/// </para><para>
/// The eventSources can be turned on with Windows ETW controllers (e.g. logman), immediately.
/// It is also possible to control and intercept the data dispatcher programmatically. See
/// <see cref="EventListener"/> for more.
/// </para>
/// </summary>
/// <remarks>
/// This is a minimal definition for a custom event source:
/// <code>
/// [EventSource(Name="Samples.Demos.Minimal")]
/// sealed class MinimalEventSource : EventSource
/// {
/// public static MinimalEventSource Log = new MinimalEventSource();
/// public void Load(long ImageBase, string Name) { WriteEvent(1, ImageBase, Name); }
/// public void Unload(long ImageBase) { WriteEvent(2, ImageBase); }
/// private MinimalEventSource() {}
/// }
/// </code>
/// </remarks>
#if !ES_BUILD_STANDALONE
// The EnsureDescriptorsInitialized() method might need to access EventSource and its derived type
// members and the trimmer ensures that these members are preserved.
[DynamicallyAccessedMembers(ManifestMemberTypes)]
#endif
public partial class EventSource : IDisposable
{
internal static bool IsSupported { get; } = InitializeIsSupported();
private static bool InitializeIsSupported() =>
AppContext.TryGetSwitch("System.Diagnostics.Tracing.EventSource.IsSupported", out bool isSupported) ? isSupported : true;
#if FEATURE_EVENTSOURCE_XPLAT
#pragma warning disable CA1823 // field is used to keep listener alive
private static readonly EventListener? persistent_Xplat_Listener = IsSupported ? XplatEventLogger.InitializePersistentListener() : null;
#pragma warning restore CA1823
#endif //FEATURE_EVENTSOURCE_XPLAT
/// <summary>
/// The human-friendly name of the eventSource. It defaults to the simple name of the class
/// </summary>
public string Name => m_name;
/// <summary>
/// Every eventSource is assigned a GUID to uniquely identify it to the system.
/// </summary>
public Guid Guid => m_guid;
/// <summary>
/// Returns true if the eventSource has been enabled at all. This is the preferred test
/// to be performed before a relatively expensive EventSource operation.
/// </summary>
public bool IsEnabled()
{
return m_eventSourceEnabled;
}
/// <summary>
/// Returns true if events with greater than or equal 'level' and have one of 'keywords' set are enabled.
///
/// Note that the result of this function is only an approximation on whether a particular
/// event is active or not. It is only meant to be used as way of avoiding expensive
/// computation for logging when logging is not on, therefore it sometimes returns false
/// positives (but is always accurate when returning false). EventSources are free to
/// have additional filtering.
/// </summary>
public bool IsEnabled(EventLevel level, EventKeywords keywords)
{
return IsEnabled(level, keywords, EventChannel.None);
}
/// <summary>
/// Returns true if events with greater than or equal 'level' and have one of 'keywords' set are enabled, or
/// if 'keywords' specifies a channel bit for a channel that is enabled.
///
/// Note that the result of this function only an approximation on whether a particular
/// event is active or not. It is only meant to be used as way of avoiding expensive
/// computation for logging when logging is not on, therefore it sometimes returns false
/// positives (but is always accurate when returning false). EventSources are free to
/// have additional filtering.
/// </summary>
public bool IsEnabled(EventLevel level, EventKeywords keywords, EventChannel channel)
{
if (!IsEnabled())
return false;
if (!IsEnabledCommon(m_eventSourceEnabled, m_level, m_matchAnyKeyword, level, keywords, channel))
return false;
return true;
}
/// <summary>
/// Returns the settings for the event source instance
/// </summary>
public EventSourceSettings Settings => m_config;
// Manifest support
/// <summary>
/// Returns the GUID that uniquely identifies the eventSource defined by 'eventSourceType'.
/// This API allows you to compute this without actually creating an instance of the EventSource.
/// It only needs to reflect over the type.
/// </summary>
public static Guid GetGuid(Type eventSourceType)
{
if (eventSourceType == null)
throw new ArgumentNullException(nameof(eventSourceType));
EventSourceAttribute? attrib = (EventSourceAttribute?)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute));
string name = eventSourceType.Name;
if (attrib != null)
{
if (attrib.Guid != null)
{
if (Guid.TryParse(attrib.Guid, out Guid g))
return g;
}
if (attrib.Name != null)
name = attrib.Name;
}
if (name == null)
{
throw new ArgumentException(SR.Argument_InvalidTypeName, nameof(eventSourceType));
}
return GenerateGuidFromName(name.ToUpperInvariant()); // Make it case insensitive.
}
/// <summary>
/// Returns the official ETW Provider name for the eventSource defined by 'eventSourceType'.
/// This API allows you to compute this without actually creating an instance of the EventSource.
/// It only needs to reflect over the type.
/// </summary>
public static string GetName(Type eventSourceType)
{
return GetName(eventSourceType, EventManifestOptions.None);
}
#if !ES_BUILD_STANDALONE
private const DynamicallyAccessedMemberTypes ManifestMemberTypes = DynamicallyAccessedMemberTypes.All;
#endif
/// <summary>
/// Returns a string of the XML manifest associated with the eventSourceType. The scheme for this XML is
/// documented at in EventManifest Schema https://docs.microsoft.com/en-us/windows/desktop/WES/eventmanifestschema-schema.
/// This is the preferred way of generating a manifest to be embedded in the ETW stream as it is fast and
/// the fact that it only includes localized entries for the current UI culture is an acceptable tradeoff.
/// </summary>
/// <param name="eventSourceType">The type of the event source class for which the manifest is generated</param>
/// <param name="assemblyPathToIncludeInManifest">The manifest XML fragment contains the string name of the DLL name in
/// which it is embedded. This parameter specifies what name will be used</param>
/// <returns>The XML data string</returns>
public static string? GenerateManifest(
#if !ES_BUILD_STANDALONE
[DynamicallyAccessedMembers(ManifestMemberTypes)]
#endif
Type eventSourceType,
string? assemblyPathToIncludeInManifest)
{
return GenerateManifest(eventSourceType, assemblyPathToIncludeInManifest, EventManifestOptions.None);
}
/// <summary>
/// Returns a string of the XML manifest associated with the eventSourceType. The scheme for this XML is
/// documented at in EventManifest Schema https://docs.microsoft.com/en-us/windows/desktop/WES/eventmanifestschema-schema.
/// Pass EventManifestOptions.AllCultures when generating a manifest to be registered on the machine. This
/// ensures that the entries in the event log will be "optimally" localized.
/// </summary>
/// <param name="eventSourceType">The type of the event source class for which the manifest is generated</param>
/// <param name="assemblyPathToIncludeInManifest">The manifest XML fragment contains the string name of the DLL name in
/// which it is embedded. This parameter specifies what name will be used</param>
/// <param name="flags">The flags to customize manifest generation. If flags has bit OnlyIfNeededForRegistration specified
/// this returns null when the eventSourceType does not require explicit registration</param>
/// <returns>The XML data string or null</returns>
public static string? GenerateManifest(
#if !ES_BUILD_STANDALONE
[DynamicallyAccessedMembers(ManifestMemberTypes)]
#endif
Type eventSourceType,
string? assemblyPathToIncludeInManifest,
EventManifestOptions flags)
{
if (!IsSupported)
{
return null;
}
if (eventSourceType == null)
throw new ArgumentNullException(nameof(eventSourceType));
byte[]? manifestBytes = EventSource.CreateManifestAndDescriptors(eventSourceType, assemblyPathToIncludeInManifest, null, flags);
return (manifestBytes == null) ? null : Encoding.UTF8.GetString(manifestBytes, 0, manifestBytes.Length);
}
// EventListener support
/// <summary>
/// returns a list (IEnumerable) of all sources in the appdomain). EventListeners typically need this.
/// </summary>
/// <returns></returns>
public static IEnumerable<EventSource> GetSources()
{
if (!IsSupported)
{
return Array.Empty<EventSource>();
}
var ret = new List<EventSource>();
lock (EventListener.EventListenersLock)
{
Debug.Assert(EventListener.s_EventSources != null);
foreach (WeakReference<EventSource> eventSourceRef in EventListener.s_EventSources)
{
if (eventSourceRef.TryGetTarget(out EventSource? eventSource) && !eventSource.IsDisposed)
ret.Add(eventSource);
}
}
return ret;
}
/// <summary>
/// Send a command to a particular EventSource identified by 'eventSource'.
/// Calling this routine simply forwards the command to the EventSource.OnEventCommand
/// callback. What the EventSource does with the command and its arguments are from
/// that point EventSource-specific.
/// </summary>
/// <param name="eventSource">The instance of EventSource to send the command to</param>
/// <param name="command">A positive user-defined EventCommand, or EventCommand.SendManifest</param>
/// <param name="commandArguments">A set of (name-argument, value-argument) pairs associated with the command</param>
public static void SendCommand(EventSource eventSource, EventCommand command, IDictionary<string, string?>? commandArguments)
{
if (!IsSupported)
{
return;
}
if (eventSource == null)
throw new ArgumentNullException(nameof(eventSource));
// User-defined EventCommands should not conflict with the reserved commands.
if ((int)command <= (int)EventCommand.Update && (int)command != (int)EventCommand.SendManifest)
{
throw new ArgumentException(SR.EventSource_InvalidCommand, nameof(command));
}
eventSource.SendCommand(null, EventProviderType.ETW, 0, 0, command, true, EventLevel.LogAlways, EventKeywords.None, commandArguments);
}
// Error APIs. (We don't throw by default, but you can probe for status)
/// <summary>
/// Because
///
/// 1) Logging is often optional and thus should not generate fatal errors (exceptions)
/// 2) EventSources are often initialized in class constructors (which propagate exceptions poorly)
///
/// The event source constructor does not throw exceptions. Instead we remember any exception that
/// was generated (it is also logged to Trace.WriteLine).
/// </summary>
public Exception? ConstructionException => m_constructionException;
/// <summary>
/// EventSources can have arbitrary string key-value pairs associated with them called Traits.
/// These traits are not interpreted by the EventSource but may be interpreted by EventListeners
/// (e.g. like the built in ETW listener). These traits are specified at EventSource
/// construction time and can be retrieved by using this GetTrait API.
/// </summary>
/// <param name="key">The key to look up in the set of key-value pairs passed to the EventSource constructor</param>
/// <returns>The value string associated with key. Will return null if there is no such key.</returns>
public string? GetTrait(string key)
{
if (m_traits != null)
{
for (int i = 0; i < m_traits.Length - 1; i += 2)
{
if (m_traits[i] == key)
return m_traits[i + 1];
}
}
return null;
}
/// <summary>
/// Displays the name and GUID for the eventSource for debugging purposes.
/// </summary>
public override string ToString()
{
return SR.Format(SR.EventSource_ToString, Name, Guid);
}
/// <summary>
/// Fires when a Command (e.g. Enable) comes from a an EventListener.
/// </summary>
public event EventHandler<EventCommandEventArgs>? EventCommandExecuted
{
add
{
if (value == null)
return;
m_eventCommandExecuted += value;
// If we have an EventHandler<EventCommandEventArgs> attached to the EventSource before the first command arrives
// It should get a chance to handle the deferred commands.
EventCommandEventArgs? deferredCommands = m_deferredCommands;
while (deferredCommands != null)
{
value(this, deferredCommands);
deferredCommands = deferredCommands.nextCommand;
}
}
remove
{
m_eventCommandExecuted -= value;
}
}
#region ActivityID
/// <summary>
/// When a thread starts work that is on behalf of 'something else' (typically another
/// thread or network request) it should mark the thread as working on that other work.
/// This API marks the current thread as working on activity 'activityID'. This API
/// should be used when the caller knows the thread's current activity (the one being
/// overwritten) has completed. Otherwise, callers should prefer the overload that
/// return the oldActivityThatWillContinue (below).
///
/// All events created with the EventSource on this thread are also tagged with the
/// activity ID of the thread.
///
/// It is common, and good practice after setting the thread to an activity to log an event
/// with a 'start' opcode to indicate that precise time/thread where the new activity
/// started.
/// </summary>
/// <param name="activityId">A Guid that represents the new activity with which to mark
/// the current thread</param>
public static void SetCurrentThreadActivityId(Guid activityId)
{
if (!IsSupported)
{
return;
}
if (TplEventSource.Log != null)
TplEventSource.Log.SetActivityId(activityId);
// We ignore errors to keep with the convention that EventSources do not throw errors.
// Note we can't access m_throwOnWrites because this is a static method.
#if FEATURE_MANAGED_ETW
#if FEATURE_PERFTRACING
// Set the activity id via EventPipe.
EventPipeEventProvider.EventActivityIdControl(
Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_SET_ID,
ref activityId);
#endif // FEATURE_PERFTRACING
#if TARGET_WINDOWS
// Set the activity id via ETW.
Interop.Advapi32.EventActivityIdControl(
Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_SET_ID,
ref activityId);
#endif // TARGET_WINDOWS
#endif // FEATURE_MANAGED_ETW
}
/// <summary>
/// Retrieves the ETW activity ID associated with the current thread.
/// </summary>
public static Guid CurrentThreadActivityId
{
get
{
if (!IsSupported)
{
return default;
}
// We ignore errors to keep with the convention that EventSources do not throw
// errors. Note we can't access m_throwOnWrites because this is a static method.
Guid retVal = default;
#if FEATURE_MANAGED_ETW
#if TARGET_WINDOWS
Interop.Advapi32.EventActivityIdControl(
Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_ID,
ref retVal);
#elif FEATURE_PERFTRACING
EventPipeEventProvider.EventActivityIdControl(
Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_ID,
ref retVal);
#endif // TARGET_WINDOWS
#endif // FEATURE_MANAGED_ETW
return retVal;
}
}
/// <summary>
/// When a thread starts work that is on behalf of 'something else' (typically another
/// thread or network request) it should mark the thread as working on that other work.
/// This API marks the current thread as working on activity 'activityID'. It returns
/// whatever activity the thread was previously marked with. There is a convention that
/// callers can assume that callees restore this activity mark before the callee returns.
/// To encourage this, this API returns the old activity, so that it can be restored later.
///
/// All events created with the EventSource on this thread are also tagged with the
/// activity ID of the thread.
///
/// It is common, and good practice after setting the thread to an activity to log an event
/// with a 'start' opcode to indicate that precise time/thread where the new activity
/// started.
/// </summary>
/// <param name="activityId">A Guid that represents the new activity with which to mark
/// the current thread</param>
/// <param name="oldActivityThatWillContinue">The Guid that represents the current activity
/// which will continue at some point in the future, on the current thread</param>
public static void SetCurrentThreadActivityId(Guid activityId, out Guid oldActivityThatWillContinue)
{
if (!IsSupported)
{
oldActivityThatWillContinue = default;
return;
}
oldActivityThatWillContinue = activityId;
#if FEATURE_MANAGED_ETW
// We ignore errors to keep with the convention that EventSources do not throw errors.
// Note we can't access m_throwOnWrites because this is a static method.
#if FEATURE_PERFTRACING && TARGET_WINDOWS
EventPipeEventProvider.EventActivityIdControl(
Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_SET_ID,
ref oldActivityThatWillContinue);
#elif FEATURE_PERFTRACING
EventPipeEventProvider.EventActivityIdControl(
Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID,
ref oldActivityThatWillContinue);
#endif // FEATURE_PERFTRACING && TARGET_WINDOWS
#if TARGET_WINDOWS
Interop.Advapi32.EventActivityIdControl(
Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID,
ref oldActivityThatWillContinue);
#endif // TARGET_WINDOWS
#endif // FEATURE_MANAGED_ETW
// We don't call the activityDying callback here because the caller has declared that
// it is not dying.
if (TplEventSource.Log != null)
TplEventSource.Log.SetActivityId(activityId);
}
#endregion
#region protected
/// <summary>
/// This is the constructor that most users will use to create their eventSource. It takes
/// no parameters. The ETW provider name and GUID of the EventSource are determined by the EventSource
/// custom attribute (so you can determine these things declaratively). If the GUID for the eventSource
/// is not specified in the EventSourceAttribute (recommended), it is Generated by hashing the name.
/// If the ETW provider name of the EventSource is not given, the name of the EventSource class is used as
/// the ETW provider name.
/// </summary>
protected EventSource()
: this(EventSourceSettings.EtwManifestEventFormat)
{
}
/// <summary>
/// By default calling the 'WriteEvent' methods do NOT throw on errors (they silently discard the event).
/// This is because in most cases users assume logging is not 'precious' and do NOT wish to have logging failures
/// crash the program. However for those applications where logging is 'precious' and if it fails the caller
/// wishes to react, setting 'throwOnEventWriteErrors' will cause an exception to be thrown if WriteEvent
/// fails. Note the fact that EventWrite succeeds does not necessarily mean that the event reached its destination
/// only that operation of writing it did not fail. These EventSources will not generate self-describing ETW events.
///
/// For compatibility only use the EventSourceSettings.ThrowOnEventWriteErrors flag instead.
/// </summary>
// [Obsolete("Use the EventSource(EventSourceSettings) overload")]
protected EventSource(bool throwOnEventWriteErrors)
: this(EventSourceSettings.EtwManifestEventFormat | (throwOnEventWriteErrors ? EventSourceSettings.ThrowOnEventWriteErrors : 0))
{ }
/// <summary>
/// Construct an EventSource with additional non-default settings (see EventSourceSettings for more)
/// </summary>
protected EventSource(EventSourceSettings settings) : this(settings, null) { }
/// <summary>
/// Construct an EventSource with additional non-default settings.
///
/// Also specify a list of key-value pairs called traits (you must pass an even number of strings).
/// The first string is the key and the second is the value. These are not interpreted by EventSource
/// itself but may be interpreted the listeners. Can be fetched with GetTrait(string).
/// </summary>
/// <param name="settings">See EventSourceSettings for more.</param>
/// <param name="traits">A collection of key-value strings (must be an even number).</param>
protected EventSource(EventSourceSettings settings, params string[]? traits)
{
if (IsSupported)
{
#if FEATURE_PERFTRACING
m_eventHandleTable = new TraceLoggingEventHandleTable();
#endif
m_config = ValidateSettings(settings);
Type myType = this.GetType();
Guid eventSourceGuid = GetGuid(myType);
string eventSourceName = GetName(myType);
Initialize(eventSourceGuid, eventSourceName, traits);
}
}
#if FEATURE_PERFTRACING
// Generate the serialized blobs that describe events for all strongly typed events (that is events that define strongly
// typed event methods. Dynamically defined events (that use Write) hare defined on the fly and are handled elsewhere.
private unsafe void DefineEventPipeEvents()
{
// If the EventSource is set to emit all events as TraceLogging events, skip this initialization.
// Events will be defined when they are emitted for the first time.
if (SelfDescribingEvents)
{
return;
}
Debug.Assert(m_eventData != null);
Debug.Assert(m_eventPipeProvider != null);
int cnt = m_eventData.Length;
for (int i = 0; i < cnt; i++)
{
uint eventID = (uint)m_eventData[i].Descriptor.EventId;
if (eventID == 0)
continue;
byte[]? metadata = EventPipeMetadataGenerator.Instance.GenerateEventMetadata(m_eventData[i]);
uint metadataLength = (metadata != null) ? (uint)metadata.Length : 0;
string eventName = m_eventData[i].Name;
long keywords = m_eventData[i].Descriptor.Keywords;
uint eventVersion = m_eventData[i].Descriptor.Version;
uint level = m_eventData[i].Descriptor.Level;
fixed (byte *pMetadata = metadata)
{
IntPtr eventHandle = m_eventPipeProvider.m_eventProvider.DefineEventHandle(
eventID,
eventName,
keywords,
eventVersion,
level,
pMetadata,
metadataLength);
Debug.Assert(eventHandle != IntPtr.Zero);
m_eventData[i].EventHandle = eventHandle;
}
}
}
#endif
/// <summary>
/// This method is called when the eventSource is updated by the controller.
/// </summary>
protected virtual void OnEventCommand(EventCommandEventArgs command) { }
#pragma warning disable 1591
// optimized for common signatures (no args)
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = EventSourceSuppressMessage)]
#endif
protected unsafe void WriteEvent(int eventId)
{
WriteEventCore(eventId, 0, null);
}
// optimized for common signatures (ints)
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = EventSourceSuppressMessage)]
#endif
protected unsafe void WriteEvent(int eventId, int arg1)
{
if (IsEnabled())
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[1];
descrs[0].DataPointer = (IntPtr)(&arg1);
descrs[0].Size = 4;
descrs[0].Reserved = 0;
WriteEventCore(eventId, 1, descrs);
}
}
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = EventSourceSuppressMessage)]
#endif
protected unsafe void WriteEvent(int eventId, int arg1, int arg2)
{
if (IsEnabled())
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
descrs[0].DataPointer = (IntPtr)(&arg1);
descrs[0].Size = 4;
descrs[0].Reserved = 0;
descrs[1].DataPointer = (IntPtr)(&arg2);
descrs[1].Size = 4;
descrs[1].Reserved = 0;
WriteEventCore(eventId, 2, descrs);
}
}
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = EventSourceSuppressMessage)]
#endif
protected unsafe void WriteEvent(int eventId, int arg1, int arg2, int arg3)
{
if (IsEnabled())
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
descrs[0].DataPointer = (IntPtr)(&arg1);
descrs[0].Size = 4;
descrs[0].Reserved = 0;
descrs[1].DataPointer = (IntPtr)(&arg2);
descrs[1].Size = 4;
descrs[1].Reserved = 0;
descrs[2].DataPointer = (IntPtr)(&arg3);
descrs[2].Size = 4;
descrs[2].Reserved = 0;
WriteEventCore(eventId, 3, descrs);
}
}
// optimized for common signatures (longs)
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = EventSourceSuppressMessage)]
#endif
protected unsafe void WriteEvent(int eventId, long arg1)
{
if (IsEnabled())
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[1];
descrs[0].DataPointer = (IntPtr)(&arg1);
descrs[0].Size = 8;
descrs[0].Reserved = 0;
WriteEventCore(eventId, 1, descrs);
}
}
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = EventSourceSuppressMessage)]
#endif
protected unsafe void WriteEvent(int eventId, long arg1, long arg2)
{
if (IsEnabled())
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
descrs[0].DataPointer = (IntPtr)(&arg1);
descrs[0].Size = 8;
descrs[0].Reserved = 0;
descrs[1].DataPointer = (IntPtr)(&arg2);
descrs[1].Size = 8;
descrs[1].Reserved = 0;
WriteEventCore(eventId, 2, descrs);
}
}
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = EventSourceSuppressMessage)]
#endif
protected unsafe void WriteEvent(int eventId, long arg1, long arg2, long arg3)
{
if (IsEnabled())
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
descrs[0].DataPointer = (IntPtr)(&arg1);
descrs[0].Size = 8;
descrs[0].Reserved = 0;
descrs[1].DataPointer = (IntPtr)(&arg2);
descrs[1].Size = 8;
descrs[1].Reserved = 0;
descrs[2].DataPointer = (IntPtr)(&arg3);
descrs[2].Size = 8;
descrs[2].Reserved = 0;
WriteEventCore(eventId, 3, descrs);
}
}
// optimized for common signatures (strings)
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = EventSourceSuppressMessage)]
#endif
protected unsafe void WriteEvent(int eventId, string? arg1)
{
if (IsEnabled())
{
arg1 ??= "";
fixed (char* string1Bytes = arg1)
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[1];
descrs[0].DataPointer = (IntPtr)string1Bytes;
descrs[0].Size = ((arg1.Length + 1) * 2);
descrs[0].Reserved = 0;
WriteEventCore(eventId, 1, descrs);
}
}
}
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = EventSourceSuppressMessage)]
#endif
protected unsafe void WriteEvent(int eventId, string? arg1, string? arg2)
{
if (IsEnabled())
{
arg1 ??= "";
arg2 ??= "";
fixed (char* string1Bytes = arg1)
fixed (char* string2Bytes = arg2)
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
descrs[0].DataPointer = (IntPtr)string1Bytes;
descrs[0].Size = ((arg1.Length + 1) * 2);
descrs[0].Reserved = 0;
descrs[1].DataPointer = (IntPtr)string2Bytes;
descrs[1].Size = ((arg2.Length + 1) * 2);
descrs[1].Reserved = 0;
WriteEventCore(eventId, 2, descrs);
}
}
}
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = EventSourceSuppressMessage)]
#endif
protected unsafe void WriteEvent(int eventId, string? arg1, string? arg2, string? arg3)
{
if (IsEnabled())
{
arg1 ??= "";
arg2 ??= "";
arg3 ??= "";
fixed (char* string1Bytes = arg1)
fixed (char* string2Bytes = arg2)
fixed (char* string3Bytes = arg3)
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
descrs[0].DataPointer = (IntPtr)string1Bytes;
descrs[0].Size = ((arg1.Length + 1) * 2);
descrs[0].Reserved = 0;
descrs[1].DataPointer = (IntPtr)string2Bytes;
descrs[1].Size = ((arg2.Length + 1) * 2);
descrs[1].Reserved = 0;
descrs[2].DataPointer = (IntPtr)string3Bytes;
descrs[2].Size = ((arg3.Length + 1) * 2);
descrs[2].Reserved = 0;
WriteEventCore(eventId, 3, descrs);
}
}
}
// optimized for common signatures (string and ints)
#if !ES_BUILD_STANDALONE
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern",
Justification = EventSourceSuppressMessage)]
#endif
protected unsafe void WriteEvent(int eventId, string? arg1, int arg2)
{
if (IsEnabled())
{
arg1 ??= "";
fixed (char* string1Bytes = arg1)
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
descrs[0].DataPointer = (IntPtr)string1Bytes;
descrs[0].Size = ((arg1.Length + 1) * 2);
descrs[0].Reserved = 0;
descrs[1].DataPointer = (IntPtr)(&arg2);
descrs[1].Size = 4;
descrs[1].Reserved = 0;
WriteEventCore(eventId, 2, descrs);
}
}
}
#if !ES_BUILD_STANDALONE