diff --git a/docs/core/diagnostics/eventsource-activity-ids.md b/docs/core/diagnostics/eventsource-activity-ids.md new file mode 100644 index 0000000000000..46c5679fbad8d --- /dev/null +++ b/docs/core/diagnostics/eventsource-activity-ids.md @@ -0,0 +1,287 @@ +--- +title: EventSource Activity IDs +description: Understand how to use EventSource Activity IDs +ms.date: 03/24/2022 +--- + +# EventSource Activity IDs + +**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions + +This guide explains Activity IDs, an optional identifier that can be logged with each event generated using +. For an introduction see +[Getting Started with EventSource](./eventsource-getting-started.md). + +## The challenge of logging concurrent work + +Long ago a typical application may have been simple and single-threaded which makes logging straightforward. You +could write each step to a log file in order and then read the log back exactly in the order it was written to +understand what happened. If the app handled requests then only one request was handled at a time. All log +messages for request A would be printed in order, then all the messages for B, and so on. +When apps become multi-threaded that strategy no longer works because multiple +requests are being handled at the same time. However if each request is assigned to a single thread which processes +it entirely you can solve the problem by recording a thread id for each log message. For example a multi-threaded app +might log: + +``` +Thread Id Message +--------- ------- +12 BeginRequest A +12 Doing some work +190 BeginRequest B +12 uh-oh error happened +190 Doing some work +``` + +By reading the thread ids you know that thread 12 was processing request A and thread 190 was processing request B, +therefore the 'uh-oh error happened' message related to request A. However application concurrency has continued to +grow ever more sophisticated. It is now common to use `async` and `await` so that a single request could +be handled partially on many different threads before the work is complete. Thread ids are no longer sufficient to +correlate together all the messages produced for one request. Activity IDs solve this problem. They provide a finer +grain identifier that can track individual requests, or portions of requests, regardless of if the work +is spread across different threads. + +> [!NOTE] +> The Activity ID concept refered to here is not the same as the System.Diagnostics.Tracing.Activity, despite the +> similar naming. + +## Tracking work using an Activity ID + +You can run the code below to see Activity tracking in action. + +```C# +using System.Diagnostics.Tracing; + +public static class Program +{ + public static async Task Main(string[] args) + { + ConsoleWriterEventListener listener = new ConsoleWriterEventListener(); + + Task a = ProcessWorkItem("A"); + Task b = ProcessWorkItem("B"); + await Task.WhenAll(a, b); + } + + private static async Task ProcessWorkItem(string requestName) + { + DemoEventSource.Log.WorkStart(requestName); + await HelperA(); + await HelperB(); + DemoEventSource.Log.WorkStop(); + } + + private static async Task HelperA() + { + DemoEventSource.Log.DebugMessage("HelperA"); + await Task.Delay(100); // pretend to do some work + } + + private static async Task HelperB() + { + DemoEventSource.Log.DebugMessage("HelperB"); + await Task.Delay(100); // pretend to do some work + } +} + +[EventSource(Name ="Demo")] +class DemoEventSource : EventSource +{ + public static DemoEventSource Log = new DemoEventSource(); + + [Event(1)] + public void WorkStart(string requestName) => WriteEvent(1, requestName); + [Event(2)] + public void WorkStop() => WriteEvent(2); + + [Event(3)] + public void DebugMessage(string message) => WriteEvent(3, message); +} + +class ConsoleWriterEventListener : EventListener +{ + protected override void OnEventSourceCreated(EventSource eventSource) + { + if(eventSource.Name == "Demo") + { + Console.WriteLine("{0,-5} {1,-40} {2,-15} {3}", "TID", "Activity ID", "Event", "Arguments"); + EnableEvents(eventSource, EventLevel.Verbose); + } + else if(eventSource.Name == "System.Threading.Tasks.TplEventSource") + { + // Activity IDs aren't enabled by default. + // Enabling Keyword 0x80 on the TplEventSource turns them on + EnableEvents(eventSource, EventLevel.LogAlways, (EventKeywords)0x80); + } + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + lock (this) + { + Console.Write("{0,-5} {1,-40} {2,-15} ", eventData.OSThreadId, eventData.ActivityId, eventData.EventName); + if (eventData.Payload.Count == 1) + { + Console.WriteLine(eventData.Payload[0]); + } + else + { + Console.WriteLine(); + } + } + } +} +``` + +When run, this code prints output: + +``` +TID Activity ID Event Arguments +21256 00000011-0000-0000-0000-00006ab99d59 WorkStart A +21256 00000011-0000-0000-0000-00006ab99d59 DebugMessage HelperA +21256 00000012-0000-0000-0000-00006bb99d59 WorkStart B +21256 00000012-0000-0000-0000-00006bb99d59 DebugMessage HelperA +14728 00000011-0000-0000-0000-00006ab99d59 DebugMessage HelperB +11348 00000012-0000-0000-0000-00006bb99d59 DebugMessage HelperB +11348 00000012-0000-0000-0000-00006bb99d59 WorkStop +14728 00000011-0000-0000-0000-00006ab99d59 WorkStop +``` + +> [!NOTE] +> There is a known issue where Visual Studio debugger may cause invalid Activity IDs to be generated. Either don't run this sample under the debugger +> or set a breakpoint at the beginning of Main and evaluate the expression 'System.Threading.Tasks.TplEventSource.Log.TasksSetActivityIds = false' in +> the immediate window before continuing to work around the issue. + +Using the Activity IDs you can see that all of the messages for work item A have ID `00000011-...` and all the messages for +work item B have ID `00000012-...`. Both work items first did some work on thread 21256, but then each of them continued their work +on separate threadpool threads 11348 and 14728 so trying to track the request only with thread IDs would not have worked. + +EventSource has an automatic heuristic where defining an event named _Something_Start followed immediately by another event named +_Something_Stop is considered the start and stop of a unit of work. Logging the start event for a new unit of work creates a new +Activity ID and begins logging all events on the same thread with that Activity ID until the stop event is logged. The ID also +automatically follows async control flow when using `async` and `await`. Although it is recommended to use the Start/Stop naming suffixes +you may name the events anything you like by explicitly annotating them using the + property with the first event set to +EventOpcode.Start and the second set to EventOpcode.Stop. + +## Log requests that do parallel work + +Sometimes a single request might do different parts of its work in parallel and you both want to group all the log events +as well as the sub-parts. The example below simulates a request that does two database queries in parallel and then does some +processing on the result of each query. You want to isolate the work for each query, but also understand which queries belong +to the same request when many concurrent requests could be running. This is modeled as a tree where each top-level request is a root and +then sub-portions of work are branches. Each node in the tree gets its own Activity ID, and the first event logged with the new child +Activity ID logs an extra field called Related Activity ID to describe its parent. + +Run the following code: + +```C# +using System.Diagnostics.Tracing; + +public static class Program +{ + public static async Task Main(string[] args) + { + ConsoleWriterEventListener listener = new ConsoleWriterEventListener(); + + await ProcessWorkItem("A"); + } + + private static async Task ProcessWorkItem(string requestName) + { + DemoEventSource.Log.WorkStart(requestName); + Task query1 = Query("SELECT bowls"); + Task query2 = Query("SELECT spoons"); + await Task.WhenAll(query1, query2); + DemoEventSource.Log.WorkStop(); + } + + private static async Task Query(string query) + { + DemoEventSource.Log.QueryStart(query); + await Task.Delay(100); // pretend to send a query + DemoEventSource.Log.DebugMessage("processing query"); + await Task.Delay(100); // pretend to do some work + DemoEventSource.Log.QueryStop(); + } +} + +[EventSource(Name = "Demo")] +class DemoEventSource : EventSource +{ + public static DemoEventSource Log = new DemoEventSource(); + + [Event(1)] + public void WorkStart(string requestName) => WriteEvent(1, requestName); + [Event(2)] + public void WorkStop() => WriteEvent(2); + [Event(3)] + public void DebugMessage(string message) => WriteEvent(3, message); + [Event(4)] + public void QueryStart(string query) => WriteEvent(4, query); + [Event(5)] + public void QueryStop() => WriteEvent(5); +} + +class ConsoleWriterEventListener : EventListener +{ + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name == "Demo") + { + Console.WriteLine("{0,-5} {1,-40} {2,-40} {3,-15} {4}", "TID", "Activity ID", "Related Activity ID", "Event", "Arguments"); + EnableEvents(eventSource, EventLevel.Verbose); + } + else if (eventSource.Name == "System.Threading.Tasks.TplEventSource") + { + // Activity IDs aren't enabled by default. + // Enabling Keyword 0x80 on the TplEventSource turns them on + EnableEvents(eventSource, EventLevel.LogAlways, (EventKeywords)0x80); + } + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + lock (this) + { + Console.Write("{0,-5} {1,-40} {2, -40} {3,-15} ", eventData.OSThreadId, eventData.ActivityId, eventData.RelatedActivityId, eventData.EventName); + if (eventData.Payload.Count == 1) + { + Console.WriteLine(eventData.Payload[0]); + } + else + { + Console.WriteLine(); + } + } + } +} +``` + +This example prints output such as: + +``` +TID Activity ID Related Activity ID Event Arguments +34276 00000011-0000-0000-0000-000086af9d59 00000000-0000-0000-0000-000000000000 WorkStart A +34276 00001011-0000-0000-0000-0000869f9d59 00000011-0000-0000-0000-000086af9d59 QueryStart SELECT bowls +34276 00002011-0000-0000-0000-0000868f9d59 00000011-0000-0000-0000-000086af9d59 QueryStart SELECT spoons +32684 00002011-0000-0000-0000-0000868f9d59 00000000-0000-0000-0000-000000000000 DebugMessage processing query +18624 00001011-0000-0000-0000-0000869f9d59 00000000-0000-0000-0000-000000000000 DebugMessage processing query +18624 00002011-0000-0000-0000-0000868f9d59 00000000-0000-0000-0000-000000000000 QueryStop +32684 00001011-0000-0000-0000-0000869f9d59 00000000-0000-0000-0000-000000000000 QueryStop +32684 00000011-0000-0000-0000-000086af9d59 00000000-0000-0000-0000-000000000000 WorkStop +``` + +This example only ran one top level request which was assigned Activity ID `00000011-...`. Then each QueryStart event began +a new branch of the request with Activity IDs `00001011-...` and `00002011-...` respectively. You can identify these IDs are +children of the original request because both of the start events logged their parent `00000011-...` in the Related Activity ID field. + +> [!NOTE] +> You may have noticed the numerical values of the IDs have some clear patterns between parent and child and are not random. Although it +> can assist in spotting the relationship visually in simple cases it is best for tools not to rely on this and treat the IDs as opaque identifiers. +> As the nesting level grows deeper the byte pattern will change. Using the Related Activity ID field is the best way to ensure tools +> work reliably regardless of nesting level. + +Because requests with complex trees of sub-work items will generate many different Activity IDs these IDs are usually +best parsed by tools rather than trying to reconstruct the tree by hand. [PerfView](https://github.com/Microsoft/perfview) is one tool +that knows how to correlate events annotated with these IDs. diff --git a/docs/core/diagnostics/eventsource-collect-and-view-traces.md b/docs/core/diagnostics/eventsource-collect-and-view-traces.md new file mode 100644 index 0000000000000..183c77ed10b2a --- /dev/null +++ b/docs/core/diagnostics/eventsource-collect-and-view-traces.md @@ -0,0 +1,276 @@ +--- +title: Collect and View EventSource Traces +description: A tutorial for collecting and viewing traces of EventSource events +ms.topic: tutorial +ms.date: 03/03/2022 +--- + +# Collect and View EventSource Traces + +**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions + +The [Getting started guide](./eventsource-getting-started.md) showed you how to create a minimal EventSource and collect events in a trace file. +This tutorial shows how different tools can configure which events are collected in a trace and then view the traces. + +## Example app + +You'll use the following sample app that generates events for this tutorial. Compile a .NET console application containing the following code: + +```C# +using System.Diagnostics.Tracing; + +namespace EventSourceDemo +{ + public static class Program + { + public static void Main(string[] args) + { + DemoEventSource.Log.AppStarted("Hello World!", 12); + DemoEventSource.Log.DebugMessage("Got here"); + DemoEventSource.Log.DebugMessage("finishing startup"); + DemoEventSource.Log.RequestStart(3); + DemoEventSource.Log.RequestStop(3); + } + } + + [EventSource(Name = "Demo")] + class DemoEventSource : EventSource + { + public static DemoEventSource Log { get; } = new DemoEventSource(); + + [Event(1, Keywords = Keywords.Startup)] + public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber); + [Event(2, Keywords = Keywords.Requests)] + public void RequestStart(int requestId) => WriteEvent(2, requestId); + [Event(3, Keywords = Keywords.Requests)] + public void RequestStop(int requestId) => WriteEvent(3, requestId); + [Event(4, Keywords = Keywords.Startup, Level = EventLevel.Verbose)] + public void DebugMessage(string message) => WriteEvent(4, message); + + + public class Keywords + { + public const EventKeywords Startup = (EventKeywords)0x0001; + public const EventKeywords Requests = (EventKeywords)0x0002; + } + } +} +``` + +## Configure which events to collect + +Most event collection tools use these configuration options to decide which events should be included in a trace: + +- Provider names - This is a list of one or more EventSource names. Only events that are defined on EventSources + in this list are eligible to be included. To collect events from the DemoEventSource class in the example app + above you would need to include the EventSource name "Demo" in the list of provider names. +- Event verbosity level - For each provider you can define a verbosity level and events with + [verbosity](./eventsource-instrumentation.md#setting-event-verbosity-levels) higher than that level will be excluded + from the trace. If you specified that the "Demo" provider in the example app above should collect at the Informational + verbosity level then the DebugMessage event would be excluded because it has a higher level. Specifying + LogAlways(0) is a special case which indicates that events of any + verbosity level should be included. +- Event keywords - For each provider you can define a set of keywords and only events tagged with at least one of + the keywords will be included. In the example app above if you specified the Startup keyword then only the + AppStarted and DebugMessage events would be included. If no keywords are specified this is a special + case and means that events with any keyword should be included. + +### Conventions for describing provider configuration + +Although each tool determines its own user interface for setting the trace configuration, there is a common convention +many tools use when specifying the configuration as a text string. The list of providers is specified as a semi-colon +delimited list, and each provider element in the list consists of name, keywords, and level separated by colons. For example, +"Demo:3:5" identifies the EventSource named "Demo" with the keyword bitmask 3 (the `Startup` bit and the `Requests` bit) and + 5, which is `Verbose`. Many tools also let you omit the level and keywords +if no level or keyword filtering is desired. For example, "Demo::5" only does level-based filtering, "Demo:3" only does +keyword-based filtering, and "Demo" does no keyword or level filtering. + +## Visual Studio + +The [Visual Studio profiler](https://docs.microsoft.com/visualstudio/profiling) supports both collecting and viewing traces. It also can view traces +that have been collected in advance by other tools, such as [dotnet-trace](./dotnet-trace.md). + +### Collect a trace + +Most of Visual Studio's profiling tools use predefined sets of events that serve a particular purpose, such as analyzing CPU usage or allocations. To +collect a trace with customized events, you'll use the [Events Viewer](https://docs.microsoft.com/visualstudio/profiling/events-viewer) tool. + +1. To open the Performance Profiler in Visual Studio, select **Alt**+**F2**. + +2. Select the **Events Viewer** check box. + + [![Visual Studio Events Viewer](media/vs-events-viewer.jpg)](media/vs-events-viewer.jpg) + +3. Select the small gear icon to the right of Events Viewer to open the configuration window. + + In the table below **Additional Providers**, add a row for each provider you wish to configure by + clicking the **Enabled** checkbox, and then entering the provider name, keywords, and level. You don't need to enter the provider GUID; it's computed automatically. + + [![Visual Studio Events Viewer Settings](media/vs-events-viewer-settings.jpg)](media/vs-events-viewer-settings.jpg) + +4. Select **OK** to confirm the configuration settings. + +5. Select **Start** to begin running the app and collecting logs. + +6. Select **Stop Collection** or exit the app to stop collecting logs and show the collected data. + +### View a trace + +Visual Studio can view traces it collected itself, or it can view traces collected in other tools. To view traces from other +tools, use **File** > **Open** and select a trace file in the file picker. Visual Studio profiler supports *.etl* files (ETW's standard format), +*.nettrace* files (EventPipe's standard format), and *.diagsession* files (Visual Studio's standard format). For information about +working with trace files in Visual Studio, see the +[Visual Studio documentation](/visualstudio/profiling/events-viewer#understand-your-data). + +[![Visual Studio Events Viewer ETW trace](media/vs-etw-events.jpg)](media/vs-etw-events.jpg) + +> [!NOTE] +> Visual Studio collects some events automatically from ETW or EventPipe, even if they weren't explicitly configured. If you see events +> you don't recognize in the Provider Name or Event Name column and want to filter them out, use the filter icon to the right to select +> only the events you want to view. + +## PerfView + +PerfView is a performance tool created by the .NET team that can collect and view ETW traces. It can also view trace files collected by other +tools in a variety of formats. In this tutorial, you'll collect an ETW trace of the [demo app](#example-app) and then examine the collected +events in PerfView's event viewer. + +### Collect a trace + +1. Download PerfView from the [releases page](https://github.com/Microsoft/perfview/releases). This tutorial was done with +[PerfView version 2.0.76](https://github.com/microsoft/perfview/releases/tag/P2.0.76), but any recent version should work. + +1. Start PerfView.exe with administrator permissions. + + > [!NOTE] + > ETW trace collection always requires administrator permissions, however if you are only using PerfView to view a pre-existing + > trace, then no special permissions are needed. + + [![PerfView Main Window](media/perfview-main-window.jpg)](media/perfview-main-window.jpg) + +1. From the **Collect** menu, select **Run**. This opens a new dialog where you'll enter the path to the [demo app](#example-app). + + [![PerfView Run Dialog](media/perfview-run-dialog.jpg)](media/perfview-run-dialog.jpg) + +1. To configure which events get collected, expand **Advanced Options** at the bottom of the dialog. In the **Additional Providers** text +box, enter providers using the [conventional text format](#conventions-for-describing-provider-configuration) described previously. In +this case, you're entering "Demo:1:4", which means keyword bit 1 (`Startup` events) and verbosity 4 (`Informational`). + + [![PerfView Run Dialog Advanced Settings](media/perfview-run-dialog-advanced.jpg)](media/perfview-run-dialog-advanced.jpg) + +1. To launch the app and begin collecting the trace, select the **Run Command** button. When the app exits, the trace *PerfViewData.etl* +is saved in the current directory. + +### View a trace + +1. In the main window drop-down text box at the upper left, select the directory containing the trace file. Then double click the trace file +in the tree view below. + + [![PerfView Main Window Selecting a trace](media/perfview-main-window-select-trace.jpg)](media/perfview-main-window-select-trace.jpg) + +1. To bring up the Events viewer, double click the **Events** item that appears in the tree view below the trace file. + + [![PerfView Events Viewer](media/perfview-events-viewer.jpg)](media/perfview-events-viewer.jpg) + +1. All event types in the trace are shown in the list at the left. Double click an event type, such as Demo\AppStarted, to show all events +of that type in the table on the right. + + [![PerfView Events Viewer AppStarted events](media/perfview-events-viewer-appstarted.jpg)](media/perfview-events-viewer-appstarted.jpg) + +### Learn more + +To learn more about using PerfView, see the [PerfView video tutorials](https://docs.microsoft.com/shows/PerfView-Tutorial/). + +## dotnet-trace + +[dotnet-trace](./dotnet-trace.md) is a cross-platform command-line tool that can collect traces from .NET Core apps using +[EventPipe](./eventpipe.md) tracing. It does not support viewing trace data, but the traces it collects can be viewed by other tools such +as [PerfView](#perfview) or [Visual Studio](#visual-studio). dotnet-trace also supports converting its default *.nettrace* format traces +into other formats, such as Chromium or [Speedscope](https://www.speedscope.app/). + +### Collect a trace + +1. Download and Install [dotnet-trace](./dotnet-trace.md#install). + +2. At the command line, run the [dotnet-trace collect](./dotnet-trace.md#dotnet-trace-collect) command: + + ```dotnetcli + E:\temp\EventSourceDemo\bin\Debug\net6.0>dotnet-trace collect --providers Demo:1:4 -- EventSourceDemo.exe + ``` + + This should show output similar to: + + ```dotnetcli + E:\temp\EventSourceDemo\bin\Debug\net6.0> dotnet-trace collect --providers Demo:1:4 -- EventSourceDemo.exe + + Provider Name Keywords Level Enabled By + Demo 0x0000000000000001 Informational(4) --providers + + Launching: EventSourceDemo.exe + Process : E:\temp\EventSourceDemo\bin\Debug\net6.0\EventSourceDemo.exe + Output File : E:\temp\EventSourceDemo\bin\Debug\net6.0\EventSourceDemo.exe_20220317_021512.nettrace + + [00:00:00:00] Recording trace 0.00 (B) + Press or to exit... + + Trace completed. + ``` + + dotnet-trace uses the [conventional text format](#conventions-for-describing-provider-configuration) for describing provider configuration in + the --providers argument. For more options on how to take traces using dotnet-trace, see the + [dotnet-trace docs](./dotnet-trace.md#collect-a-trace-with-dotnet-trace). + +## EventListener + + is an .NET API that can be used from in-process +to receive callbacks for events generated by an . +This API can be used to create custom logging tools or to analyze the events in memory without ever serializing them. + +To use `EventListener`, declare a type that derives from `EventListener`, invoke to +subscribe to the events from any EventSource of interest, and override the , +which will be called whenever a new event is available. It's often useful to override + to discover which EventSource objects exist, but +this isn't required. Following is an example `EventListener` implementation that prints to console when messages are received: + +1. Add this code to the [demo app](#example-app). + + ```C# + class ConsoleWriterEventListener : EventListener + { + protected override void OnEventSourceCreated(EventSource eventSource) + { + if(eventSource.Name == "Demo") + { + EnableEvents(eventSource, EventLevel.Informational); + } + } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + Console.WriteLine(eventData.TimeStamp + " " + eventData.EventName); + } + } + ``` + +1. Modify the `Main` method to create an instance of the new listener. + + ```C# + public static void Main(string[] args) + { + ConsoleWriterEventListener listener = new ConsoleWriterEventListener(); + + DemoEventSource.Log.AppStarted("Hello World!", 12); + DemoEventSource.Log.DebugMessage("Got here"); + DemoEventSource.Log.DebugMessage("finishing startup"); + DemoEventSource.Log.RequestStart(3); + DemoEventSource.Log.RequestStop(3); + } + ``` + +1. Build and run the app. Previously, it had no output, but now it writes the events to the console: + + ``` + 3/24/2022 9:23:35 AM AppStarted + 3/24/2022 9:23:35 AM RequestStart + 3/24/2022 9:23:35 AM RequestStop + ``` diff --git a/docs/core/diagnostics/eventsource-getting-started.md b/docs/core/diagnostics/eventsource-getting-started.md new file mode 100644 index 0000000000000..eb0b2478a3a93 --- /dev/null +++ b/docs/core/diagnostics/eventsource-getting-started.md @@ -0,0 +1,122 @@ +--- +title: Getting Started with EventSource +description: A tutorial to create a basic EventSource and understand key concepts +ms.date: 02/17/2022 +--- +# Getting Started with EventSource + +**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions + +This walkthrough shows how to log a new event with , +collect events in a trace file, view the trace, and understand basic EventSource concepts. + +> [!NOTE] +> Many technologies that integrate with EventSource use the terms 'Tracing' and 'Traces' instead of 'Logging' and 'Logs'. +> The meaning is the same here. + +## Log an event + +The goal of EventSource is to allow .NET developers to write code like this to log an event: + +``` +DemoEventSource.Log.AppStarted("Hello World!", 12); +``` + +This line of code has a logging object (`DemoEventSource.Log`), a method representing the event to log (`AppStarted`), and +optionally some strongly typed event parameters (`HelloWorld!` and `12`). There are no verbosity levels, event IDs, message +templates, or anything else that doesn't need to be at the callsite. All of this other information about events is written +by defining a new class derived from . + +Here is complete minimal example: + +```C# +using System.Diagnostics.Tracing; + +namespace EventSourceDemo +{ + public static class Program + { + public static void Main(string[] args) + { + DemoEventSource.Log.AppStarted("Hello World!", 12); + } + } + + [EventSource(Name = "Demo")] + class DemoEventSource : EventSource + { + public static DemoEventSource Log { get; } = new DemoEventSource(); + + [Event(1)] + public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber); + } +} +``` + +The DemoEventSource class declares a method for each type of event that you wish to log. In this case a single event called 'AppStarted' is defined +by the AppStarted() method. Each time the code invokes the AppStarted method another AppStarted event will be recorded in the trace if the event is enabled. +This is some of the data that can be captured with each event: + +- Event name - A name that identifies the kind of event which was logged. The event name will be identical to the method name, 'AppStarted' in this case. +- Event ID - A numerical id that identifies the kind of event which was logged. This serves a similar role to the name but can assist in fast automated + log processing. The AppStarted event has an ID of 1, specified in the . +- Source name - The name of the EventSource which contains the event. This is used as a namespace for events. Event names and IDs only need to + be unique within the scope of their source. Here the source is named "Demo", specified in the + on the class definition. The source name is also commonly refered to as a provider name. +- Arguments - All the method argument values are serialized. +- Other information - Events can also contain timestamps, thread IDs, processor IDs, [Activity IDs](./eventsource-activity-ids.md), stack traces, and event + metadata such as message templates, verbosity levels, and keywords. + +For more information and best practices on creating events see [Instrumenting code to create events](./eventsource-instrumentation.md). + +## Collect and view a trace file + +There is no required configuration in code that describes which events should be enabled, where the logged data should be sent, +or what format the data should be stored in. If you run the app now it won't produce any trace file by default. EventSource uses the +[Publish-subscribe pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) which requires subscribers to indicate which events +should be enabled and to control all serialization for the subscribed events. EventSource has integrations for subscribing from +[Event Tracing for Windows (ETW)](/windows/win32/etw/event-tracing-portal) and [EventPipe](./eventpipe.md) (.NET Core only). +Custom subscribers can also be created using the API. + +This demo shows an [EventPipe](./eventpipe.md) example for .NET Core apps. To learn about more options see +[Collecting and viewing event traces](./eventsource-collect-and-view-traces.md). [EventPipe](./eventpipe.md) is an open and cross-platform tracing +technology built into the .NET Core runtime to give .NET developers trace collection tools and a portable compact trace +format (*.nettrace files). [dotnet-trace](./dotnet-trace.md) is a command-line tool that collects EventPipe traces. + +1. Download and Install [dotnet-trace](./dotnet-trace.md#install) +1. Build the console app above. This demo assumes the app is named EventSourceDemo.exe and it is in the current directory. At the command-line run: + +```dotnetcli +>dotnet-trace collect --providers Demo -- EventSourceDemo.exe +``` + +This should show output similar to: + +```dotnetcli +Provider Name Keywords Level Enabled By +Demo 0xFFFFFFFFFFFFFFFF Verbose(5) --providers + +Launching: EventSourceDemo.exe +Process : E:\temp\EventSourceDemo\bin\Debug\net6.0\EventSourceDemo.exe +Output File : E:\temp\EventSourceDemo\bin\Debug\net6.0\EventSourceDemo.exe_20220303_001619.nettrace + +[00:00:00:00] Recording trace 0.00 (B) +Press or to exit... + +Trace completed. +``` + +This command ran EventSourceDemo.exe with all events in the 'Demo' EventSource enabled and output the trace file `EventSourceDemo.exe_20220303_001619.nettrace`. +Opening the file in Visual Studio shows the events that were logged. + +[![Visual Studio nettrace file](media/vs-nettrace-events.jpg)](media/vs-nettrace-events.jpg) + +In the list view you can see the first event is the Demo/AppStarted event. The text column has the saved arguments, the timestamp column shows the event +occured 27ms after logging started and to the right you can see the callstack. The other events are automatically enabled in every trace collected by +dotnet-trace though they can be ignored and filtered from view in the UI if they are distracting. Those extra events capture some information about the +process and jitted code which allows VS to reconstruct the event stack traces. + +## Learning more about EventSource + +- [Instrumenting code to create events](./eventsource-instrumentation.md) +- [Collecting and viewing event traces](./eventsource-collect-and-view-traces.md) diff --git a/docs/core/diagnostics/eventsource-instrumentation.md b/docs/core/diagnostics/eventsource-instrumentation.md new file mode 100644 index 0000000000000..93a1ef0958532 --- /dev/null +++ b/docs/core/diagnostics/eventsource-instrumentation.md @@ -0,0 +1,372 @@ +--- +title: Instrument Code to Create EventSource Events +description: A tutorial for instrumenting code with EventSource +ms.date: 03/03/2022 +--- + +# Instrument Code to Create EventSource Events + +**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions + +The [Getting Started guide](./eventsource-getting-started.md) showed you how to create a minimal EventSource and collect events in a trace file. +This tutorial will cover more detail creating events using . + +## A minimal EventSource + +```C# +[EventSource(Name = "Demo")] +class DemoEventSource : EventSource +{ + public static DemoEventSource Log { get; } = new DemoEventSource(); + + [Event(1)] + public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber); +} +``` + +The basic structure of a derived EventSource is always the same. In particular: + +- The class inherits from +- For each different type of event you wish to generate, a method needs to be defined. + This method should be named using the name of the event being created. If the event has additional data these should be + passed using arguments. These event arguments need to be serialized so only [certain types](#supported-parameter-types) are allowed. +- Each method has a body that calls WriteEvent passing it an ID (a numeric value that represents the event) and the + arguments of the event method. The ID needs to be unique within the EventSource. The ID is explicitly assigned + using the +- EventSources are intended to be singleton instances. Thus it is convenient to define a static variable, by convention + called `Log`, that represents this singleton. + +## Rules for defining event methods + +1. Any instance, non-virtual, void returning method defined in an EventSource class is by default an event logging method. +2. Virtual or non-void-returning methods are included only if they are marked with the + +3. To mark a qualifying method as non-logging you must decorate it with the + +4. Event logging methods have event IDs associated with them. This can be done either explicitly by decorating the method +with a or implicitly by the ordinal number of +the method in the class. For example using implicit numbering the first method in the class has ID 1, the second has ID 2, and +so on. +5. Event logging methods must call a , +, + or + overload. +6. The event ID, whether implied or explicit, must match the first argument passed to the WriteEvent\* API it calls. +7. The number, types and order of arguments passed to the EventSource method must align with how they are passed +to the WriteEvent\* APIs. For WriteEvent the arguments follow the Event ID, for WriteEventWithRelatedActivityId the arguments +follow the relatedActivityId. For the WriteEvent\*Core methods the arguments must be serialized manually into the +`data` parameter. + +## Best practices + +1. Types that derive from EventSource usually don't have intermediate types in the hierarchy or implement interfaces. See +[Advanced customizations](#advanced-customizations) below for some exceptions where this may be useful. +2. Generally the name of the EventSource class is a bad public name for the EventSource. Public names, the names that will +show up in logging configurations and log viewers, should be globally unique. Thus it is good practice to give your EventSource +a public name using the . The name "Demo" +used above is short and unlikely to be unique so not a good choice for production use. A common convention is to use a +hierarchial name with . or - as a separator such as "MyCompany-Samples-Demo" or the name of the Assembly or namespace for +which the EventSource provides events. It is not recommended to include "EventSource" as part of the public name. +3. Assign Event IDs explicitly, this way seemingly benign changes to the code in the source class such as rearranging it or +adding a method in the middle will not change the event ID associated with each method. +4. When authoring events that represent the start and end of a unit of work, by convention these methods are named with +suffixes 'Start' and 'Stop'. For example "RequestStart' and 'RequestStop'. +5. Do not specify an explicit value for EventSourceAttribute’s Guid property, unless you need it for backwards compatibility reasons. +The default Guid value is derived from the source’s name, which allows tools to accept the more human-readable name and derive +the same Guid. +6. Call before performing any resource intensive work related to +firing an event, such as computing an expensive event argument that won't be needed if the event is disabled. +7. Attempt to keep EventSource object back compatible and version them appropriately. The default version for an event is 0. +The version can be changed be setting . +Change the version of an event whenever you change the data that is serialized with it. Always add new serialized data to the +end of the event declaration, ie at the end of the list of method parameters. If this is not possible, create a new event with a +new ID to replace the old one. +8. When declaring events methods, specify fixed-size payload data before variably sized data. + +## Typical event customizations + +### Setting event verbosity levels + +Each event has a verbosity level and event subscribers often enable all events on an EventSource up to a certain verbosity level. +Events declare their verbosity level using the property. For example in +this EventSource below a subscriber that requests events of level Informational and lower will not log the Verbose DebugMessage +event. + +```C# +[EventSource(Name = "MyCompany-Samples-Demo")] +class DemoEventSource : EventSource +{ + public static DemoEventSource Log { get; } = new DemoEventSource(); + + [Event(1, Level = EventLevel.Informational)] + public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber); + [Event(2, Level = EventLevel.Verbose)] + public void DebugMessage(string message) => WriteEvent(2, message); +} +``` + +If the verbosity level of an event is not specified in the EventAttribute then it defaults to Informational. + +#### Best practice + +Use levels less than Informational for relatively rare warnings or errors. When in doubt, stick with the default of Informational and +use Verbose for events that occur more frequently than 1000 events/sec. + +### Setting event keywords + +Some event tracing systems support keywords as an additional filtering mechanism. Unlike verbosity that categorizes events by level +of detail, keywords are intended to categorize events based on other criteria such as areas of code functionality or which would be useful +for diagnosing certain problems. Keywords are named bit flags and each event can have any combination of keywords applied to it. For +example the EventSource below defines some events that relate to request processing and other events that relate to startup. If a developer +wanted to analyze the performance of startup they might only enable logging the events marked with the startup keyword. + +```C# +[EventSource(Name = "Demo")] +class DemoEventSource : EventSource +{ + public static DemoEventSource Log { get; } = new DemoEventSource(); + + [Event(1, Keywords = Keywords.Startup)] + public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber); + [Event(2, Keywords = Keywords.Requests)] + public void RequestStart(int requestId) => WriteEvent(2, requestId); + [Event(3, Keywords = Keywords.Requests)] + public void RequestStop(int requestId) => WriteEvent(3, requestId); + + public class Keywords // This is a bitvector + { + public const EventKeywords Startup = (EventKeywords)0x0001; + public const EventKeywords Requests = (EventKeywords)0x0002; + } +} +``` + +Keywords must be defined by using a nested class called `Keywords` and each individual keyword is defined by a member typed +`public const EventKeywords`. + +#### Best practice + +Keywords are more important when distinguishing between high volume events. This allows an event consumer to raise the +verbosity to a high level but manage the performance overhead and log size by only enabling narrow subsets of the events. +Events that are triggered more than 1,000/sec are good candidates for a unique keyword. + +## Supported parameter types + +EventSource requires that all event parameters can be serialized so it only accepts a limited set of types. These are: + +- Primitives: bool, byte, sybte, char, short, ushort, int, uint, long, ulong, float, double, IntPtr, and UIntPtr, Guid + decimal, string, DateTime, DateTimeOffset, TimeSpan +- Enums +- Structures attributed with . Only + the public instance properties with serialiable types will be serialized. +- Anonymous types where all public properties are serializable types +- Arrays of serializable types +- Nullable\ where T is a serializable type +- KeyValuePair\ where T and U are both serializable types +- Types that implement IEnumerable\ for exactly one type T and where T is a serializable type + +## Troubleshooting + +The EventSource class was designed so that it would never thow an Exception by default. This is a useful property as +logging is often treated as optional and you usually don't want an error writing a log message to cause your application +to fail. However this makes finding any mistake in your EventSource difficult. Here are several techniques that can help +troubleshoot: + +1. The EventSource constructor has overloads which take . Try +enabling the ThrowOnEventWriteErrors flag temporarily. +2. The property +stores any Exception that was generated when validating the event logging methods. This can reveal a variety of +authoring errors. +3. EventSource logs errors using a event ID 0 and this error event has a string describing the error. +4. When debugging, that same error string will also be logged using Debug.WriteLine() and show up in the debug +output window. +5. EventSource internally throws and then catches Exceptions when errors occur. Enable 1st chance exceptions +in a debugger or use event tracing with the .NET runtime's +[Exception events](../../fundamentals/diagnostics/runtime-exception-events.md) +enabled to observe when these Exceptions are occuring. + +## Advanced customizations + +### Setting OpCodes and Tasks + +ETW has concepts of [Tasks and OpCodes](https://docs.microsoft.com/windows/win32/wes/defining-tasks-and-opcodes) +which are further mechanisms for tagging and filtering events. You can associate events with specific tasks and opcodes +using the and + properties. Here is an example: + +```C# +[EventSource(Name = "Samples-EventSourceDemos-Customized")] +public sealed class CustomizedEventSource : EventSource +{ + static public CustomizedEventSource Log { get; } = new CustomizedEventSource(); + + [Event(1, Task = Tasks.Request, Opcode=EventOpcode.Start)] + public void RequestStart(int RequestID, string Url) + { + WriteEvent(1, RequestID, Url); + } + + [Event(2, Task = Tasks.Request, Opcode=EventOpcode.Info)] + public void RequestPhase(int RequestID, string PhaseName) + { + WriteEvent(2, RequestID, PhaseName); + } + + [Event(3, Keywords = Keywords.Requests, + Task = Tasks.Request, Opcode=EventOpcode.Stop)] + public void RequestStop(int RequestID) + { + WriteEvent(3, RequestID); + } + + public class Tasks + { + public const EventTask Request = (EventTask)0x1; + } +} +``` + +You can implicitly create EventTask objects by declaring two event methods with subsequent event IDs that have the naming +pattern \Start and \Stop. These events must be declared next to each other in the class definition and +the \Start method must come first. + +### Self-describing (tracelogging) vs. manifest event formats + +This concept only matters when subscribing to EventSource from ETW. ETW has two different ways that it can log events, +manifest format and self-describing (sometimes called tracelogging) format. Manifest-based EventSource objects generate +and log an XML document representing the events defined on the class upon initialization. This requires the EventSource +to reflect over itself to generate the provider and event metadata. In the Self-describing format metadata for each event is +transmitted inline with the event data rather than up-front. The self-describing approach supports the more flexible + methods that can send arbitrary events without having created +a pre-defined event logging method. It is also slightly faster at startup because it avoids eager reflection. However the +extra metadata that is emitted with each event adds a small performance overhead which may not be desirable when sending +a very high volume of events. + +To use self-describing event format, construct your EventSource using the EventSource(String) constructor, the +EventSource(String, EventSourceSettings) constructor, or by setting the EtwSelfDescribingEventFormat flag on EventSourceSettings. + +### EventSource types implementing interfaces + +An EventSource type may implement an interface in order to integrate seamlessly in various advanced logging +systems that use interfaces to define a common logging target. Here's an example of a possible use: + +```C# +public interface IMyLogging +{ + void Error(int errorCode, string msg); + void Warning(string msg); +} + +[EventSource(Name = "Samples-EventSourceDemos-MyComponentLogging")] +public sealed class MyLoggingEventSource : EventSource, IMyLogging +{ + public static MyLoggingEventSource Log { get; } = new MyLoggingEventSource(); + + [Event(1)] + public void Error(int errorCode, string msg) + { WriteEvent(1, errorCode, msg); } + + [Event(2)] + public void Warning(string msg) + { WriteEvent(2, msg); } +} +``` + +You must specify the EventAttribute on the interface methods, otherwise (for compatibility reasons) the method will not +be treated as a logging method. Explicit interface method implementation is disallowed in order to prevent naming collisions. + +### EventSource class hierarchies + +In most cases you will be able to write types that directly derive from the EventSource class. Sometimes however it +is useful to define functionality that will be shared by multiple derived EventSource types, such as customized WriteEvent +overloads (see [optimizing performance for high volume events](#optimizing-performance-for-high-volume-events) below). + +Abstract base classes can be used as long as they don't define any keywords, tasks, opcodes, channels, or events. Here is +an example where the UtilBaseEventSource class defines an optimized WriteEvent overload this is needed by multiple derived +EventSources in the same component. One of these derived types is illustrated below as OptimizedEventSource. + +```C# +public abstract class UtilBaseEventSource : EventSource +{ + protected UtilBaseEventSource() + : base() + { } + protected UtilBaseEventSource(bool throwOnEventWriteErrors) + : base(throwOnEventWriteErrors) + { } + + protected unsafe void WriteEvent(int eventId, int arg1, short arg2, long arg3) + { + if (IsEnabled()) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 4; + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 2; + descrs[2].DataPointer = (IntPtr)(&arg3); + descrs[2].Size = 8; + WriteEventCore(eventId, 3, descrs); + } + } +} + +[EventSource(Name = "OptimizedEventSource")] +public sealed class OptimizedEventSource : UtilBaseEventSource +{ + public static OptimizedEventSource Log { get; } = new OptimizedEventSource(); + + [Event(1, Keywords = Keywords.Kwd1, Level = EventLevel.Informational, + Message = "LogElements called {0}/{1}/{2}.")] + public void LogElements(int n, short sh, long l) + { + WriteEvent(1, n, sh, l); // Calls UtilBaseEventSource.WriteEvent + } + + #region Keywords / Tasks /Opcodes / Channels + public static class Keywords + { + public const EventKeywords Kwd1 = (EventKeywords)1; + } + #endregion +} +``` + +## Optimizing performance for high volume events + +The EventSource class has a number of overloads for WriteEvent, including one for variable number of arguments. When none of the other +overloads matches, the params method is called. Unforetunately, the params overload is relatively expensive. In particular it: + +1. Allocates an array to hold the variable arguments +2. Casts each parameter to an object which causes allocations for value types +3. Assigns these objects to the array +4. Calls the function +5. Figures out the type of each array element to determine how to serialize it + +This is probably 10 to 20 times as expensive as specialized types. This does not matter much for low volume cases but for high +volume events it can be important. There are two important cases for insuring that the params overload is not used: + +1. Ensure that enumerated types are cast to 'int' so that they match one of the fast overloads +2. Create new fast WriteEvent overloads for high volume payloads + +Here is an example for adding a WriteEvent overload that takes four integer arguments + +```C# +[NonEvent] +public unsafe void WriteEvent(int eventId, int arg1, int arg2, + int arg3, int arg4) +{ + EventData* descrs = stackalloc EventProvider.EventData[4]; + + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 4; + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 4; + descrs[2].DataPointer = (IntPtr)(&arg3); + descrs[2].Size = 4; + descrs[3].DataPointer = (IntPtr)(&arg4); + descrs[3].Size = 4; + + WriteEventCore(eventId, 4, (IntPtr)descrs); +} +``` diff --git a/docs/core/diagnostics/eventsource.md b/docs/core/diagnostics/eventsource.md new file mode 100644 index 0000000000000..6686804b6d04b --- /dev/null +++ b/docs/core/diagnostics/eventsource.md @@ -0,0 +1,26 @@ +--- +title: EventSource +description: A guide to logging with EventSource +ms.date: 02/17/2022 +--- +# EventSource + +**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions + + is a fast structured logging solution +built into the .NET runtime. On .NET Framework EventSource can send events to +[Event Tracing for Windows (ETW)](/windows/win32/etw/event-tracing-portal) and +. On .NET Core EventSource +additionally supports [EventPipe](./eventpipe.md), a cross-platform tracing option. Most often developers use EventSource +logs for performance analysis, but EventSource can be used for any diagnostic tasks where logs are useful. +The .NET runtime is already instrumented with [built-in events](./well-known-event-providers.md) and you can log +your own custom events. + +> [!NOTE] +> Many technologies that integrate with EventSource use the terms 'Tracing' and 'Traces' instead of 'Logging' and 'Logs'. +> The meaning is the same here. + +- [Getting started](./eventsource-getting-started.md) +- [Instrumenting code to create events](./eventsource-instrumentation.md) +- [Collecting and viewing event traces](./eventsource-collect-and-view-traces.md) +- [Understanding Activity IDs](./eventsource-activity-ids.md) diff --git a/docs/core/diagnostics/logging-tracing.md b/docs/core/diagnostics/logging-tracing.md index bdda4e4c67b3f..b08b337fdf528 100644 --- a/docs/core/diagnostics/logging-tracing.md +++ b/docs/core/diagnostics/logging-tracing.md @@ -43,7 +43,7 @@ The choice of which print-style API to use is up to you. The key differences are The following APIs are more event oriented. Rather than logging simple strings they log event objects. -- +- [System.Diagnostics.Tracing.EventSource](./eventsource.md) - EventSource is the primary root .NET Core tracing API. - Available in all .NET Standard versions. - Only allows tracing serializable objects. @@ -51,8 +51,6 @@ The following APIs are more event oriented. Rather than logging simple strings t - Can be consumed out-of-process via: - [.NET Core's EventPipe](./eventpipe.md) on all platforms - [Event Tracing for Windows (ETW)](/windows/win32/etw/event-tracing-portal) - - [LTTng tracing framework for Linux](https://lttng.org/) - - Walkthrough: [Collect an LTTng trace using PerfCollect](trace-perfcollect-lttng.md). - - Included in .NET Core and as a [NuGet package](https://www.nuget.org/packages/System.Diagnostics.DiagnosticSource) for .NET Framework. diff --git a/docs/core/diagnostics/media/perfview-events-viewer-appstarted.jpg b/docs/core/diagnostics/media/perfview-events-viewer-appstarted.jpg new file mode 100644 index 0000000000000..d61a3caba3c48 Binary files /dev/null and b/docs/core/diagnostics/media/perfview-events-viewer-appstarted.jpg differ diff --git a/docs/core/diagnostics/media/perfview-events-viewer.jpg b/docs/core/diagnostics/media/perfview-events-viewer.jpg new file mode 100644 index 0000000000000..a90cad275147e Binary files /dev/null and b/docs/core/diagnostics/media/perfview-events-viewer.jpg differ diff --git a/docs/core/diagnostics/media/perfview-main-window-select-trace.jpg b/docs/core/diagnostics/media/perfview-main-window-select-trace.jpg new file mode 100644 index 0000000000000..c537ad7e73cc0 Binary files /dev/null and b/docs/core/diagnostics/media/perfview-main-window-select-trace.jpg differ diff --git a/docs/core/diagnostics/media/perfview-main-window.jpg b/docs/core/diagnostics/media/perfview-main-window.jpg new file mode 100644 index 0000000000000..f39c0f8500854 Binary files /dev/null and b/docs/core/diagnostics/media/perfview-main-window.jpg differ diff --git a/docs/core/diagnostics/media/perfview-run-dialog-advanced.jpg b/docs/core/diagnostics/media/perfview-run-dialog-advanced.jpg new file mode 100644 index 0000000000000..b38ca6461876e Binary files /dev/null and b/docs/core/diagnostics/media/perfview-run-dialog-advanced.jpg differ diff --git a/docs/core/diagnostics/media/perfview-run-dialog.jpg b/docs/core/diagnostics/media/perfview-run-dialog.jpg new file mode 100644 index 0000000000000..489c16894d7ca Binary files /dev/null and b/docs/core/diagnostics/media/perfview-run-dialog.jpg differ diff --git a/docs/core/diagnostics/media/vs-etw-events.jpg b/docs/core/diagnostics/media/vs-etw-events.jpg new file mode 100644 index 0000000000000..bcae2c960f894 Binary files /dev/null and b/docs/core/diagnostics/media/vs-etw-events.jpg differ diff --git a/docs/core/diagnostics/media/vs-events-viewer-settings.jpg b/docs/core/diagnostics/media/vs-events-viewer-settings.jpg new file mode 100644 index 0000000000000..a10b321998573 Binary files /dev/null and b/docs/core/diagnostics/media/vs-events-viewer-settings.jpg differ diff --git a/docs/core/diagnostics/media/vs-events-viewer.jpg b/docs/core/diagnostics/media/vs-events-viewer.jpg new file mode 100644 index 0000000000000..db260fd4ff393 Binary files /dev/null and b/docs/core/diagnostics/media/vs-events-viewer.jpg differ diff --git a/docs/core/diagnostics/media/vs-nettrace-events.jpg b/docs/core/diagnostics/media/vs-nettrace-events.jpg new file mode 100644 index 0000000000000..5d563dee6bb33 Binary files /dev/null and b/docs/core/diagnostics/media/vs-nettrace-events.jpg differ diff --git a/docs/fundamentals/toc.yml b/docs/fundamentals/toc.yml index 0a4f543d88186..e463c524d6300 100644 --- a/docs/fundamentals/toc.yml +++ b/docs/fundamentals/toc.yml @@ -643,6 +643,18 @@ items: href: ../core/diagnostics/distributed-tracing-instrumentation-walkthroughs.md - name: Collection href: ../core/diagnostics/distributed-tracing-collection-walkthroughs.md + - name: Event Source + items: + - name: Overview + href: ../core/diagnostics/eventsource.md + - name: Getting started + href: ../core/diagnostics/eventsource-getting-started.md + - name: Instrumentation + href: ../core/diagnostics/eventsource-instrumentation.md + - name: Collection + href: ../core/diagnostics/eventsource-collect-and-view-traces.md + - name: Activity IDs + href: ../core/diagnostics/eventsource-activity-ids.md - name: Metrics items: - name: Overview