Skip to content

Commit 3e26d4d

Browse files
author
John Salem
authored
Update and improve EventSource documentation (dotnet#7093)
1 parent 47edec8 commit 3e26d4d

File tree

7 files changed

+279
-23
lines changed

7 files changed

+279
-23
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp5.0</TargetFramework>
6+
</PropertyGroup>
7+
8+
</Project>

samples/snippets/csharp/VS_Snippets_CLR/etwtrace/cs/program.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ namespace Demo2
77
enum MyColor { Red, Yellow, Blue };
88

99
[EventSource(Name = "MyCompany")]
10-
class MyCompanyEventSource : EventSource
10+
sealed class MyCompanyEventSource : EventSource
1111
{
1212
//<Snippet2>
1313
//<Snippet3>
14-
public class Keywords
14+
public static class Keywords
1515
{
1616
public const EventKeywords Page = (EventKeywords)1;
1717
public const EventKeywords DataBase = (EventKeywords)2;
@@ -21,7 +21,7 @@ public class Keywords
2121
//</Snippet3>
2222

2323
//<Snippet4>
24-
public class Tasks
24+
public static class Tasks
2525
{
2626
public const EventTask Page = (EventTask)1;
2727
public const EventTask DBQuery = (EventTask)2;
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
//<root>
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Diagnostics.Tracing;
5+
6+
namespace Demo
7+
{
8+
//<InterfaceSource>
9+
public interface IMyLogging
10+
{
11+
void Error(int errorCode, string message);
12+
void Warning(string message);
13+
}
14+
15+
public sealed class MySource : EventSource, IMyLogging
16+
{
17+
public static MySource Log = new();
18+
19+
[Event(1)]
20+
public void Error(int errorCode, string message) => WriteEvent(1, errorCode, message);
21+
22+
[Event(2)]
23+
public void Warning(string message) => WriteEvent(2, message);
24+
}
25+
//</InterfaceSource>
26+
27+
//<UtilitySource>
28+
public abstract class UtilBaseEventSource : EventSource
29+
{
30+
protected UtilBaseEventSource()
31+
: base()
32+
{ }
33+
34+
protected UtilBaseEventSource(bool throwOnEventWriteErrors)
35+
: base(throwOnEventWriteErrors)
36+
{ }
37+
38+
// helper overload of WriteEvent for optimizing writing an event containing
39+
// payload properties that don't align with a provided overload. This prevents
40+
// EventSource from using the object[] overload which is expensive.
41+
protected unsafe void WriteEvent(int eventId, int arg1, short arg2, long arg3)
42+
{
43+
if (IsEnabled())
44+
{
45+
EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
46+
descrs[0] = new EventData { DataPointer = (IntPtr)(&arg1), Size = 4 };
47+
descrs[1] = new EventData { DataPointer = (IntPtr)(&arg2), Size = 2 };
48+
descrs[2] = new EventData { DataPointer = (IntPtr)(&arg3), Size = 8 };
49+
WriteEventCore(eventId, 3, descrs);
50+
}
51+
}
52+
}
53+
54+
public sealed class OptimizedEventSource : UtilBaseEventSource
55+
{
56+
public static OptimizedEventSource Log = new();
57+
58+
public static class Keywords
59+
{
60+
public const EventKeywords Kwd1 = (EventKeywords)1;
61+
}
62+
63+
[Event(1, Keywords = Keywords.Kwd1, Level = EventLevel.Informational, Message = "LogElements called {0}/{1}/{2}.")]
64+
public void LogElements(int n, short sh, long l) => WriteEvent(1, n, sh, l); // uses the overload we added!
65+
}
66+
//</UtilitySource>
67+
68+
//<ComplexSource>
69+
public class ComplexComponent : IDisposable
70+
{
71+
internal static Dictionary<string,string> _internalState = new();
72+
73+
private string _name;
74+
75+
public ComplexComponent(string name)
76+
{
77+
_name = name ?? throw new ArgumentNullException(nameof(name));
78+
ComplexSource.Log.NewComponent(_name);
79+
}
80+
81+
public void SetState(string key, string value)
82+
{
83+
lock (_internalState)
84+
{
85+
_internalState[key] = value;
86+
ComplexSource.Log.SetState(_name, key, value);
87+
}
88+
}
89+
90+
private void ExpensiveWork1() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250));
91+
private void ExpensiveWork2() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250));
92+
private void ExpensiveWork3() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250));
93+
private void ExpensiveWork4() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250));
94+
95+
public void DoWork()
96+
{
97+
ComplexSource.Log.ExpensiveWorkStart(_name);
98+
99+
ExpensiveWork1();
100+
ExpensiveWork2();
101+
ExpensiveWork3();
102+
ExpensiveWork4();
103+
104+
ComplexSource.Log.ExpensiveWorkStop(_name);
105+
}
106+
107+
public void Dispose()
108+
{
109+
ComplexSource.Log.ComponentDisposed(_name);
110+
}
111+
}
112+
113+
internal sealed class ComplexSource : EventSource
114+
{
115+
public static ComplexSource Log = new();
116+
117+
public static class Keywords
118+
{
119+
public const EventKeywords ComponentLifespan = (EventKeywords)1;
120+
public const EventKeywords StateChanges = (EventKeywords)(1 << 1);
121+
public const EventKeywords Performance = (EventKeywords)(1 << 2);
122+
public const EventKeywords DumpState = (EventKeywords)(1 << 3);
123+
// a utility keyword for a common combination of keywords users might enable
124+
public const EventKeywords StateTracking = ComponentLifespan & StateChanges & DumpState;
125+
}
126+
127+
protected override void OnEventCommand(EventCommandEventArgs args)
128+
{
129+
base.OnEventCommand(args);
130+
131+
if (args.Command == EventCommand.Enable)
132+
{
133+
DumpComponentState();
134+
}
135+
}
136+
137+
[Event(1, Keywords = Keywords.ComponentLifespan, Message = "New component with name '{0}'.")]
138+
public void NewComponent(string name) => WriteEvent(1, name);
139+
140+
[Event(2, Keywords = Keywords.ComponentLifespan, Message = "Component with name '{0}' disposed.")]
141+
public void ComponentDisposed(string name) => WriteEvent(2, name);
142+
143+
[Event(3, Keywords = Keywords.StateChanges)]
144+
public void SetState(string name, string key, string value) => WriteEvent(3, name, key, value);
145+
146+
[Event(4, Keywords = Keywords.Performance)]
147+
public void ExpensiveWorkStart(string name) => WriteEvent(4, name);
148+
149+
[Event(5, Keywords = Keywords.Performance)]
150+
public void ExpensiveWorkStop(string name) => WriteEvent(5, name);
151+
152+
[Event(6, Keywords = Keywords.DumpState)]
153+
public void ComponentState(string key, string value) => WriteEvent(6, key, value);
154+
155+
[NonEvent]
156+
public void DumpComponentState()
157+
{
158+
if (IsEnabled(EventLevel.Informational, Keywords.DumpState))
159+
{
160+
lock (ComplexComponent._internalState)
161+
{
162+
foreach (var (key, value) in ComplexComponent._internalState)
163+
ComponentState(key, value);
164+
}
165+
}
166+
}
167+
}
168+
//</ComplexSource>
169+
170+
//<Main>
171+
class Program
172+
{
173+
static void Main(string[] args)
174+
{
175+
Console.WriteLine($"PID: {System.Diagnostics.Process.GetCurrentProcess().Id}");
176+
177+
long i = 0;
178+
while (true)
179+
{
180+
using ComplexComponent c1 = new($"COMPONENT_{i++}");
181+
using ComplexComponent c2 = new($"COMPONENT_{i++}");
182+
using ComplexComponent c3 = new($"COMPONENT_{i++}");
183+
using ComplexComponent c4 = new($"COMPONENT_{i++}");
184+
185+
c1.SetState("key1", "value1");
186+
c2.SetState("key2", "value2");
187+
c3.SetState("key3", "value3");
188+
c4.SetState("key4", "value4");
189+
190+
c1.DoWork();
191+
c2.DoWork();
192+
c3.DoWork();
193+
c4.DoWork();
194+
}
195+
}
196+
}
197+
//</Main>
198+
}
199+
//</root>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp5.0</TargetFramework>
6+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
7+
</PropertyGroup>
8+
9+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>netcoreapp5.0</TargetFramework>
6+
</PropertyGroup>
7+
8+
</Project>

samples/snippets/csharp/VS_Snippets_CLR/etwtracesmall/cs/program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Demo1
66
{
77
//<Snippet2>
8-
class MyCompanyEventSource : EventSource
8+
sealed class MyCompanyEventSource : EventSource
99
{
1010
public static MyCompanyEventSource Log = new MyCompanyEventSource();
1111

xml/System.Diagnostics.Tracing/EventSource.xml

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -49,29 +49,39 @@
4949
</Attribute>
5050
</Attributes>
5151
<Docs>
52-
<summary>Provides the ability to create events for event tracing for Windows (ETW).</summary>
52+
<summary>Provides the ability to create events for event tracing across platforms.</summary>
5353
<remarks>
5454
<format type="text/markdown"><![CDATA[
5555
5656
## Remarks
57-
This class is intended to be inherited by a user class that provides specific events to be used for ETW. The <xref:System.Diagnostics.Tracing.EventSource.WriteEvent%2A?displayProperty=nameWithType> methods are called to log the events.
57+
This class is intended to be inherited by a user class that provides specific events to be used for event tracing. The <xref:System.Diagnostics.Tracing.EventSource.WriteEvent%2A?displayProperty=nameWithType> methods are called to log the events.
5858
59-
> [!IMPORTANT]
60-
> This type implements the <xref:System.IDisposable> interface. When you have finished using the type, you should dispose of it either directly or indirectly. To dispose of the type directly, call its <xref:System.IDisposable.Dispose%2A> method in a `try`/`catch` block. To dispose of it indirectly, use a language construct such as `using` (in C#) or `Using` (in Visual Basic). For more information, see the "Using an Object that Implements IDisposable" section in the <xref:System.IDisposable> interface topic.
61-
62-
The basic functionality of <xref:System.Diagnostics.Tracing.EventSource> is sufficient for most applications. If you want more control over the ETW manifest that is created, you can apply the <xref:System.Diagnostics.Tracing.EventAttribute> attribute to the methods. For advanced event source applications, it is possible to intercept the commands being sent to the derived event source and change the filtering, or to cause actions (such as dumping a data structure) to be performed by the inheritor. An event source can be activated with Windows ETW controllers, such as the Logman tool, immediately. It is also possible to programmatically control and intercept the data dispatcher. The <xref:System.Diagnostics.Tracing.EventListener> class provides additional functionality.
63-
64-
Starting with .NET Framework 4.6, <xref:System.Diagnostics.Tracing.EventSource> provides channel support and some of the event source validation rules have been relaxed. This means:
65-
66-
- <xref:System.Diagnostics.Tracing.EventSource> types may now implement interfaces. This enables the use of event source types in advanced logging systems that use interfaces to define a common logging target.
67-
68-
- The concept of a utility event source type has been introduced. This feature enables sharing code across multiple event source types in a project to enable scenarios such as optimized <xref:System.Diagnostics.Tracing.EventSource.WriteEvent%2A> overloads.
69-
70-
For a version of the <xref:System.Diagnostics.Tracing.EventSource> class that provides features such as channel support you are targeting .NET Framework 4.5.1 or earlier, see [Microsoft EventSource Library 1.0.16](https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.EventSource).
71-
72-
73-
74-
## Examples
59+
The basic functionality of <xref:System.Diagnostics.Tracing.EventSource> is sufficient for most applications. If you want more control over the event metadata that's created, you can apply the <xref:System.Diagnostics.Tracing.EventAttribute> attribute to the methods. For advanced event source applications, it is possible to intercept the commands being sent to the derived event source and change the filtering, or to cause actions (such as dumping a data structure) to be performed by the inheritor. An event source can be activated in-process using <xref:System.Diagnostics.Tracing.EventListener> and out-of-process using EventPipe-based tools such as `dotnet-trace` or Event Tracing for Windows (ETW) based tools like `PerfView` or `Logman`. It is also possible to programmatically control and intercept the data dispatcher. The <xref:System.Diagnostics.Tracing.EventListener> class provides additional functionality.
60+
61+
### Conventions
62+
<xref:System.Diagnostics.Tracing.EventSource>-derived classes should follow the following conventions:
63+
64+
- User-defined classes should implement a singleton pattern. The singleton instance is traditionally named `Log`. By extension, users should not call `IDisposable.Dispose` manually and allow the runtime to clean up the singleton instance at the end of managed code execution.
65+
- A user-defined, derived class should be marked as `sealed` unless it implements the advanced "Utility EventSource" configuration discussed in the Advanced Usage section.
66+
- Call <xref:System.Diagnostics.Tracing.EventSource.IsEnabled> before performing any resource intensive work related to firing an event.
67+
- You can implicitly create <xref:System.Diagnostics.Tracing.EventTask> objects by declaring two event methods with subsequent event IDs that have the naming pattern `<EventName>Start` and `<EventName>Stop`. These events _must_ be declared next to each other in the class definition and the `<EventName>Start` method _must_ come first.
68+
- Attempt to keep <xref:System.Diagnostics.Tracing.EventSource> objects backwards compatible and version them appropriately. The default version for an event is `0`. The version can be can be changed by setting <xref:System.Diagnostics.Tracing.EventAttribute.Version>. Change the version of an event whenever you change properties of the payload. Always add new payload properties to the end of the event declaration. If this is not possible, create a new event with a new ID to replace the old one.
69+
- When declaring event methods, specify fixed-size payload properties before variably sized properties.
70+
- <xref:System.Diagnostics.Tracing.EventKeywords> are used as a bit mask for specifying specific events when subscribing to a provider. You can specify keywords by defining a `public static class Keywords` member class that has `public const EventKeywords` members.
71+
- Associate expensive events with an <xref:System.Diagnostics.Tracing.EventKeywords> using <xref:System.Diagnostics.Tracing.EventAttribute>. This pattern allows users of your <xref:System.Diagnostics.Tracing.EventSource> to opt out of these expensive operations.
72+
73+
### Self-describing (tracelogging) vs. manifest event formats
74+
<xref:System.Diagnostics.Tracing.EventSource> can be configured into to two different modes based on the constructor used or what flags are set on <xref:System.Diagnostics.Tracing.EventSourceOptions>.
75+
76+
Historically, these two formats are derived from two formats that Event Tracing for Windows (ETW) used. While these two modes don't affect your ability to use Event Tracing for Windows (ETW) or EventPipe-based listeners, they do generate the metadata for events differently.
77+
78+
The default event format is <xref:System.Diagnostics.Tracing.EventSourceSettings.EtwManifestEventFormat>, which is set if not specified on <xref:System.Diagnostics.Tracing.EventSourceSettings>. Manifest-based <xref:System.Diagnostics.Tracing.EventSource> objects generate an XML document representing the events defined on the class upon initialization. This requires the <xref:System.Diagnostics.Tracing.EventSource> to reflect over itself to generate the provider and event metadata.
79+
80+
To use self-describing (tracelogging) event format, construct your <xref:System.Diagnostics.Tracing.EventSource> using the <xref:System.Diagnostics.Tracing.EventSource.%23ctor(System.String)> constructor, the <xref:System.Diagnostics.Tracing.EventSource.%23ctor(System.String,System.Diagnostics.Tracing.EventSourceSettings)> constructor, or by setting the `EtwSelfDescribingEventFormat` flag on <xref:System.Diagnostics.Tracing.EventSourceSettings>. Self-describing sources generate minimal provider metadata on initialization and only generate event metadata when <xref:System.Diagnostics.Tracing.EventSource.Write(System.String)> is called.
81+
82+
In practice, these event format settings only affect usage with readers based on Event Tracing for Windows (ETW). They can, however, have a small effect on initialization time and per-event write times due to the time required for reflection and generating the metadata.
83+
84+
### Examples
7585
The following example shows a simple implementation of the <xref:System.Diagnostics.Tracing.EventSource> class.
7686
7787
:::code language="csharp" source="~/samples/snippets/csharp/VS_Snippets_CLR/etwtracesmall/cs/program.cs" id="Snippet1":::
@@ -82,6 +92,28 @@
8292
:::code language="csharp" source="~/samples/snippets/csharp/VS_Snippets_CLR/etwtrace/cs/program.cs" id="Snippet1":::
8393
:::code language="vb" source="~/samples/snippets/visualbasic/VS_Snippets_CLR/etwtrace/vb/program.vb" id="Snippet1":::
8494
95+
96+
### Advanced Usage
97+
Traditionally, user-defined <xref:System.Diagnostics.Tracing.EventSource> objects expect to inherit directly from <xref:System.Diagnostics.Tracing.EventSource>. For advanced scenarios, however, you can create `abstract` <xref:System.Diagnostics.Tracing.EventSource> objects, called *Utility Sources*, and implement interfaces. Using one or both of these techniques allows you to share code between different derived sources.
98+
99+
> [!IMPORTANT]
100+
> Abstract <xref:System.Diagnostics.Tracing.EventSource> objects cannot define keywords, tasks, opcodes, channels, or events.
101+
102+
> [!IMPORTANT]
103+
> To avoid name collisions at run time when generating event metadata, don't explicitly implement interface methods when using interfaces with <xref:System.Diagnostics.Tracing.EventSource>.
104+
105+
The following example shows an implementation of <xref:System.Diagnostics.Tracing.EventSource> that uses an interface.
106+
107+
:::code language="csharp" source="~/samples/snippets/csharp/VS_Snippets_CLR/etwtracelarge/cs/program.cs" id="InterfaceSource":::
108+
109+
The following example shows an implementation of <xref:System.Diagnostics.Tracing.EventSource> that uses the Utility EventSource pattern.
110+
111+
:::code language="csharp" source="~/samples/snippets/csharp/VS_Snippets_CLR/etwtracelarge/cs/program.cs" id="UtilitySource":::
112+
113+
The following example shows an implementation of <xref:System.Diagnostics.Tracing.EventSource> for tracing information about a component in a library.
114+
115+
:::code language="csharp" source="~/samples/snippets/csharp/VS_Snippets_CLR/etwtracelarge/cs/program.cs" id="ComplexSource":::
116+
85117
]]></format>
86118
</remarks>
87119
<threadsafe>This type is thread safe.</threadsafe>
@@ -731,7 +763,7 @@
731763
<AssemblyVersion>4.0.0.0</AssemblyVersion>
732764
</AssemblyInfo>
733765
<AssemblyInfo>
734-
<AssemblyName>netstandard</AssemblyName>
766+
<AssemblyName>netstandard</AssemblyName>
735767
<AssemblyVersion>2.0.0.0</AssemblyVersion>
736768
<AssemblyVersion>2.1.0.0</AssemblyVersion>
737769
</AssemblyInfo>

0 commit comments

Comments
 (0)