From 1901847a1aa3b7ec332bc55c4847530d0580620d Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Mon, 2 May 2022 16:00:53 -0600 Subject: [PATCH 01/28] Create diagnosticsource-getting-started.md This is a rough draft, all suggestions appreciated. --- .../diagnosticsource-getting-started.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 docs/core/diagnostics/diagnosticsource-getting-started.md diff --git a/docs/core/diagnostics/diagnosticsource-getting-started.md b/docs/core/diagnostics/diagnosticsource-getting-started.md new file mode 100644 index 0000000000000..837ed1bf21b57 --- /dev/null +++ b/docs/core/diagnostics/diagnosticsource-getting-started.md @@ -0,0 +1,27 @@ +--- +title: Getting Started with DiagnosticSource +description: A tutorial to create a basic DiagnosticSource and understand key concepts +ms.date: 05/02/2022 +--- +# Getting Started with DiagnosticSource + +**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions + +This walkthrough shows + +## Log an event + +The `DiagnosticSource` type is an abstract base class that defines the methods needed to log events. The class that holds the implementation is `DiagnosticListener`. +The first step in instrumenting code with `DiagnosticSource` is to create a +`DiagnosticListener`. For example: + +```C# + private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); +``` +Notice that httpLogger is typed as a `DiagnosticSource`. +This is because this code +only cares about writing events and thus only cares about the `DiagnosticSource` methods that +the `DiagnosticListener` implements. `DiagnosticListeners` are given names when they are created +and this name should be the name of logical grouping of related events (typically the component). +Later this name is used to find the Listener and subscribe to any of its events. `DiagnosticListeners` have a name, which is used to represent the component associated with the event. +Thus the event names only need to be unique within a component. From ebf91419856eb5a2227716320dc11a97cd5c58fd Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Tue, 3 May 2022 12:33:17 -0600 Subject: [PATCH 02/28] DiagnosticSource Overview page --- docs/core/diagnostics/diagnosticsource.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/core/diagnostics/diagnosticsource.md diff --git a/docs/core/diagnostics/diagnosticsource.md b/docs/core/diagnostics/diagnosticsource.md new file mode 100644 index 0000000000000..445f8c9afd8fa --- /dev/null +++ b/docs/core/diagnostics/diagnosticsource.md @@ -0,0 +1,22 @@ +--- +title: DiagnosticSource +description: A guide to logging with DiagnosticSource +ms.date: 05/03/2022 +--- +# DiagnosticSource + +**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions + + is a simple module that allows code to be instrumented for production-time +logging of rich data payloads for consumption within the process that was instrumented. At runtime, consumers can dynamically discover +data sources and subscribe to the ones of interest. was designed to allow in-process +tools to access rich data. When using , the consumer is assumed +to be within the same process and as a result, non-serializable types (e.g. `HttpResponseMessage` or `HttpContext`) can be passed, +giving customers plenty of data to work with. + +> [!NOTE] +> Many technologies that integrate with DiagnosticSource use the terms 'Tracing' and 'Traces' instead of 'Logging' and 'Logs'. +> The meaning is the same here. + +- [Getting started](./diagnosticsource-getting-started.md) +- [Instrumenting code to create events and consuming data](./diagnosticsource-instrumentation-consumption.md) From d29f7b01de65f0495ee694581ab0183c0f7feb5b Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Tue, 3 May 2022 13:47:26 -0600 Subject: [PATCH 03/28] Alter formatting and clarify contents of the file --- ...osticsource-instrumentation-consumption.md | 263 ++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 docs/core/diagnostics/diagnosticsource-instrumentation-consumption.md diff --git a/docs/core/diagnostics/diagnosticsource-instrumentation-consumption.md b/docs/core/diagnostics/diagnosticsource-instrumentation-consumption.md new file mode 100644 index 0000000000000..0c3734d7087d3 --- /dev/null +++ b/docs/core/diagnostics/diagnosticsource-instrumentation-consumption.md @@ -0,0 +1,263 @@ +--- +title: Instrument Code to Create DiagnosticSource Events and Consume Data with DiagnosticListener +description: A tutorial for instrumenting code with DiagnosticSource and consuming data with DiagnosticListener +ms.date: 05/02/2022 +--- +# Instrument Code to Create DiagnosticSource Events and Consume Data with DiagnosticListener + +**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions + +This walkthrough shows how to instrument code with . +It then moves on to explain how consumes the data by first +finding the specified DiagnosticListeners one is interested in, subscribing to them, and decoding payloads for more specific data. +It finishes by describing filtering, which controls which events pass through the system. + +------------------------------------------- + +## Instrumenting with DiagnosticSource/DiagnosticListener + +We show how to instrument DiagnosticListener using the DiagnosticListener we made in the [Getting Started Guide](/docs/core/diagnostics/diagnosticsource-getting-started). + +The `DiagnosticSource` logging +interface consists of two methods: + +```C# + bool IsEnabled(string name) + void Write(string name, object value); +``` + +A typical call site will look like: + +```C# + if (httpLogger.IsEnabled("RequestStart")) + httpLogger.Write("RequestStart", new { Url="http://clr", Request=aRequest }); +``` + +Architectural elements shown in the above code are as follows. +Every event has a `string` name (e.g. `RequestStart`), and exactly one `object` as a payload. +If you need to send more than one item, you can do so by creating an `object` with all information +in it as properties. C#'s [anonymous type](https://msdn.microsoft.com/library/bb397696.aspx) +feature is typically used to create a type to pass 'on the fly', and makes this scheme very +convenient. At the instrumentation site, you must guard the call to `Write()` with an `IsEnabled()` check on +the same event name. Without this check, even when the instrumentation is inactive, the rules +of the C# language require all the work of creating the payload `object` and calling `Write()` to be +done, even though nothing is actually listening for the data. By guarding the `Write()` call, we +make it efficient when the source is not enabled. + +------------------------------------------- + +## Consuming Data with DiagnosticListener + +Up until now, this guide has focused on how to instrument code to generate logging +information. In this section we focus on subscribing and decoding of that information. + +### Discovery of DiagnosticListeners + +The first step in receiving events is to discover which `DiagnosticListeners` you are +interested in. `DiagnosticListener` supports a way of discovering `DiagnosticListeners` that are +active in the system at runtime. The API to accomplish this is the `AllListeners` +`IObservable`. + +The `IObservable` interface is the 'callback' version of the `IEnumerable` interface. You can learn +more about it at the [Reactive Extensions](https://msdn.microsoft.com/data/gg577609.aspx) site. +An `IObserver` has three callbacks, `OnNext`, `OnComplete` +and `OnError`, and an `IObservable` has single method called `Subscribe` which gets passed one of these +Observers. Once connected, the Observer gets callbacks (mostly `OnNext` callbacks) when things +happen. By including the `System.Reactive.Core` NuGet package, you can gain access to extensions that work well with `IObservable`. + +A typical use of the `AllListeners` static property looks like this: + +```C# + // We are using AllListeners to turn an Action into an IObserver + static IDisposable listenerSubscription = DiagnosticListener.AllListeners.Subscribe(delegate (DiagnosticListener listener) + { + // We get a callback of every Diagnostics Listener that is active in the system (past present or future) + if (listener.Name == "System.Net.Http") + { + // Here is where we put code to subscribe to the Listener. + } + }); + + // Typically you leave the listenerSubscription subscription active forever. + // However when you no longer want your callback to be called, you can + // call listenerSubscription.Dispose() to cancel your subscription to the IObservable. +``` + +This code creates a callback delegate and using the `AllListeners.Subscribe` method requests +that the delegate be called for every active `DiagnosticListener` in the system. The decision of whether or not to subscribe to the listener +is made by inspecting its name. The code above is looking for our 'System.Net.Http' listener that we created previously. + +Like all calls to `Subscribe()`, this one returns an `IDisposable` that represents the subscription itself. +Callbacks will continue to happen as long as nothing calls `Dispose()` on this subscription object. +The above code never calls `Dispose()`, so it will receive callbacks forever. + +When you subscribe to `AllListeners`, you get a callback for ALL ACTIVE `DiagnosticListeners`. +Thus, upon subscribing, you get a flurry of callbacks for all existing `DiagnosticListeners`, and as new ones +are created, you receive a callback for those as well. You receive a complete list of everything it is possible +to subscribe to. + +Finally, note that the code above is taking advantage of convenience functionality in the `System.Reactive.Core` +library. The `DiagnosticListener.AllListeners.Subscribe` method actually requires that it be passed +an `IObserver`, which is a class that has three callbacks (`OnNext`, `OnError`, `OnComplete`), +but we passed it an `Action`. An extension method +in `System.Reactive.Core` that takes the `Action` and from it makes an `IObserver` (called `AnonymousObserver`) +which calls the `Action` in its `OnNext` callback is what makes this work and the code concise. + +#### Subscribing to DiagnosticListeners + +A `DiagnosticListener` implements the `IObservable>` interface, so you can +call `Subscribe()` on it as well. We show how to fill out the previous example: + +```C# + static IDisposable networkSubscription = null; + static IDisposable listenerSubscription = DiagnosticListener.AllListeners.Subscribe(delegate (DiagnosticListener listener) + { + if (listener.Name == "System.Net.Http") + { + lock(allListeners) + { + if (networkSubscription != null) + networkSubscription.Dispose(); + + networkSubscription = listener.Subscribe((KeyValuePair evnt) => + Console.WriteLine("From Listener {0} Received Event {1} with payload {2}", + networkListener.Name, evnt.Key, evnt.Value.ToString())); + } + } + }); + + // At some point you may wish to dispose the networkSubscription. +``` + +In the above example, after finding the 'System.Net.Http' `DiagnosticListener`, an action is created that +prints out the name of the listener, event, and `payload.ToString()`. Please note the following. + +`DiagnosticListener` implements `IObservable>`. This means + on each callback we get a `KeyValuePair`. The key of this pair is the name of the event + and the value is the payload `object`. In the code above we simply log this information + to the console. + +It is important to keep track of subscriptions to the `DiagnosticListener`. In the above code the +networkSubscription variable that remembers this. If another `creation` is formed, one must +unsubscribe the previous listener and subscribe to the new one. + +The `DiagnosticSource`/`DiagnosticListener` code is thread safe, but the +callback code also needs to be thread safe. To ensure the callback code is thread safe, locks are used. It is possible to create two `DiagnosticListeners` +with the same name at the same time. To avoid races updates of shared variables are performed under the protection of a lock. + +Once the above code is run, the next time a `Write()` is done on 'System.Net.Http' `DiagnosticListener` +the information will be logged to the console. + +Subscriptions are independent of one another. As a result, other code +can do exactly the same thing as the code above, and generate two 'pipes' of the logging +information. + +#### Decoding Payloads + +The `KeyvaluePair` that is passed to the callback has the event name and payload, but the payload is typed simply as +an `object`. There are two ways of getting more specific data: + +1. If the payload is a well known type (e.g. a `string`, or an `HttpMessageRequest`) then you can simply + cast the `object` to the expected type (using the `as` operator so as not to cause an exception if + you are wrong) and then access the fields. This is very efficient. + +2. Use reflection API. For example, assume the method below is present. + +```C# + /// Define a shortcut method that fetches a field of a particular name. + static class PropertyExtensions + { + static object GetProperty(this object _this, string propertyName) + { + return _this.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(_this); + } + } +``` + +The `listener.Subscribe()` call above could be replaced with the following code, to decode the payload more fully. + +```C# + networkSubscription = listener.Subscribe(delegate(KeyValuePair evnt) { + var eventName = evnt.Key; + var payload = evnt.Value; + if (eventName == "RequestStart") + { + var url = payload.GetProperty("Url") as string; + var request = payload.GetProperty("Request"); + Console.WriteLine("Got RequestStart with URL {0} and Request {1}", url, request); + } + }); +``` + +Note that using reflection is relatively expensive. However, using reflection is the only +option if the payloads were generated using anonymous types. This overhead can be reduced by +making fast, specialized property fetchers either using `PropertyInfo.CreateDelegate` or +`ReflectEmit`, but that is beyond the scope of this document. +(See the [PropertySpec](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs#L784) +class used in the `DiagnosticSourceEventSource` for an example of a fast, delegate-based property fetcher.) + +#### Filtering + +In the example above, the code uses the `IObservable.Subscribe()` method to hook up the callback. This +causes all events to be given to the callback. However `DiagnosticListener` has overloads of +`Subscribe()` that allow the controller to control which events are given. + +The `listener.Subscribe()` call in the previous example can be replaced with the following code to demonstrate. + +```C# + // Create the callback delegate + Action> callback = (KeyValuePair evnt) => + Console.WriteLine("From Listener {0} Received Event {1} with payload {2}", networkListener.Name, evnt.Key, evnt.Value.ToString()); + + // Turn it into an observer (using System.Reactive.Core's AnonymousObserver) + Observer> observer = new AnonymousObserver>(callback); + + // Create a predicate (asks only for one kind of event) + Predicate predicate = (string eventName) => eventName == "RequestStart"; + + // Subscribe with a filter predicate + IDisposable subscription = listener.Subscribe(observer, predicate); + + // subscription.Dispose() to stop the callbacks. +``` + +This very efficiently subscribes to only the 'RequestStart' events. All other events will cause the `DiagnosticSource.IsEnabled()` +method to return `false`, and thus be efficiently filtered out. + +##### Context-based Filtering + +Some scenarios require advanced filtering based on extended context. +Producers may call `DiagnosticSource.IsEnabled()` overloads and supply additional event properties as shown in the code below. + +```C# + if (httpLogger.IsEnabled("RequestStart", aRequest, anActivity)) + httpLogger.Write("RequestStart", new { Url="http://clr", Request=aRequest }); +``` + +The next code demonstrates that consumers may use such properties to filter events more precisely: + +```C# + // Create a predicate (asks only for Requests for certains URIs) + Func predicate = (string eventName, object context, object activity) => + { + if (eventName == "RequestStart") + { + HttpRequestMessage request = context as HttpRequestMessage; + if (request != null) + { + return IsUriEnabled(request.RequestUri); + } + } + return false; + } + + // Subscribe with a filter predicate + IDisposable subscription = listener.Subscribe(observer, predicate); +``` + +Note that producers are not aware of the filter a consumer has provided. `DiagnosticListener` +will invoke the provided filter, omitting additional arguments if necessary, thus the filter +should expect to receive a `null` context. +Producers should enclose `IsEnabled()` calls with event name and context with pure `IsEnabled()` +calls for event name, so consumers must ensure that their filter allows events without context +to pass through. From 3cdb16cd78f286ae1e7e25468481b25afdcfcb18 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Tue, 3 May 2022 13:49:27 -0600 Subject: [PATCH 04/28] Update diagnosticsource.md --- docs/core/diagnostics/diagnosticsource.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/diagnostics/diagnosticsource.md b/docs/core/diagnostics/diagnosticsource.md index 445f8c9afd8fa..5ad47c47d27e1 100644 --- a/docs/core/diagnostics/diagnosticsource.md +++ b/docs/core/diagnostics/diagnosticsource.md @@ -7,7 +7,7 @@ ms.date: 05/03/2022 **This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions - is a simple module that allows code to be instrumented for production-time + is a module that allows code to be instrumented for production-time logging of rich data payloads for consumption within the process that was instrumented. At runtime, consumers can dynamically discover data sources and subscribe to the ones of interest. was designed to allow in-process tools to access rich data. When using , the consumer is assumed From 48c76a0b5e9bef3667608eb5fe7bf1b3cad7d451 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Tue, 3 May 2022 13:53:09 -0600 Subject: [PATCH 05/28] Fixing lint failures --- docs/core/diagnostics/diagnosticsource-getting-started.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/core/diagnostics/diagnosticsource-getting-started.md b/docs/core/diagnostics/diagnosticsource-getting-started.md index 837ed1bf21b57..0cdd998aed91a 100644 --- a/docs/core/diagnostics/diagnosticsource-getting-started.md +++ b/docs/core/diagnostics/diagnosticsource-getting-started.md @@ -7,7 +7,7 @@ ms.date: 05/02/2022 **This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions -This walkthrough shows +This walkthrough shows ## Log an event @@ -18,7 +18,8 @@ The first step in instrumenting code with `DiagnosticSource` is to create a ```C# private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); ``` -Notice that httpLogger is typed as a `DiagnosticSource`. + +Notice that httpLogger is typed as a `DiagnosticSource`. This is because this code only cares about writing events and thus only cares about the `DiagnosticSource` methods that the `DiagnosticListener` implements. `DiagnosticListeners` are given names when they are created From 012a00dfbe7034e4ea741c174275dd872676b88e Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Thu, 5 May 2022 17:34:29 -0600 Subject: [PATCH 06/28] Fix up the code and finish up the documentation --- .../diagnosticsource-diagnosticlistener.md | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 docs/core/diagnostics/diagnosticsource-diagnosticlistener.md diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md new file mode 100644 index 0000000000000..b7342c47f176a --- /dev/null +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -0,0 +1,327 @@ +--- +title: DiagnosticSource and DiagnosticListener +description: An overview of DiagnosticSource/DiagnosticListener including guidance on logging events, instrumenting code, and consuming data. +ms.date: 05/03/2022 +--- +# DiagnosticSource and DiagnosticListener + +**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions + + is a module that allows code to be instrumented for production-time +logging of rich data payloads for consumption within the process that was instrumented. At runtime, consumers can dynamically discover +data sources and subscribe to the ones of interest. was designed to allow in-process +tools to access rich data. When using , the consumer is assumed +to be within the same process and as a result, non-serializable types (e.g. `HttpResponseMessage` or `HttpContext`) can be passed, +giving customers plenty of data to work with. + +> [!NOTE] +> Many technologies that integrate with DiagnosticSource use the terms 'Tracing' and 'Traces' instead of 'Logging' and 'Logs'. +> The meaning is the same here. + +## Getting Started with DiagnosticSource + + +This walkthrough shows how to create a DiagnosticSource event and instrument code with . +It then explains how consumes the data by first +finding the specified DiagnosticListeners one is interested in, subscribing to them, and decoding payloads for more specific data. +It finishes by describing filtering, which controls which events pass through the system. + +## Log an event + +The `DiagnosticSource` type is an abstract base class that defines the methods needed to log events. The class that holds the implementation is `DiagnosticListener`. +The first step in instrumenting code with `DiagnosticSource` is to create a +`DiagnosticListener`. For example: + +```C# + private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); +``` + +Notice that httpLogger is typed as a `DiagnosticSource`. +This is because this code +only cares about writing events and thus only cares about the `DiagnosticSource` methods that +the `DiagnosticListener` implements. `DiagnosticListeners` are given names when they are created +and this name should be the name of logical grouping of related events (typically the component). +Later this name is used to find the Listener and subscribe to any of its events. `DiagnosticListeners` have a name, which is used to represent the component associated with the event. +Thus the event names only need to be unique within a component. + +------------------------------------------- + +## Instrumenting with DiagnosticSource/DiagnosticListener + +We show how to instrument DiagnosticListener using the DiagnosticListener we made in the code above. + +The `DiagnosticSource` logging +interface consists of two methods: + +```C# + bool IsEnabled(string name) + void Write(string name, object value); +``` + +A typical call site will look like: + +```C# + if (httpLogger.IsEnabled("RequestStart")) + httpLogger.Write("RequestStart", new { Url="http://clr", }); //any object can be the second argument +``` + +Architectural elements shown in the above code are as follows. +Every event has a `string` name (e.g. `RequestStart`), and exactly one `object` as a payload. +If you need to send more than one item, you can do so by creating an `object` with all information +in it as properties. C#'s [anonymous type](https://docs.microsoft.com/dotnet/csharp/fundamentals/types/anonymous-types) +feature is typically used to create a type to pass 'on the fly', and makes this scheme very +convenient. At the instrumentation site, you must guard the call to `Write()` with an `IsEnabled()` check on +the same event name. Without this check, even when the instrumentation is inactive, the rules +of the C# language require all the work of creating the payload `object` and calling `Write()` to be +done, even though nothing is actually listening for the data. By guarding the `Write()` call, we +make it efficient when the source is not enabled. + +Create a class to house the `DiagnosticListener and call site: + +```C# + class MessageSender + { + private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); + + public void SendMessage() + { + if (httpLogger.IsEnabled("RequestStart")) + { + httpLogger.Write("RequestStart", new + { + Url = "http://clr", + }); + } + + } + } +``` + +------------------------------------------- + +### Discovery of DiagnosticListeners + +The first step in receiving events is to discover which `DiagnosticListeners` you are +interested in. `DiagnosticListener` supports a way of discovering `DiagnosticListeners` that are +active in the system at runtime. The API to accomplish this is the `AllListeners` +`IObservable`. + +The `IObservable` interface is the 'callback' version of the `IEnumerable` interface. You can learn +more about it at the [Reactive Extensions](https://docs.microsoft.com/dotnet/csharp/fundamentals/types/anonymous-types) site. +An `IObserver` has three callbacks, `OnNext`, `OnComplete` +and `OnError`, and an `IObservable` has single method called `Subscribe` which gets passed one of these +Observers. Once connected, the Observer gets callbacks (mostly `OnNext` callbacks) when things +happen. By including the `System.Reactive.Core` NuGet package, you can gain access to extensions that work well with `IObservable`. + +A typical use of the `AllListeners` static property looks like this: + +```C# + class Observer : IObserver + { + public Observer(Action onNext, Action onCompleted) + { + _onNext = onNext ?? new Action(_ => { }); + _onCompleted = onCompleted ?? new Action(() => { }); + } + + public void OnCompleted() { _onCompleted(); } + public void OnError(Exception error) { } + public void OnNext(T value) { _onNext(value); } + + private Action _onNext; + private Action _onCompleted; + } + + Action onNewListener = delegate (DiagnosticListener listener) + { + Console.WriteLine($"New Listener discovered: {listener.Name}"); + if (listener.Name == "System.Net.Http") + { + // Here is where we put code to subscribe to the Listener. + } + }; + IObserver TheIObserver = new Observer(onNewListener, null); + //when a listener is created, invoke the Observer onNext function which calls the delegate + listenerSubscription = DiagnosticListener.AllListeners.Subscribe(TheIObserver); + + // Typically you leave the listenerSubscription subscription active forever. + // However when you no longer want your callback to be called, you can + // call listenerSubscription.Dispose() to cancel your subscription to the IObservable. +``` + +This code creates a callback delegate and using the `AllListeners.Subscribe` method requests +that the delegate be called for every active `DiagnosticListener` in the system. The decision of whether or not to subscribe to the listener +is made by inspecting its name. The code above is looking for our 'System.Net.Http' listener that we created previously. + +Like all calls to `Subscribe()`, this one returns an `IDisposable` that represents the subscription itself. +Callbacks will continue to happen as long as nothing calls `Dispose()` on this subscription object. +The above code never calls `Dispose()`, so it will receive callbacks forever. + +When you subscribe to `AllListeners`, you get a callback for ALL ACTIVE `DiagnosticListeners`. +Thus, upon subscribing, you get a flurry of callbacks for all existing `DiagnosticListeners`, and as new ones +are created, you receive a callback for those as well. You receive a complete list of everything it is possible +to subscribe to. + +#### Subscribing to DiagnosticListeners + +A `DiagnosticListener` implements the `IObservable>` interface, so you can +call `Subscribe()` on it as well. We show how to fill out the previous example: + +```C# + static IDisposable networkSubscription = null; + static IDisposable listenerSubscription; + Action> onMessage = delegate (KeyValuePair message) + { + Console.WriteLine($"Message received: {message.Key}: {message.Value}"); + }; + Action onNewListener = delegate (DiagnosticListener listener) + { + if (listener.Name == "System.Net.Http") + { + lock(allListeners) + { + if (networkSubscription != null) + { + networkSubscription.Dispose(); + } + IObserver> iobserver = new Observer>(onMessage, null); + networkSubscription = listener.Subscribe(iobserver); + } + } + }; + + // At some point you may wish to dispose the networkSubscription. +``` + +In the above example, after finding the 'System.Net.Http' `DiagnosticListener`, an action is created that +prints out the name of the listener, event, and `payload.ToString()`. Please note the following. + +`DiagnosticListener` implements `IObservable>`. This means + on each callback we get a `KeyValuePair`. The key of this pair is the name of the event + and the value is the payload `object`. In the code above we simply log this information + to the console. + +It is important to keep track of subscriptions to the `DiagnosticListener`. In the above code the +networkSubscription variable that remembers this. If another `creation` is formed, one must +unsubscribe the previous listener and subscribe to the new one. + +The `DiagnosticSource`/`DiagnosticListener` code is thread safe, but the +callback code also needs to be thread safe. To ensure the callback code is thread safe, locks are used. It is possible to create two `DiagnosticListeners` +with the same name at the same time. To avoid races updates of shared variables are performed under the protection of a lock. + +Once the above code is run, the next time a `Write()` is done on 'System.Net.Http' `DiagnosticListener` +the information will be logged to the console. + +Subscriptions are independent of one another. As a result, other code +can do exactly the same thing as the code above, and generate two 'pipes' of the logging +information. + +#### Decoding Payloads + +The `KeyvaluePair` that is passed to the callback has the event name and payload, but the payload is typed simply as +an `object`. There are two ways of getting more specific data: + +1. If the payload is a well known type (e.g. a `string`, or an `HttpMessageRequest`) then you can simply + cast the `object` to the expected type (using the `as` operator so as not to cause an exception if + you are wrong) and then access the fields. This is very efficient. + +2. Use reflection API. For example, assume the method below is present. + +```C# + /// Define a shortcut method that fetches a field of a particular name. + static class PropertyExtensions + { + static object GetProperty(this object _this, string propertyName) + { + return _this.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(_this); + } + } +``` + +The `listener.Subscribe()` call above could be replaced with the following code, to decode the payload more fully. + +```C# + networkSubscription = listener.Subscribe(delegate(KeyValuePair evnt) { + var eventName = evnt.Key; + var payload = evnt.Value; + if (eventName == "RequestStart") + { + var url = payload.GetProperty("Url") as string; + var request = payload.GetProperty("Request"); + Console.WriteLine("Got RequestStart with URL {0} and Request {1}", url, request); + } + }); +``` + +Note that using reflection is relatively expensive. However, using reflection is the only +option if the payloads were generated using anonymous types. This overhead can be reduced by +making fast, specialized property fetchers either using `PropertyInfo.CreateDelegate` or +`ReflectEmit`, but that is beyond the scope of this document. +(See the [PropertySpec](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs#L784) +class used in the `DiagnosticSourceEventSource` for an example of a fast, delegate-based property fetcher.) + +#### Filtering + +In the example above, the code uses the `IObservable.Subscribe()` method to hook up the callback. This +causes all events to be given to the callback. However `DiagnosticListener` has overloads of +`Subscribe()` that allow the controller to control which events are given. + +The `listener.Subscribe()` call in the previous example can be replaced with the following code to demonstrate. + +```C# + // Create the callback delegate + Action> callback = (KeyValuePair evnt) => + Console.WriteLine("From Listener {0} Received Event {1} with payload {2}", networkListener.Name, evnt.Key, evnt.Value.ToString()); + + // Turn it into an observer (using System.Reactive.Core's AnonymousObserver) + Observer> observer = new AnonymousObserver>(callback); + + // Create a predicate (asks only for one kind of event) + Predicate predicate = (string eventName) => eventName == "RequestStart"; + + // Subscribe with a filter predicate + IDisposable subscription = listener.Subscribe(observer, predicate); + + // subscription.Dispose() to stop the callbacks. +``` + +This very efficiently subscribes to only the 'RequestStart' events. All other events will cause the `DiagnosticSource.IsEnabled()` +method to return `false`, and thus be efficiently filtered out. + +##### Context-based Filtering + +Some scenarios require advanced filtering based on extended context. +Producers may call `DiagnosticSource.IsEnabled()` overloads and supply additional event properties as shown in the code below. + +```C# + if (httpLogger.IsEnabled("RequestStart", aRequest, anActivity)) + httpLogger.Write("RequestStart", new { Url="http://clr", Request=aRequest }); +``` + +The next code demonstrates that consumers may use such properties to filter events more precisely: + +```C# + // Create a predicate (asks only for Requests for certains URIs) + Func predicate = (string eventName, object context, object activity) => + { + if (eventName == "RequestStart") + { + HttpRequestMessage request = context as HttpRequestMessage; + if (request != null) + { + return IsUriEnabled(request.RequestUri); + } + } + return false; + } + + // Subscribe with a filter predicate + IDisposable subscription = listener.Subscribe(observer, predicate); +``` + +Note that producers are not aware of the filter a consumer has provided. `DiagnosticListener` +will invoke the provided filter, omitting additional arguments if necessary, thus the filter +should expect to receive a `null` context. +Producers should enclose `IsEnabled()` calls with event name and context with pure `IsEnabled()` +calls for event name, so consumers must ensure that their filter allows events without context +to pass through. From 14cd364ea6481e260656fcf2a6ce0942d5dd3868 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Thu, 5 May 2022 17:35:36 -0600 Subject: [PATCH 07/28] Not needed --- .../diagnosticsource-getting-started.md | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 docs/core/diagnostics/diagnosticsource-getting-started.md diff --git a/docs/core/diagnostics/diagnosticsource-getting-started.md b/docs/core/diagnostics/diagnosticsource-getting-started.md deleted file mode 100644 index 0cdd998aed91a..0000000000000 --- a/docs/core/diagnostics/diagnosticsource-getting-started.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Getting Started with DiagnosticSource -description: A tutorial to create a basic DiagnosticSource and understand key concepts -ms.date: 05/02/2022 ---- -# Getting Started with DiagnosticSource - -**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions - -This walkthrough shows - -## Log an event - -The `DiagnosticSource` type is an abstract base class that defines the methods needed to log events. The class that holds the implementation is `DiagnosticListener`. -The first step in instrumenting code with `DiagnosticSource` is to create a -`DiagnosticListener`. For example: - -```C# - private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); -``` - -Notice that httpLogger is typed as a `DiagnosticSource`. -This is because this code -only cares about writing events and thus only cares about the `DiagnosticSource` methods that -the `DiagnosticListener` implements. `DiagnosticListeners` are given names when they are created -and this name should be the name of logical grouping of related events (typically the component). -Later this name is used to find the Listener and subscribe to any of its events. `DiagnosticListeners` have a name, which is used to represent the component associated with the event. -Thus the event names only need to be unique within a component. From 0e8014282b249d27dff1f0da1e2a02b2bed03e5e Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Thu, 5 May 2022 17:36:07 -0600 Subject: [PATCH 08/28] Update the date --- docs/core/diagnostics/diagnosticsource-diagnosticlistener.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index b7342c47f176a..07fd47618e847 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -1,7 +1,7 @@ --- title: DiagnosticSource and DiagnosticListener description: An overview of DiagnosticSource/DiagnosticListener including guidance on logging events, instrumenting code, and consuming data. -ms.date: 05/03/2022 +ms.date: 05/05/2022 --- # DiagnosticSource and DiagnosticListener From f530c99a1041db847c35b429e371cb4fb5285e1b Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Thu, 5 May 2022 17:36:43 -0600 Subject: [PATCH 09/28] Combine into one file --- ...osticsource-instrumentation-consumption.md | 263 ------------------ 1 file changed, 263 deletions(-) delete mode 100644 docs/core/diagnostics/diagnosticsource-instrumentation-consumption.md diff --git a/docs/core/diagnostics/diagnosticsource-instrumentation-consumption.md b/docs/core/diagnostics/diagnosticsource-instrumentation-consumption.md deleted file mode 100644 index 0c3734d7087d3..0000000000000 --- a/docs/core/diagnostics/diagnosticsource-instrumentation-consumption.md +++ /dev/null @@ -1,263 +0,0 @@ ---- -title: Instrument Code to Create DiagnosticSource Events and Consume Data with DiagnosticListener -description: A tutorial for instrumenting code with DiagnosticSource and consuming data with DiagnosticListener -ms.date: 05/02/2022 ---- -# Instrument Code to Create DiagnosticSource Events and Consume Data with DiagnosticListener - -**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions - -This walkthrough shows how to instrument code with . -It then moves on to explain how consumes the data by first -finding the specified DiagnosticListeners one is interested in, subscribing to them, and decoding payloads for more specific data. -It finishes by describing filtering, which controls which events pass through the system. - -------------------------------------------- - -## Instrumenting with DiagnosticSource/DiagnosticListener - -We show how to instrument DiagnosticListener using the DiagnosticListener we made in the [Getting Started Guide](/docs/core/diagnostics/diagnosticsource-getting-started). - -The `DiagnosticSource` logging -interface consists of two methods: - -```C# - bool IsEnabled(string name) - void Write(string name, object value); -``` - -A typical call site will look like: - -```C# - if (httpLogger.IsEnabled("RequestStart")) - httpLogger.Write("RequestStart", new { Url="http://clr", Request=aRequest }); -``` - -Architectural elements shown in the above code are as follows. -Every event has a `string` name (e.g. `RequestStart`), and exactly one `object` as a payload. -If you need to send more than one item, you can do so by creating an `object` with all information -in it as properties. C#'s [anonymous type](https://msdn.microsoft.com/library/bb397696.aspx) -feature is typically used to create a type to pass 'on the fly', and makes this scheme very -convenient. At the instrumentation site, you must guard the call to `Write()` with an `IsEnabled()` check on -the same event name. Without this check, even when the instrumentation is inactive, the rules -of the C# language require all the work of creating the payload `object` and calling `Write()` to be -done, even though nothing is actually listening for the data. By guarding the `Write()` call, we -make it efficient when the source is not enabled. - -------------------------------------------- - -## Consuming Data with DiagnosticListener - -Up until now, this guide has focused on how to instrument code to generate logging -information. In this section we focus on subscribing and decoding of that information. - -### Discovery of DiagnosticListeners - -The first step in receiving events is to discover which `DiagnosticListeners` you are -interested in. `DiagnosticListener` supports a way of discovering `DiagnosticListeners` that are -active in the system at runtime. The API to accomplish this is the `AllListeners` -`IObservable`. - -The `IObservable` interface is the 'callback' version of the `IEnumerable` interface. You can learn -more about it at the [Reactive Extensions](https://msdn.microsoft.com/data/gg577609.aspx) site. -An `IObserver` has three callbacks, `OnNext`, `OnComplete` -and `OnError`, and an `IObservable` has single method called `Subscribe` which gets passed one of these -Observers. Once connected, the Observer gets callbacks (mostly `OnNext` callbacks) when things -happen. By including the `System.Reactive.Core` NuGet package, you can gain access to extensions that work well with `IObservable`. - -A typical use of the `AllListeners` static property looks like this: - -```C# - // We are using AllListeners to turn an Action into an IObserver - static IDisposable listenerSubscription = DiagnosticListener.AllListeners.Subscribe(delegate (DiagnosticListener listener) - { - // We get a callback of every Diagnostics Listener that is active in the system (past present or future) - if (listener.Name == "System.Net.Http") - { - // Here is where we put code to subscribe to the Listener. - } - }); - - // Typically you leave the listenerSubscription subscription active forever. - // However when you no longer want your callback to be called, you can - // call listenerSubscription.Dispose() to cancel your subscription to the IObservable. -``` - -This code creates a callback delegate and using the `AllListeners.Subscribe` method requests -that the delegate be called for every active `DiagnosticListener` in the system. The decision of whether or not to subscribe to the listener -is made by inspecting its name. The code above is looking for our 'System.Net.Http' listener that we created previously. - -Like all calls to `Subscribe()`, this one returns an `IDisposable` that represents the subscription itself. -Callbacks will continue to happen as long as nothing calls `Dispose()` on this subscription object. -The above code never calls `Dispose()`, so it will receive callbacks forever. - -When you subscribe to `AllListeners`, you get a callback for ALL ACTIVE `DiagnosticListeners`. -Thus, upon subscribing, you get a flurry of callbacks for all existing `DiagnosticListeners`, and as new ones -are created, you receive a callback for those as well. You receive a complete list of everything it is possible -to subscribe to. - -Finally, note that the code above is taking advantage of convenience functionality in the `System.Reactive.Core` -library. The `DiagnosticListener.AllListeners.Subscribe` method actually requires that it be passed -an `IObserver`, which is a class that has three callbacks (`OnNext`, `OnError`, `OnComplete`), -but we passed it an `Action`. An extension method -in `System.Reactive.Core` that takes the `Action` and from it makes an `IObserver` (called `AnonymousObserver`) -which calls the `Action` in its `OnNext` callback is what makes this work and the code concise. - -#### Subscribing to DiagnosticListeners - -A `DiagnosticListener` implements the `IObservable>` interface, so you can -call `Subscribe()` on it as well. We show how to fill out the previous example: - -```C# - static IDisposable networkSubscription = null; - static IDisposable listenerSubscription = DiagnosticListener.AllListeners.Subscribe(delegate (DiagnosticListener listener) - { - if (listener.Name == "System.Net.Http") - { - lock(allListeners) - { - if (networkSubscription != null) - networkSubscription.Dispose(); - - networkSubscription = listener.Subscribe((KeyValuePair evnt) => - Console.WriteLine("From Listener {0} Received Event {1} with payload {2}", - networkListener.Name, evnt.Key, evnt.Value.ToString())); - } - } - }); - - // At some point you may wish to dispose the networkSubscription. -``` - -In the above example, after finding the 'System.Net.Http' `DiagnosticListener`, an action is created that -prints out the name of the listener, event, and `payload.ToString()`. Please note the following. - -`DiagnosticListener` implements `IObservable>`. This means - on each callback we get a `KeyValuePair`. The key of this pair is the name of the event - and the value is the payload `object`. In the code above we simply log this information - to the console. - -It is important to keep track of subscriptions to the `DiagnosticListener`. In the above code the -networkSubscription variable that remembers this. If another `creation` is formed, one must -unsubscribe the previous listener and subscribe to the new one. - -The `DiagnosticSource`/`DiagnosticListener` code is thread safe, but the -callback code also needs to be thread safe. To ensure the callback code is thread safe, locks are used. It is possible to create two `DiagnosticListeners` -with the same name at the same time. To avoid races updates of shared variables are performed under the protection of a lock. - -Once the above code is run, the next time a `Write()` is done on 'System.Net.Http' `DiagnosticListener` -the information will be logged to the console. - -Subscriptions are independent of one another. As a result, other code -can do exactly the same thing as the code above, and generate two 'pipes' of the logging -information. - -#### Decoding Payloads - -The `KeyvaluePair` that is passed to the callback has the event name and payload, but the payload is typed simply as -an `object`. There are two ways of getting more specific data: - -1. If the payload is a well known type (e.g. a `string`, or an `HttpMessageRequest`) then you can simply - cast the `object` to the expected type (using the `as` operator so as not to cause an exception if - you are wrong) and then access the fields. This is very efficient. - -2. Use reflection API. For example, assume the method below is present. - -```C# - /// Define a shortcut method that fetches a field of a particular name. - static class PropertyExtensions - { - static object GetProperty(this object _this, string propertyName) - { - return _this.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(_this); - } - } -``` - -The `listener.Subscribe()` call above could be replaced with the following code, to decode the payload more fully. - -```C# - networkSubscription = listener.Subscribe(delegate(KeyValuePair evnt) { - var eventName = evnt.Key; - var payload = evnt.Value; - if (eventName == "RequestStart") - { - var url = payload.GetProperty("Url") as string; - var request = payload.GetProperty("Request"); - Console.WriteLine("Got RequestStart with URL {0} and Request {1}", url, request); - } - }); -``` - -Note that using reflection is relatively expensive. However, using reflection is the only -option if the payloads were generated using anonymous types. This overhead can be reduced by -making fast, specialized property fetchers either using `PropertyInfo.CreateDelegate` or -`ReflectEmit`, but that is beyond the scope of this document. -(See the [PropertySpec](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs#L784) -class used in the `DiagnosticSourceEventSource` for an example of a fast, delegate-based property fetcher.) - -#### Filtering - -In the example above, the code uses the `IObservable.Subscribe()` method to hook up the callback. This -causes all events to be given to the callback. However `DiagnosticListener` has overloads of -`Subscribe()` that allow the controller to control which events are given. - -The `listener.Subscribe()` call in the previous example can be replaced with the following code to demonstrate. - -```C# - // Create the callback delegate - Action> callback = (KeyValuePair evnt) => - Console.WriteLine("From Listener {0} Received Event {1} with payload {2}", networkListener.Name, evnt.Key, evnt.Value.ToString()); - - // Turn it into an observer (using System.Reactive.Core's AnonymousObserver) - Observer> observer = new AnonymousObserver>(callback); - - // Create a predicate (asks only for one kind of event) - Predicate predicate = (string eventName) => eventName == "RequestStart"; - - // Subscribe with a filter predicate - IDisposable subscription = listener.Subscribe(observer, predicate); - - // subscription.Dispose() to stop the callbacks. -``` - -This very efficiently subscribes to only the 'RequestStart' events. All other events will cause the `DiagnosticSource.IsEnabled()` -method to return `false`, and thus be efficiently filtered out. - -##### Context-based Filtering - -Some scenarios require advanced filtering based on extended context. -Producers may call `DiagnosticSource.IsEnabled()` overloads and supply additional event properties as shown in the code below. - -```C# - if (httpLogger.IsEnabled("RequestStart", aRequest, anActivity)) - httpLogger.Write("RequestStart", new { Url="http://clr", Request=aRequest }); -``` - -The next code demonstrates that consumers may use such properties to filter events more precisely: - -```C# - // Create a predicate (asks only for Requests for certains URIs) - Func predicate = (string eventName, object context, object activity) => - { - if (eventName == "RequestStart") - { - HttpRequestMessage request = context as HttpRequestMessage; - if (request != null) - { - return IsUriEnabled(request.RequestUri); - } - } - return false; - } - - // Subscribe with a filter predicate - IDisposable subscription = listener.Subscribe(observer, predicate); -``` - -Note that producers are not aware of the filter a consumer has provided. `DiagnosticListener` -will invoke the provided filter, omitting additional arguments if necessary, thus the filter -should expect to receive a `null` context. -Producers should enclose `IsEnabled()` calls with event name and context with pure `IsEnabled()` -calls for event name, so consumers must ensure that their filter allows events without context -to pass through. From f1998d91d37e5a36cb99f48cda3083f403447957 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Thu, 5 May 2022 17:36:59 -0600 Subject: [PATCH 10/28] Not needed --- docs/core/diagnostics/diagnosticsource.md | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 docs/core/diagnostics/diagnosticsource.md diff --git a/docs/core/diagnostics/diagnosticsource.md b/docs/core/diagnostics/diagnosticsource.md deleted file mode 100644 index 5ad47c47d27e1..0000000000000 --- a/docs/core/diagnostics/diagnosticsource.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: DiagnosticSource -description: A guide to logging with DiagnosticSource -ms.date: 05/03/2022 ---- -# DiagnosticSource - -**This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions - - is a module that allows code to be instrumented for production-time -logging of rich data payloads for consumption within the process that was instrumented. At runtime, consumers can dynamically discover -data sources and subscribe to the ones of interest. was designed to allow in-process -tools to access rich data. When using , the consumer is assumed -to be within the same process and as a result, non-serializable types (e.g. `HttpResponseMessage` or `HttpContext`) can be passed, -giving customers plenty of data to work with. - -> [!NOTE] -> Many technologies that integrate with DiagnosticSource use the terms 'Tracing' and 'Traces' instead of 'Logging' and 'Logs'. -> The meaning is the same here. - -- [Getting started](./diagnosticsource-getting-started.md) -- [Instrumenting code to create events and consuming data](./diagnosticsource-instrumentation-consumption.md) From 7bd10107b227dfe818258008cd7c535cba54ac86 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Thu, 5 May 2022 17:40:56 -0600 Subject: [PATCH 11/28] Remove whitespace --- docs/core/diagnostics/diagnosticsource-diagnosticlistener.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index 07fd47618e847..3967c1d283094 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -20,7 +20,6 @@ giving customers plenty of data to work with. ## Getting Started with DiagnosticSource - This walkthrough shows how to create a DiagnosticSource event and instrument code with . It then explains how consumes the data by first finding the specified DiagnosticListeners one is interested in, subscribing to them, and decoding payloads for more specific data. @@ -76,7 +75,7 @@ of the C# language require all the work of creating the payload `object` and cal done, even though nothing is actually listening for the data. By guarding the `Write()` call, we make it efficient when the source is not enabled. -Create a class to house the `DiagnosticListener and call site: +Create a class to house the `DiagnosticListener and call site: ```C# class MessageSender From 9e27b559621f1677cdcde7039be6d83b6fbb92bc Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Mon, 9 May 2022 10:42:14 -0600 Subject: [PATCH 12/28] Fixed suggestions from code review --- .../diagnosticsource-diagnosticlistener.md | 79 +++++++++++-------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index 3967c1d283094..0e35a90ce8bb7 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -14,19 +14,44 @@ tools to access rich data. When using [!NOTE] -> Many technologies that integrate with DiagnosticSource use the terms 'Tracing' and 'Traces' instead of 'Logging' and 'Logs'. -> The meaning is the same here. - ## Getting Started with DiagnosticSource This walkthrough shows how to create a DiagnosticSource event and instrument code with . -It then explains how consumes the data by first -finding the specified DiagnosticListeners one is interested in, subscribing to them, and decoding payloads for more specific data. -It finishes by describing filtering, which controls which events pass through the system. +It then explains how to consume the event by finding interesting DiagnosticListeners, subscribing to their events, and decoding event data payloads. +It finishes by describing filtering, the act of controlling which events pass through the system. ## Log an event +We will work with the following code. This code is an HttpClient class with a `SendWebRequest` method that sends an HTTP request to the url and receives a reply. + +```C# + // before adding logging + class HttpClient + { + byte[] SendWebRequest(string url) + { + // pretend this sends an HTTP request to the url and gets back a reply + byte[] reply = new byte[] {}; + return reply; + } + + // after adding logging + class HttpClient + { + private static DiagnosticSource httpLogger= new DiagnosticListener("System.Net.Http"); + public byte[] SendWebRequest(string url) + { + if (httpLogger.IsEnabled("RequestStart")) + { + httpLogger.Write("RequestStart", new { Url = url }); + } + // pretend this sends an HTTP request to the url and gets back a reply + byte[] reply = new byte[] {}; + return reply; + } + } +``` + The `DiagnosticSource` type is an abstract base class that defines the methods needed to log events. The class that holds the implementation is `DiagnosticListener`. The first step in instrumenting code with `DiagnosticSource` is to create a `DiagnosticListener`. For example: @@ -40,15 +65,11 @@ This is because this code only cares about writing events and thus only cares about the `DiagnosticSource` methods that the `DiagnosticListener` implements. `DiagnosticListeners` are given names when they are created and this name should be the name of logical grouping of related events (typically the component). -Later this name is used to find the Listener and subscribe to any of its events. `DiagnosticListeners` have a name, which is used to represent the component associated with the event. +Later this name is used to find the Listener and subscribe to any of its events. Thus the event names only need to be unique within a component. ------------------------------------------- -## Instrumenting with DiagnosticSource/DiagnosticListener - -We show how to instrument DiagnosticListener using the DiagnosticListener we made in the code above. - The `DiagnosticSource` logging interface consists of two methods: @@ -64,7 +85,6 @@ A typical call site will look like: httpLogger.Write("RequestStart", new { Url="http://clr", }); //any object can be the second argument ``` -Architectural elements shown in the above code are as follows. Every event has a `string` name (e.g. `RequestStart`), and exactly one `object` as a payload. If you need to send more than one item, you can do so by creating an `object` with all information in it as properties. C#'s [anonymous type](https://docs.microsoft.com/dotnet/csharp/fundamentals/types/anonymous-types) @@ -75,23 +95,21 @@ of the C# language require all the work of creating the payload `object` and cal done, even though nothing is actually listening for the data. By guarding the `Write()` call, we make it efficient when the source is not enabled. -Create a class to house the `DiagnosticListener and call site: +Combining everything we have: ```C# - class MessageSender + class HttpClient { - private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); - - public void SendMessage() + private static DiagnosticSource httpLogger= new DiagnosticListener("System.Net.Http"); + public byte[] SendWebRequest(string url) { if (httpLogger.IsEnabled("RequestStart")) { - httpLogger.Write("RequestStart", new - { - Url = "http://clr", - }); + httpLogger.Write("RequestStart", new { Url = url }); } - + // pretend this sends an HTTP request to the url and gets back a reply + byte[] reply = new byte[] {}; + return reply; } } ``` @@ -105,12 +123,11 @@ interested in. `DiagnosticListener` supports a way of discovering `DiagnosticLis active in the system at runtime. The API to accomplish this is the `AllListeners` `IObservable`. -The `IObservable` interface is the 'callback' version of the `IEnumerable` interface. You can learn -more about it at the [Reactive Extensions](https://docs.microsoft.com/dotnet/csharp/fundamentals/types/anonymous-types) site. +Implement an `Observer` class that inherits from the `IObservable` interface, which is the 'callback' version of the `IEnumerable` interface. You can learn more about it at the [Reactive Extensions](https://docs.microsoft.com/dotnet/csharp/fundamentals/types/anonymous-types) site. An `IObserver` has three callbacks, `OnNext`, `OnComplete` and `OnError`, and an `IObservable` has single method called `Subscribe` which gets passed one of these Observers. Once connected, the Observer gets callbacks (mostly `OnNext` callbacks) when things -happen. By including the `System.Reactive.Core` NuGet package, you can gain access to extensions that work well with `IObservable`. +happen. A typical use of the `AllListeners` static property looks like this: @@ -139,9 +156,9 @@ A typical use of the `AllListeners` static property looks like this: // Here is where we put code to subscribe to the Listener. } }; - IObserver TheIObserver = new Observer(onNewListener, null); + IObserver observer = new Observer(onNewListener, null); //when a listener is created, invoke the Observer onNext function which calls the delegate - listenerSubscription = DiagnosticListener.AllListeners.Subscribe(TheIObserver); + listenerSubscription = DiagnosticListener.AllListeners.Subscribe(observer); // Typically you leave the listenerSubscription subscription active forever. // However when you no longer want your callback to be called, you can @@ -201,7 +218,7 @@ prints out the name of the listener, event, and `payload.ToString()`. Please not to the console. It is important to keep track of subscriptions to the `DiagnosticListener`. In the above code the -networkSubscription variable that remembers this. If another `creation` is formed, one must +networkSubscription variable remembers this. If another `creation` is formed, one must unsubscribe the previous listener and subscribe to the new one. The `DiagnosticSource`/`DiagnosticListener` code is thread safe, but the @@ -256,7 +273,7 @@ Note that using reflection is relatively expensive. However, using reflection is option if the payloads were generated using anonymous types. This overhead can be reduced by making fast, specialized property fetchers either using `PropertyInfo.CreateDelegate` or `ReflectEmit`, but that is beyond the scope of this document. -(See the [PropertySpec](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs#L784) +(See the [PropertySpec](https://github.com/dotnet/runtime/blob/6de7147b9266d7730b0d73ba67632b0c198cb11e/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs#L1235) class used in the `DiagnosticSourceEventSource` for an example of a fast, delegate-based property fetcher.) #### Filtering @@ -272,7 +289,7 @@ The `listener.Subscribe()` call in the previous example can be replaced with the Action> callback = (KeyValuePair evnt) => Console.WriteLine("From Listener {0} Received Event {1} with payload {2}", networkListener.Name, evnt.Key, evnt.Value.ToString()); - // Turn it into an observer (using System.Reactive.Core's AnonymousObserver) + // Turn it into an observer (using the Observer Class above) Observer> observer = new AnonymousObserver>(callback); // Create a predicate (asks only for one kind of event) From 672fb4848ca7f1f16a46ce63c79caf7b63060b44 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Mon, 9 May 2022 10:45:01 -0600 Subject: [PATCH 13/28] Two additional edits --- docs/core/diagnostics/diagnosticsource-diagnosticlistener.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index 0e35a90ce8bb7..11642eb05aa44 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -1,7 +1,7 @@ --- title: DiagnosticSource and DiagnosticListener description: An overview of DiagnosticSource/DiagnosticListener including guidance on logging events, instrumenting code, and consuming data. -ms.date: 05/05/2022 +ms.date: 05/09/2022 --- # DiagnosticSource and DiagnosticListener @@ -290,7 +290,7 @@ The `listener.Subscribe()` call in the previous example can be replaced with the Console.WriteLine("From Listener {0} Received Event {1} with payload {2}", networkListener.Name, evnt.Key, evnt.Value.ToString()); // Turn it into an observer (using the Observer Class above) - Observer> observer = new AnonymousObserver>(callback); + Observer> observer = new Observer>(callback); // Create a predicate (asks only for one kind of event) Predicate predicate = (string eventName) => eventName == "RequestStart"; From 19ebd1ee9cd4f98406859624716d6154914a3f92 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Tue, 10 May 2022 10:27:42 -0600 Subject: [PATCH 14/28] Response to suggestions Use you instead of we, shorten sentences to help with localization, use active voice, comments should be capitalized and have punctuation. --- .../diagnosticsource-diagnosticlistener.md | 129 +++++++++--------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index 11642eb05aa44..4c216d4918ee5 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -8,24 +8,24 @@ ms.date: 05/09/2022 **This article applies to: ✔️** .NET Core 3.1 and later versions **✔️** .NET Framework 4.5 and later versions is a module that allows code to be instrumented for production-time -logging of rich data payloads for consumption within the process that was instrumented. At runtime, consumers can dynamically discover +logging of rich data payloads for consumption within the process that was instrumented. At run time, consumers can dynamically discover data sources and subscribe to the ones of interest. was designed to allow in-process tools to access rich data. When using , the consumer is assumed -to be within the same process and as a result, non-serializable types (e.g. `HttpResponseMessage` or `HttpContext`) can be passed, +to be within the same process and as a result, non-serializable types (for example, `HttpResponseMessage` or `HttpContext`) can be passed, giving customers plenty of data to work with. ## Getting Started with DiagnosticSource This walkthrough shows how to create a DiagnosticSource event and instrument code with . It then explains how to consume the event by finding interesting DiagnosticListeners, subscribing to their events, and decoding event data payloads. -It finishes by describing filtering, the act of controlling which events pass through the system. +It finishes by describing *filtering*, which allows only specific events to pass through the system. ## Log an event -We will work with the following code. This code is an HttpClient class with a `SendWebRequest` method that sends an HTTP request to the url and receives a reply. +You will work with the following code. This code is an *HttpClient* class with a `SendWebRequest` method that sends an HTTP request to the URL and receives a reply. ```C# - // before adding logging + // The class before adding logging. class HttpClient { byte[] SendWebRequest(string url) @@ -35,7 +35,7 @@ We will work with the following code. This code is an HttpClient class with a `S return reply; } - // after adding logging + // The class after adding logging. class HttpClient { private static DiagnosticSource httpLogger= new DiagnosticListener("System.Net.Http"); @@ -45,7 +45,7 @@ We will work with the following code. This code is an HttpClient class with a `S { httpLogger.Write("RequestStart", new { Url = url }); } - // pretend this sends an HTTP request to the url and gets back a reply + // Pretend this sends an HTTP request to the url and gets back a reply. byte[] reply = new byte[] {}; return reply; } @@ -57,15 +57,15 @@ The first step in instrumenting code with `DiagnosticSource` is to create a `DiagnosticListener`. For example: ```C# - private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); +private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); ``` -Notice that httpLogger is typed as a `DiagnosticSource`. -This is because this code -only cares about writing events and thus only cares about the `DiagnosticSource` methods that -the `DiagnosticListener` implements. `DiagnosticListeners` are given names when they are created -and this name should be the name of logical grouping of related events (typically the component). -Later this name is used to find the Listener and subscribe to any of its events. +Notice that `httpLogger` is typed as a `DiagnosticSource`. +That's because this code +only writes events and thus is only concerned with the `DiagnosticSource` methods that +the `DiagnosticListener` implements. `DiagnosticListeners` are given names when they are created, +and this name should be the name of a logical grouping of related events (typically the component). +Later, this name is used to find the Listener and subscribe to any of its events. Thus the event names only need to be unique within a component. ------------------------------------------- @@ -82,20 +82,19 @@ A typical call site will look like: ```C# if (httpLogger.IsEnabled("RequestStart")) - httpLogger.Write("RequestStart", new { Url="http://clr", }); //any object can be the second argument + httpLogger.Write("RequestStart", new { Url="http://clr", }); //Any object can be the second argument. ``` -Every event has a `string` name (e.g. `RequestStart`), and exactly one `object` as a payload. -If you need to send more than one item, you can do so by creating an `object` with all information -in it as properties. C#'s [anonymous type](https://docs.microsoft.com/dotnet/csharp/fundamentals/types/anonymous-types) +Every event has a `string` name (for example, `RequestStart`), and exactly one `object` as a payload. +If you need to send more than one item, you can do so by creating an `object` with properties to represent all its information. C#'s [anonymous type](../../csharp/fundamentals/types/anonymous-types.md) feature is typically used to create a type to pass 'on the fly', and makes this scheme very convenient. At the instrumentation site, you must guard the call to `Write()` with an `IsEnabled()` check on the same event name. Without this check, even when the instrumentation is inactive, the rules of the C# language require all the work of creating the payload `object` and calling `Write()` to be -done, even though nothing is actually listening for the data. By guarding the `Write()` call, we +done, even though nothing is actually listening for the data. By guarding the `Write()` call, you make it efficient when the source is not enabled. -Combining everything we have: +Combining everything you have: ```C# class HttpClient @@ -107,7 +106,7 @@ Combining everything we have: { httpLogger.Write("RequestStart", new { Url = url }); } - // pretend this sends an HTTP request to the url and gets back a reply + // Pretend this sends an HTTP request to the url and gets back a reply. byte[] reply = new byte[] {}; return reply; } @@ -120,12 +119,11 @@ Combining everything we have: The first step in receiving events is to discover which `DiagnosticListeners` you are interested in. `DiagnosticListener` supports a way of discovering `DiagnosticListeners` that are -active in the system at runtime. The API to accomplish this is the `AllListeners` -`IObservable`. +active in the system at run time. The API to accomplish this is the property. Implement an `Observer` class that inherits from the `IObservable` interface, which is the 'callback' version of the `IEnumerable` interface. You can learn more about it at the [Reactive Extensions](https://docs.microsoft.com/dotnet/csharp/fundamentals/types/anonymous-types) site. -An `IObserver` has three callbacks, `OnNext`, `OnComplete` -and `OnError`, and an `IObservable` has single method called `Subscribe` which gets passed one of these +An `IObserver` has three callbacks, `OnNext`, `OnComplete`, +and `OnError`. An `IObservable` has a single method called `Subscribe` which gets passed one of these Observers. Once connected, the Observer gets callbacks (mostly `OnNext` callbacks) when things happen. @@ -153,11 +151,11 @@ A typical use of the `AllListeners` static property looks like this: Console.WriteLine($"New Listener discovered: {listener.Name}"); if (listener.Name == "System.Net.Http") { - // Here is where we put code to subscribe to the Listener. + // Here is where you will put code to subscribe to the Listener. } }; IObserver observer = new Observer(onNewListener, null); - //when a listener is created, invoke the Observer onNext function which calls the delegate + //When a listener is created, invoke the Observer onNext function which calls the delegate. listenerSubscription = DiagnosticListener.AllListeners.Subscribe(observer); // Typically you leave the listenerSubscription subscription active forever. @@ -165,23 +163,23 @@ A typical use of the `AllListeners` static property looks like this: // call listenerSubscription.Dispose() to cancel your subscription to the IObservable. ``` -This code creates a callback delegate and using the `AllListeners.Subscribe` method requests +This code creates a callback delegate and, using the `AllListeners.Subscribe` method, requests that the delegate be called for every active `DiagnosticListener` in the system. The decision of whether or not to subscribe to the listener -is made by inspecting its name. The code above is looking for our 'System.Net.Http' listener that we created previously. +is made by inspecting its name. The code above is looking for the 'System.Net.Http' listener that you created previously. Like all calls to `Subscribe()`, this one returns an `IDisposable` that represents the subscription itself. Callbacks will continue to happen as long as nothing calls `Dispose()` on this subscription object. -The above code never calls `Dispose()`, so it will receive callbacks forever. +The code example never calls `Dispose()`, so it will receive callbacks forever. When you subscribe to `AllListeners`, you get a callback for ALL ACTIVE `DiagnosticListeners`. Thus, upon subscribing, you get a flurry of callbacks for all existing `DiagnosticListeners`, and as new ones -are created, you receive a callback for those as well. You receive a complete list of everything it is possible +are created, you receive a callback for those as well. You receive a complete list of everything it's possible to subscribe to. -#### Subscribing to DiagnosticListeners +#### Subscribe to DiagnosticListeners A `DiagnosticListener` implements the `IObservable>` interface, so you can -call `Subscribe()` on it as well. We show how to fill out the previous example: +call `Subscribe()` on it as well. The following code shows how to fill out the previous example: ```C# static IDisposable networkSubscription = null; @@ -209,39 +207,40 @@ call `Subscribe()` on it as well. We show how to fill out the previous example: // At some point you may wish to dispose the networkSubscription. ``` -In the above example, after finding the 'System.Net.Http' `DiagnosticListener`, an action is created that -prints out the name of the listener, event, and `payload.ToString()`. Please note the following. +In this example, after finding the 'System.Net.Http' `DiagnosticListener`, an action is created that +prints out the name of the listener, event, and `payload.ToString()`. -`DiagnosticListener` implements `IObservable>`. This means +> [!NOTE] +>`DiagnosticListener` implements `IObservable>`. This means on each callback we get a `KeyValuePair`. The key of this pair is the name of the event - and the value is the payload `object`. In the code above we simply log this information + and the value is the payload `object`. The example simply logs this information to the console. -It is important to keep track of subscriptions to the `DiagnosticListener`. In the above code the -networkSubscription variable remembers this. If another `creation` is formed, one must +It's important to keep track of subscriptions to the `DiagnosticListener`. In the previous code, the +'networkSubscription' variable remembers this. If you form another `creation`, you must unsubscribe the previous listener and subscribe to the new one. The `DiagnosticSource`/`DiagnosticListener` code is thread safe, but the callback code also needs to be thread safe. To ensure the callback code is thread safe, locks are used. It is possible to create two `DiagnosticListeners` -with the same name at the same time. To avoid races updates of shared variables are performed under the protection of a lock. +with the same name at the same time. To avoid race conditions, updates of shared variables are performed under the protection of a lock. -Once the above code is run, the next time a `Write()` is done on 'System.Net.Http' `DiagnosticListener` +Once the previous code is run, the next time a `Write()` is done on 'System.Net.Http' `DiagnosticListener` the information will be logged to the console. Subscriptions are independent of one another. As a result, other code -can do exactly the same thing as the code above, and generate two 'pipes' of the logging +can do exactly the same thing as the code example, and generate two 'pipes' of the logging information. -#### Decoding Payloads +#### Decode Payloads The `KeyvaluePair` that is passed to the callback has the event name and payload, but the payload is typed simply as an `object`. There are two ways of getting more specific data: -1. If the payload is a well known type (e.g. a `string`, or an `HttpMessageRequest`) then you can simply - cast the `object` to the expected type (using the `as` operator so as not to cause an exception if - you are wrong) and then access the fields. This is very efficient. +If the payload is a well known type (for example, a `string`, or an `HttpMessageRequest`), then you can simply +cast the `object` to the expected type (using the `as` operator so as not to cause an exception if +you are wrong) and then access the fields. This is very efficient. -2. Use reflection API. For example, assume the method below is present. +Use reflection API. For example, assume the following method is present. ```C# /// Define a shortcut method that fetches a field of a particular name. @@ -254,7 +253,7 @@ an `object`. There are two ways of getting more specific data: } ``` -The `listener.Subscribe()` call above could be replaced with the following code, to decode the payload more fully. +To decode the payload more fully, you could replace the `listener.Subscribe()` call with the following code. ```C# networkSubscription = listener.Subscribe(delegate(KeyValuePair evnt) { @@ -271,50 +270,50 @@ The `listener.Subscribe()` call above could be replaced with the following code, Note that using reflection is relatively expensive. However, using reflection is the only option if the payloads were generated using anonymous types. This overhead can be reduced by -making fast, specialized property fetchers either using `PropertyInfo.CreateDelegate` or -`ReflectEmit`, but that is beyond the scope of this document. -(See the [PropertySpec](https://github.com/dotnet/runtime/blob/6de7147b9266d7730b0d73ba67632b0c198cb11e/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs#L1235) -class used in the `DiagnosticSourceEventSource` for an example of a fast, delegate-based property fetcher.) +making fast, specialized property fetchers using either `PropertyInfo.CreateDelegate` or +`ReflectEmit`, but that's beyond the scope of this article. +(For an example of a fast, delegate-based property fetcher, see [PropertySpec](https://github.com/dotnet/runtime/blob/6de7147b9266d7730b0d73ba67632b0c198cb11e/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs#L1235) +class used in the `DiagnosticSourceEventSource`.) #### Filtering In the example above, the code uses the `IObservable.Subscribe()` method to hook up the callback. This -causes all events to be given to the callback. However `DiagnosticListener` has overloads of +causes all events to be given to the callback. However, `DiagnosticListener` has overloads of `Subscribe()` that allow the controller to control which events are given. The `listener.Subscribe()` call in the previous example can be replaced with the following code to demonstrate. ```C# - // Create the callback delegate + // Create the callback delegate. Action> callback = (KeyValuePair evnt) => Console.WriteLine("From Listener {0} Received Event {1} with payload {2}", networkListener.Name, evnt.Key, evnt.Value.ToString()); - // Turn it into an observer (using the Observer Class above) + // Turn it into an observer (using the Observer Class above). Observer> observer = new Observer>(callback); - // Create a predicate (asks only for one kind of event) + // Create a predicate (asks only for one kind of event). Predicate predicate = (string eventName) => eventName == "RequestStart"; - // Subscribe with a filter predicate + // Subscribe with a filter predicate. IDisposable subscription = listener.Subscribe(observer, predicate); // subscription.Dispose() to stop the callbacks. ``` -This very efficiently subscribes to only the 'RequestStart' events. All other events will cause the `DiagnosticSource.IsEnabled()` -method to return `false`, and thus be efficiently filtered out. +This efficiently subscribes to only the 'RequestStart' events. All other events will cause the `DiagnosticSource.IsEnabled()` +method to return `false` and thus be efficiently filtered out. -##### Context-based Filtering +##### Context-based filtering Some scenarios require advanced filtering based on extended context. -Producers may call `DiagnosticSource.IsEnabled()` overloads and supply additional event properties as shown in the code below. +Producers can call xref:System.Diagnostics.DiagnosticSource.IsEnabled()%2A?displayProperty=nameWithType> overloads and supply additional event properties as shown in the following code. ```C# if (httpLogger.IsEnabled("RequestStart", aRequest, anActivity)) httpLogger.Write("RequestStart", new { Url="http://clr", Request=aRequest }); ``` -The next code demonstrates that consumers may use such properties to filter events more precisely: +The next code example demonstrates that consumers can use such properties to filter events more precisely. ```C# // Create a predicate (asks only for Requests for certains URIs) @@ -335,9 +334,9 @@ The next code demonstrates that consumers may use such properties to filter even IDisposable subscription = listener.Subscribe(observer, predicate); ``` -Note that producers are not aware of the filter a consumer has provided. `DiagnosticListener` +Producers are not aware of the filter a consumer has provided. `DiagnosticListener` will invoke the provided filter, omitting additional arguments if necessary, thus the filter should expect to receive a `null` context. -Producers should enclose `IsEnabled()` calls with event name and context with pure `IsEnabled()` -calls for event name, so consumers must ensure that their filter allows events without context +If a producer calls `IsEnabled()` with event name and context, those calls are enclosed in an overload that takes +only the event name. Consumers must ensure that their filter allows events without context to pass through. From 9a72826ae5919dd435fcd78a8a0f94793174af7c Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Thu, 12 May 2022 18:05:10 -0600 Subject: [PATCH 15/28] Update to working example Add clarifying comments, URLs for the APIs and responded to suggestions. --- .../diagnosticsource-diagnosticlistener.md | 124 ++++++++++++++---- 1 file changed, 96 insertions(+), 28 deletions(-) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index 4c216d4918ee5..913a9a9b6fa1f 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -1,7 +1,7 @@ --- title: DiagnosticSource and DiagnosticListener description: An overview of DiagnosticSource/DiagnosticListener including guidance on logging events, instrumenting code, and consuming data. -ms.date: 05/09/2022 +ms.date: 05/12/2022 --- # DiagnosticSource and DiagnosticListener @@ -20,37 +20,101 @@ This walkthrough shows how to create a DiagnosticSource event and instrument cod It then explains how to consume the event by finding interesting DiagnosticListeners, subscribing to their events, and decoding event data payloads. It finishes by describing *filtering*, which allows only specific events to pass through the system. -## Log an event +## DiagnosticSource Implementation You will work with the following code. This code is an *HttpClient* class with a `SendWebRequest` method that sends an HTTP request to the URL and receives a reply. ```C# - // The class before adding logging. - class HttpClient +using System.Diagnostics; + +MyListener TheListener = new MyListener(); +TheListener.Listening(); + +HTTPClient Client = new HTTPClient(); +Client.SendWebRequest("https://docs.microsoft.com/dotnet/core/diagnostics/"); + +class HTTPClient +{ + private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); + + public byte[] SendWebRequest(string url) { - byte[] SendWebRequest(string url) + if (httpLogger.IsEnabled("RequestStart")) { - // pretend this sends an HTTP request to the url and gets back a reply - byte[] reply = new byte[] {}; - return reply; + httpLogger.Write("RequestStart", new { Url = url }); + } + //Pretend this sends an HTTP request to the url and gets back a reply. + byte[] reply = new byte[] { }; + return reply; + } +} - // The class after adding logging. - class HttpClient +class Observer : IObserver +{ + public Observer(Action onNext, Action onCompleted) { - private static DiagnosticSource httpLogger= new DiagnosticListener("System.Net.Http"); - public byte[] SendWebRequest(string url) + _onNext = onNext ?? new Action(_ => { }); + _onCompleted = onCompleted ?? new Action(() => { }); + } + + public void OnCompleted() { _onCompleted(); } + public void OnError(Exception error) { } + public void OnNext(T value) { _onNext(value); } + + private Action _onNext; + private Action _onCompleted; +} + +class MyListener +{ + IDisposable networkSubscription; + IDisposable listenerSubscription; + private readonly object allListeners = new(); + public void Listening() + { + Action> whenHeard = delegate (KeyValuePair data) { - if (httpLogger.IsEnabled("RequestStart")) + Console.WriteLine($"Data received: {data.Key}: {data.Value}"); + }; + Action onNewListener = delegate (DiagnosticListener listener) + { + Console.WriteLine($"New Listener discovered: {listener.Name}"); + //Suscribe to the specific DiagnosticListener of interest. + if (listener.Name == "System.Net.Http") { - httpLogger.Write("RequestStart", new { Url = url }); + //Use lock to ensure the callback code is thread safe. + lock(allListeners) + { + if (networkSubscription != null) + { + networkSubscription.Dispose(); + } + IObserver> iobserver = new Observer>(whenHeard, null); + networkSubscription = listener.Subscribe(iobserver); + } + } - // Pretend this sends an HTTP request to the url and gets back a reply. - byte[] reply = new byte[] {}; - return reply; - } + }; + //Subscribe to discover all DiagnosticListeners + IObserver observer = new Observer(onNewListener, null); + //When a listener is created, invoke the onNext function which calls the delegate. + listenerSubscription = DiagnosticListener.AllListeners.Subscribe(observer); + } + // Typically you leave the listenerSubscription subscription active forever. + // However when you no longer want your callback to be called, you can + // call listenerSubscription.Dispose() to cancel your subscription to the IObservable. +} +``` +Running the provided implementation prints to the console. + ``` +New Listener discovered: System.Net.Http +Data received: RequestStart: { Url = https://docs.microsoft.com/dotnet/core/diagnostics/ } +``` + +## Log an event The `DiagnosticSource` type is an abstract base class that defines the methods needed to log events. The class that holds the implementation is `DiagnosticListener`. The first step in instrumenting code with `DiagnosticSource` is to create a @@ -78,6 +142,9 @@ interface consists of two methods: void Write(string name, object value); ``` +This is instrument site specific. You need to check the instrumentation site to see what types are passed into `IsEnabled`. This provides the information +to know what to cast to. + A typical call site will look like: ```C# @@ -121,7 +188,7 @@ The first step in receiving events is to discover which `DiagnosticListeners` yo interested in. `DiagnosticListener` supports a way of discovering `DiagnosticListeners` that are active in the system at run time. The API to accomplish this is the property. -Implement an `Observer` class that inherits from the `IObservable` interface, which is the 'callback' version of the `IEnumerable` interface. You can learn more about it at the [Reactive Extensions](https://docs.microsoft.com/dotnet/csharp/fundamentals/types/anonymous-types) site. +Implement an `Observer` class that inherits from the `IObservable` interface, which is the 'callback' version of the `IEnumerable` interface. You can learn more about it at the [Reactive Extensions](https://github.com/dotnet/reactive) site. An `IObserver` has three callbacks, `OnNext`, `OnComplete`, and `OnError`. An `IObservable` has a single method called `Subscribe` which gets passed one of these Observers. Once connected, the Observer gets callbacks (mostly `OnNext` callbacks) when things @@ -192,14 +259,15 @@ call `Subscribe()` on it as well. The following code shows how to fill out the p { if (listener.Name == "System.Net.Http") { + //Lock is used to ensure the callback code is thread safe. lock(allListeners) { if (networkSubscription != null) { networkSubscription.Dispose(); } - IObserver> iobserver = new Observer>(onMessage, null); - networkSubscription = listener.Subscribe(iobserver); + IObserver> observer = new Observer>(onMessage, null); + networkSubscription = listener.Subscribe(observer); } } }; @@ -222,7 +290,7 @@ unsubscribe the previous listener and subscribe to the new one. The `DiagnosticSource`/`DiagnosticListener` code is thread safe, but the callback code also needs to be thread safe. To ensure the callback code is thread safe, locks are used. It is possible to create two `DiagnosticListeners` -with the same name at the same time. To avoid race conditions, updates of shared variables are performed under the protection of a lock. +with the same name at the same time. To avoid race conditions, updates of shared variables are performed under the protection of a lock. Once the previous code is run, the next time a `Write()` is done on 'System.Net.Http' `DiagnosticListener` the information will be logged to the console. @@ -270,8 +338,8 @@ To decode the payload more fully, you could replace the `listener.Subscribe()` c Note that using reflection is relatively expensive. However, using reflection is the only option if the payloads were generated using anonymous types. This overhead can be reduced by -making fast, specialized property fetchers using either `PropertyInfo.CreateDelegate` or -`ReflectEmit`, but that's beyond the scope of this article. +making fast, specialized property fetchers using either [`PropertyInfo.GetMethod.CreateDelegate`](/api/system.reflection.methodinfo?view=net-6.0#:~:text=CreateDelegate(Type)) or +[`System.Reflect.Emit`Namespace](/api/system.reflection.emit?view=net-6.0), but that's beyond the scope of this article. (For an example of a fast, delegate-based property fetcher, see [PropertySpec](https://github.com/dotnet/runtime/blob/6de7147b9266d7730b0d73ba67632b0c198cb11e/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs#L1235) class used in the `DiagnosticSourceEventSource`.) @@ -309,8 +377,9 @@ Some scenarios require advanced filtering based on extended context. Producers can call xref:System.Diagnostics.DiagnosticSource.IsEnabled()%2A?displayProperty=nameWithType> overloads and supply additional event properties as shown in the following code. ```C# - if (httpLogger.IsEnabled("RequestStart", aRequest, anActivity)) - httpLogger.Write("RequestStart", new { Url="http://clr", Request=aRequest }); +//aRequest and anActivity are the current request and activity about to be logged. +if (httpLogger.IsEnabled("RequestStart", aRequest, anActivity)) + httpLogger.Write("RequestStart", new { Url="http://clr", Request=aRequest }); ``` The next code example demonstrates that consumers can use such properties to filter events more precisely. @@ -321,8 +390,7 @@ The next code example demonstrates that consumers can use such properties to fil { if (eventName == "RequestStart") { - HttpRequestMessage request = context as HttpRequestMessage; - if (request != null) + if (context is HttpRequestMessage request) { return IsUriEnabled(request.RequestUri); } From 15c251e0630d9567643b16474d8ac9dbd0bbd02a Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Thu, 12 May 2022 18:13:56 -0600 Subject: [PATCH 16/28] Add a line, remove a space --- docs/core/diagnostics/diagnosticsource-diagnosticlistener.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index 913a9a9b6fa1f..9e95aaff9a782 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -107,6 +107,7 @@ class MyListener // call listenerSubscription.Dispose() to cancel your subscription to the IObservable. } ``` + Running the provided implementation prints to the console. ``` @@ -290,7 +291,7 @@ unsubscribe the previous listener and subscribe to the new one. The `DiagnosticSource`/`DiagnosticListener` code is thread safe, but the callback code also needs to be thread safe. To ensure the callback code is thread safe, locks are used. It is possible to create two `DiagnosticListeners` -with the same name at the same time. To avoid race conditions, updates of shared variables are performed under the protection of a lock. +with the same name at the same time. To avoid race conditions, updates of shared variables are performed under the protection of a lock. Once the previous code is run, the next time a `Write()` is done on 'System.Net.Http' `DiagnosticListener` the information will be logged to the console. From 3b085cf74f05187686ddd0fe59fdf351f4020613 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Mon, 23 May 2022 07:38:55 -0600 Subject: [PATCH 17/28] Updating from feedback on code review --- .../diagnosticsource-diagnosticlistener.md | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index 9e95aaff9a782..6d19978341c4d 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -24,7 +24,7 @@ It finishes by describing *filtering*, which allows only specific events to pass You will work with the following code. This code is an *HttpClient* class with a `SendWebRequest` method that sends an HTTP request to the URL and receives a reply. -```C# +```csharp using System.Diagnostics; MyListener TheListener = new MyListener(); @@ -110,7 +110,7 @@ class MyListener Running the provided implementation prints to the console. -``` +```console New Listener discovered: System.Net.Http Data received: RequestStart: { Url = https://docs.microsoft.com/dotnet/core/diagnostics/ } ``` @@ -121,7 +121,7 @@ The `DiagnosticSource` type is an abstract base class that defines the methods n The first step in instrumenting code with `DiagnosticSource` is to create a `DiagnosticListener`. For example: -```C# +```csharp private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); ``` @@ -138,19 +138,19 @@ Thus the event names only need to be unique within a component. The `DiagnosticSource` logging interface consists of two methods: -```C# +```csharp bool IsEnabled(string name) void Write(string name, object value); ``` -This is instrument site specific. You need to check the instrumentation site to see what types are passed into `IsEnabled`. This provides the information +This is instrument site specific. You need to check the instrumentation site to see what types are passed into `IsEnabled`. This provides the information of the cooking chicken to know what to cast to. A typical call site will look like: -```C# +```csharp if (httpLogger.IsEnabled("RequestStart")) - httpLogger.Write("RequestStart", new { Url="http://clr", }); //Any object can be the second argument. + httpLogger.Write("RequestStart", new { Url="http://lr", }); //Any object can be the second argument. ``` Every event has a `string` name (for example, `RequestStart`), and exactly one `object` as a payload. @@ -164,7 +164,7 @@ make it efficient when the source is not enabled. Combining everything you have: -```C# +```csharp class HttpClient { private static DiagnosticSource httpLogger= new DiagnosticListener("System.Net.Http"); @@ -197,7 +197,7 @@ happen. A typical use of the `AllListeners` static property looks like this: -```C# +```csharp class Observer : IObserver { public Observer(Action onNext, Action onCompleted) @@ -249,7 +249,7 @@ to subscribe to. A `DiagnosticListener` implements the `IObservable>` interface, so you can call `Subscribe()` on it as well. The following code shows how to fill out the previous example: -```C# +```csharp static IDisposable networkSubscription = null; static IDisposable listenerSubscription; Action> onMessage = delegate (KeyValuePair message) @@ -311,7 +311,7 @@ you are wrong) and then access the fields. This is very efficient. Use reflection API. For example, assume the following method is present. -```C# +```csharp /// Define a shortcut method that fetches a field of a particular name. static class PropertyExtensions { @@ -324,7 +324,7 @@ Use reflection API. For example, assume the following method is present. To decode the payload more fully, you could replace the `listener.Subscribe()` call with the following code. -```C# +```csharp networkSubscription = listener.Subscribe(delegate(KeyValuePair evnt) { var eventName = evnt.Key; var payload = evnt.Value; @@ -352,7 +352,7 @@ causes all events to be given to the callback. However, `DiagnosticListener` has The `listener.Subscribe()` call in the previous example can be replaced with the following code to demonstrate. -```C# +```csharp // Create the callback delegate. Action> callback = (KeyValuePair evnt) => Console.WriteLine("From Listener {0} Received Event {1} with payload {2}", networkListener.Name, evnt.Key, evnt.Value.ToString()); @@ -377,7 +377,7 @@ method to return `false` and thus be efficiently filtered out. Some scenarios require advanced filtering based on extended context. Producers can call xref:System.Diagnostics.DiagnosticSource.IsEnabled()%2A?displayProperty=nameWithType> overloads and supply additional event properties as shown in the following code. -```C# +```csharp //aRequest and anActivity are the current request and activity about to be logged. if (httpLogger.IsEnabled("RequestStart", aRequest, anActivity)) httpLogger.Write("RequestStart", new { Url="http://clr", Request=aRequest }); @@ -385,7 +385,7 @@ if (httpLogger.IsEnabled("RequestStart", aRequest, anActivity)) The next code example demonstrates that consumers can use such properties to filter events more precisely. -```C# +```csharp // Create a predicate (asks only for Requests for certains URIs) Func predicate = (string eventName, object context, object activity) => { From 5c42c6fa525e1b8c28dcaa2a1b09dc1ea48f04f8 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Wed, 25 May 2022 02:55:41 -0600 Subject: [PATCH 18/28] Add DiagnosticSource and DiagnosticListener to toc --- docs/fundamentals/toc.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/fundamentals/toc.yml b/docs/fundamentals/toc.yml index 788b5d4ef2511..47b1bcee5b554 100644 --- a/docs/fundamentals/toc.yml +++ b/docs/fundamentals/toc.yml @@ -663,6 +663,10 @@ items: href: ../core/diagnostics/eventsource-collect-and-view-traces.md - name: Activity IDs href: ../core/diagnostics/eventsource-activity-ids.md + - name: DiagnosticSource and DiagnosticListener + items: + - name: Getting started + href: ../core/diagnistics/diagnosticsource-diagnosticlistener.md - name: Metrics items: - name: Overview From 3edf4ce0fe934e24c34e19225fa7fefd6c855a91 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Wed, 25 May 2022 03:28:10 -0600 Subject: [PATCH 19/28] Add .csproj and .cs files --- docs/core/diagnostics/DiagnosticSource.csproj | 10 +++ docs/core/diagnostics/Program.cs | 71 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 docs/core/diagnostics/DiagnosticSource.csproj create mode 100644 docs/core/diagnostics/Program.cs diff --git a/docs/core/diagnostics/DiagnosticSource.csproj b/docs/core/diagnostics/DiagnosticSource.csproj new file mode 100644 index 0000000000000..40c60dd4c8843 --- /dev/null +++ b/docs/core/diagnostics/DiagnosticSource.csproj @@ -0,0 +1,10 @@ + + + + Exe + net6.0 + enable + enable + + + diff --git a/docs/core/diagnostics/Program.cs b/docs/core/diagnostics/Program.cs new file mode 100644 index 0000000000000..047e6ccd516c9 --- /dev/null +++ b/docs/core/diagnostics/Program.cs @@ -0,0 +1,71 @@ +using System.Diagnostics; +MyListener TheListener = new MyListener(); +TheListener.Listening(); +HTTPClient Client = new HTTPClient(); +Client.SendWebRequest("https://docs.microsoft.com/dotnet/core/diagnostics/"); +class HTTPClient +{ + private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); + public byte[] SendWebRequest(string url) + { + if (httpLogger.IsEnabled("RequestStart")) + { + httpLogger.Write("RequestStart", new { Url = url }); + } + //Pretend this sends an HTTP request to the url and gets back a reply. + byte[] reply = new byte[] { }; + return reply; + } +} +class Observer : IObserver +{ + public Observer(Action onNext, Action onCompleted) + { + _onNext = onNext ?? new Action(_ => { }); + _onCompleted = onCompleted ?? new Action(() => { }); + } + public void OnCompleted() { _onCompleted(); } + public void OnError(Exception error) { } + public void OnNext(T value) { _onNext(value); } + private Action _onNext; + private Action _onCompleted; +} +class MyListener +{ + IDisposable networkSubscription; + IDisposable listenerSubscription; + private readonly object allListeners = new(); + public void Listening() + { + Action> whenHeard = delegate (KeyValuePair data) + { + Console.WriteLine($"Data received: {data.Key}: {data.Value}"); + }; + Action onNewListener = delegate (DiagnosticListener listener) + { + Console.WriteLine($"New Listener discovered: {listener.Name}"); + //Suscribe to the specific DiagnosticListener of interest. + if (listener.Name == "System.Net.Http") + { + //Use lock to ensure the callback code is thread safe. + lock (allListeners) + { + if (networkSubscription != null) + { + networkSubscription.Dispose(); + } + IObserver> iobserver = new Observer>(whenHeard, null); + networkSubscription = listener.Subscribe(iobserver); + } + + } + }; + //Subscribe to discover all DiagnosticListeners + IObserver observer = new Observer(onNewListener, null); + //When a listener is created, invoke the onNext function which calls the delegate. + listenerSubscription = DiagnosticListener.AllListeners.Subscribe(observer); + } + // Typically you leave the listenerSubscription subscription active forever. + // However when you no longer want your callback to be called, you can + // call listenerSubscription.Dispose() to cancel your subscription to the IObservable. +} \ No newline at end of file From 5eb2e4f3ca5d2ccd7cea16e6de76966b42b1a4b4 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Wed, 25 May 2022 03:38:54 -0600 Subject: [PATCH 20/28] Add links to the .cs and .csproj folders --- docs/core/diagnostics/diagnosticsource-diagnosticlistener.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index 6d19978341c4d..9f83a1287dbbc 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -20,6 +20,8 @@ This walkthrough shows how to create a DiagnosticSource event and instrument cod It then explains how to consume the event by finding interesting DiagnosticListeners, subscribing to their events, and decoding event data payloads. It finishes by describing *filtering*, which allows only specific events to pass through the system. +Click these links to find the code for the [.cs](./Program.cs) and [.csproj](./DiagnosticSource.csproj) files. + ## DiagnosticSource Implementation You will work with the following code. This code is an *HttpClient* class with a `SendWebRequest` method that sends an HTTP request to the URL and receives a reply. From 6f2f3bc22b77e79a0405754339f9970929f25efe Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Wed, 25 May 2022 13:53:23 -0600 Subject: [PATCH 21/28] Move the file to the correct place --- docs/core/diagnostics/{ => snippets}/DiagnosticSource.csproj | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/core/diagnostics/{ => snippets}/DiagnosticSource.csproj (100%) diff --git a/docs/core/diagnostics/DiagnosticSource.csproj b/docs/core/diagnostics/snippets/DiagnosticSource.csproj similarity index 100% rename from docs/core/diagnostics/DiagnosticSource.csproj rename to docs/core/diagnostics/snippets/DiagnosticSource.csproj From d8c5a420c52d809d17c16e208329aae5019cac0f Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Wed, 25 May 2022 14:06:30 -0600 Subject: [PATCH 22/28] move location, add blocking --- .../diagnosticsource/csharp}/Program.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) rename docs/core/diagnostics/{ => snippets/diagnosticsource/csharp}/Program.cs (89%) diff --git a/docs/core/diagnostics/Program.cs b/docs/core/diagnostics/snippets/diagnosticsource/csharp/Program.cs similarity index 89% rename from docs/core/diagnostics/Program.cs rename to docs/core/diagnostics/snippets/diagnosticsource/csharp/Program.cs index 047e6ccd516c9..1f134118a0e17 100644 --- a/docs/core/diagnostics/Program.cs +++ b/docs/core/diagnostics/snippets/diagnosticsource/csharp/Program.cs @@ -1,22 +1,31 @@ -using System.Diagnostics; +// +using System.Diagnostics; MyListener TheListener = new MyListener(); TheListener.Listening(); HTTPClient Client = new HTTPClient(); Client.SendWebRequest("https://docs.microsoft.com/dotnet/core/diagnostics/"); + +// class HTTPClient { + // private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); + // public byte[] SendWebRequest(string url) { + // if (httpLogger.IsEnabled("RequestStart")) { httpLogger.Write("RequestStart", new { Url = url }); } + // //Pretend this sends an HTTP request to the url and gets back a reply. byte[] reply = new byte[] { }; return reply; } } +// +// class Observer : IObserver { public Observer(Action onNext, Action onCompleted) @@ -32,6 +41,7 @@ public void OnError(Exception error) { } } class MyListener { + // IDisposable networkSubscription; IDisposable listenerSubscription; private readonly object allListeners = new(); @@ -65,7 +75,10 @@ public void Listening() //When a listener is created, invoke the onNext function which calls the delegate. listenerSubscription = DiagnosticListener.AllListeners.Subscribe(observer); } + // // Typically you leave the listenerSubscription subscription active forever. // However when you no longer want your callback to be called, you can // call listenerSubscription.Dispose() to cancel your subscription to the IObservable. -} \ No newline at end of file +} +/ +// From a9b6a75866a4ddb21820301199700ed33475132d Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Wed, 25 May 2022 14:07:31 -0600 Subject: [PATCH 23/28] Moving to correct address for real --- .../{ => diagnosticsource/csharp}/DiagnosticSource.csproj | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/core/diagnostics/snippets/{ => diagnosticsource/csharp}/DiagnosticSource.csproj (100%) diff --git a/docs/core/diagnostics/snippets/DiagnosticSource.csproj b/docs/core/diagnostics/snippets/diagnosticsource/csharp/DiagnosticSource.csproj similarity index 100% rename from docs/core/diagnostics/snippets/DiagnosticSource.csproj rename to docs/core/diagnostics/snippets/diagnosticsource/csharp/DiagnosticSource.csproj From f1550c3d35488ea3b4cba19fe9270740248cc036 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Wed, 25 May 2022 14:14:04 -0600 Subject: [PATCH 24/28] Linking the code to the snippits --- .../diagnosticsource-diagnosticlistener.md | 171 +----------------- 1 file changed, 6 insertions(+), 165 deletions(-) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index 9f83a1287dbbc..04d2fa2c29bb4 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -26,89 +26,7 @@ Click these links to find the code for the [.cs](./Program.cs) and [.csproj](./D You will work with the following code. This code is an *HttpClient* class with a `SendWebRequest` method that sends an HTTP request to the URL and receives a reply. -```csharp -using System.Diagnostics; - -MyListener TheListener = new MyListener(); -TheListener.Listening(); - -HTTPClient Client = new HTTPClient(); -Client.SendWebRequest("https://docs.microsoft.com/dotnet/core/diagnostics/"); - -class HTTPClient -{ - private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); - - public byte[] SendWebRequest(string url) - { - if (httpLogger.IsEnabled("RequestStart")) - { - httpLogger.Write("RequestStart", new { Url = url }); - } - //Pretend this sends an HTTP request to the url and gets back a reply. - byte[] reply = new byte[] { }; - return reply; - - } -} - -class Observer : IObserver -{ - public Observer(Action onNext, Action onCompleted) - { - _onNext = onNext ?? new Action(_ => { }); - _onCompleted = onCompleted ?? new Action(() => { }); - } - - public void OnCompleted() { _onCompleted(); } - public void OnError(Exception error) { } - public void OnNext(T value) { _onNext(value); } - - private Action _onNext; - private Action _onCompleted; -} - -class MyListener -{ - IDisposable networkSubscription; - IDisposable listenerSubscription; - private readonly object allListeners = new(); - public void Listening() - { - Action> whenHeard = delegate (KeyValuePair data) - { - Console.WriteLine($"Data received: {data.Key}: {data.Value}"); - }; - Action onNewListener = delegate (DiagnosticListener listener) - { - Console.WriteLine($"New Listener discovered: {listener.Name}"); - //Suscribe to the specific DiagnosticListener of interest. - if (listener.Name == "System.Net.Http") - { - //Use lock to ensure the callback code is thread safe. - lock(allListeners) - { - if (networkSubscription != null) - { - networkSubscription.Dispose(); - } - IObserver> iobserver = new Observer>(whenHeard, null); - networkSubscription = listener.Subscribe(iobserver); - } - - } - }; - //Subscribe to discover all DiagnosticListeners - IObserver observer = new Observer(onNewListener, null); - //When a listener is created, invoke the onNext function which calls the delegate. - listenerSubscription = DiagnosticListener.AllListeners.Subscribe(observer); - - } - // Typically you leave the listenerSubscription subscription active forever. - // However when you no longer want your callback to be called, you can - // call listenerSubscription.Dispose() to cancel your subscription to the IObservable. -} -``` +:::code language="csharp" source="snippets/diagnosticsource/csharp/Program.cs" id="WholeProgram"::: Running the provided implementation prints to the console. @@ -123,9 +41,7 @@ The `DiagnosticSource` type is an abstract base class that defines the methods n The first step in instrumenting code with `DiagnosticSource` is to create a `DiagnosticListener`. For example: -```csharp -private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http"); -``` +:::code language="csharp" source="snippets/diagnosticsource/csharp/Program.cs" id="snippit1"::: Notice that `httpLogger` is typed as a `DiagnosticSource`. That's because this code @@ -150,10 +66,7 @@ to know what to cast to. A typical call site will look like: -```csharp - if (httpLogger.IsEnabled("RequestStart")) - httpLogger.Write("RequestStart", new { Url="http://lr", }); //Any object can be the second argument. -``` +:::code language="csharp" source="snippets/diagnosticsource/csharp/Program.cs" id="snippit3"::: Every event has a `string` name (for example, `RequestStart`), and exactly one `object` as a payload. If you need to send more than one item, you can do so by creating an `object` with properties to represent all its information. C#'s [anonymous type](../../csharp/fundamentals/types/anonymous-types.md) @@ -166,22 +79,7 @@ make it efficient when the source is not enabled. Combining everything you have: -```csharp - class HttpClient - { - private static DiagnosticSource httpLogger= new DiagnosticListener("System.Net.Http"); - public byte[] SendWebRequest(string url) - { - if (httpLogger.IsEnabled("RequestStart")) - { - httpLogger.Write("RequestStart", new { Url = url }); - } - // Pretend this sends an HTTP request to the url and gets back a reply. - byte[] reply = new byte[] {}; - return reply; - } - } -``` +:::code language="csharp" source="snippets/diagnosticsource/csharp/Program.cs" id="snippit4"::: ------------------------------------------- @@ -199,39 +97,7 @@ happen. A typical use of the `AllListeners` static property looks like this: -```csharp - class Observer : IObserver - { - public Observer(Action onNext, Action onCompleted) - { - _onNext = onNext ?? new Action(_ => { }); - _onCompleted = onCompleted ?? new Action(() => { }); - } - - public void OnCompleted() { _onCompleted(); } - public void OnError(Exception error) { } - public void OnNext(T value) { _onNext(value); } - - private Action _onNext; - private Action _onCompleted; - } - - Action onNewListener = delegate (DiagnosticListener listener) - { - Console.WriteLine($"New Listener discovered: {listener.Name}"); - if (listener.Name == "System.Net.Http") - { - // Here is where you will put code to subscribe to the Listener. - } - }; - IObserver observer = new Observer(onNewListener, null); - //When a listener is created, invoke the Observer onNext function which calls the delegate. - listenerSubscription = DiagnosticListener.AllListeners.Subscribe(observer); - - // Typically you leave the listenerSubscription subscription active forever. - // However when you no longer want your callback to be called, you can - // call listenerSubscription.Dispose() to cancel your subscription to the IObservable. -``` +:::code language="csharp" source="snippets/diagnosticsource/csharp/Program.cs" id="snippit5"::: This code creates a callback delegate and, using the `AllListeners.Subscribe` method, requests that the delegate be called for every active `DiagnosticListener` in the system. The decision of whether or not to subscribe to the listener @@ -251,32 +117,7 @@ to subscribe to. A `DiagnosticListener` implements the `IObservable>` interface, so you can call `Subscribe()` on it as well. The following code shows how to fill out the previous example: -```csharp - static IDisposable networkSubscription = null; - static IDisposable listenerSubscription; - Action> onMessage = delegate (KeyValuePair message) - { - Console.WriteLine($"Message received: {message.Key}: {message.Value}"); - }; - Action onNewListener = delegate (DiagnosticListener listener) - { - if (listener.Name == "System.Net.Http") - { - //Lock is used to ensure the callback code is thread safe. - lock(allListeners) - { - if (networkSubscription != null) - { - networkSubscription.Dispose(); - } - IObserver> observer = new Observer>(onMessage, null); - networkSubscription = listener.Subscribe(observer); - } - } - }; - - // At some point you may wish to dispose the networkSubscription. -``` +:::code language="csharp" source="snippets/diagnosticsource/csharp/Program.cs" id="snippit6"::: In this example, after finding the 'System.Net.Http' `DiagnosticListener`, an action is created that prints out the name of the listener, event, and `payload.ToString()`. From 797159573abefd2921d4882b6710e2462153c0de Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Wed, 25 May 2022 14:19:18 -0600 Subject: [PATCH 25/28] Correct filepath --- docs/fundamentals/toc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fundamentals/toc.yml b/docs/fundamentals/toc.yml index 4f8a6d28da19f..3e98fcad47b16 100644 --- a/docs/fundamentals/toc.yml +++ b/docs/fundamentals/toc.yml @@ -657,7 +657,7 @@ items: - name: DiagnosticSource and DiagnosticListener items: - name: Getting started - href: ../core/diagnistics/diagnosticsource-diagnosticlistener.md + href: ../core/diagnostics/diagnosticsource-diagnosticlistener.md - name: EventPipe href: ../core/diagnostics/eventpipe.md - name: Metrics From 04eb9805b38705ae4b2b40ac231f7af114559019 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Wed, 25 May 2022 14:32:55 -0600 Subject: [PATCH 26/28] Responding to feedback --- .../diagnosticsource-diagnosticlistener.md | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index 04d2fa2c29bb4..fdd9350a22231 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -61,8 +61,7 @@ interface consists of two methods: void Write(string name, object value); ``` -This is instrument site specific. You need to check the instrumentation site to see what types are passed into `IsEnabled`. This provides the information of the cooking chicken -to know what to cast to. +This is instrument site specific. You need to check the instrumentation site to see what types are passed into `IsEnabled`. This provides you with the information to know what to cast the payload to. A typical call site will look like: @@ -91,7 +90,7 @@ active in the system at run time. The API to accomplish this is the ` class that inherits from the `IObservable` interface, which is the 'callback' version of the `IEnumerable` interface. You can learn more about it at the [Reactive Extensions](https://github.com/dotnet/reactive) site. An `IObserver` has three callbacks, `OnNext`, `OnComplete`, -and `OnError`. An `IObservable` has a single method called `Subscribe` which gets passed one of these +and `OnError`. An `IObservable` has a single method called `Subscribe` that gets passed one of these Observers. Once connected, the Observer gets callbacks (mostly `OnNext` callbacks) when things happen. @@ -123,13 +122,13 @@ In this example, after finding the 'System.Net.Http' `DiagnosticListener`, an ac prints out the name of the listener, event, and `payload.ToString()`. > [!NOTE] ->`DiagnosticListener` implements `IObservable>`. This means +> `DiagnosticListener` implements `IObservable>`. This means on each callback we get a `KeyValuePair`. The key of this pair is the name of the event and the value is the payload `object`. The example simply logs this information to the console. It's important to keep track of subscriptions to the `DiagnosticListener`. In the previous code, the -'networkSubscription' variable remembers this. If you form another `creation`, you must +`networkSubscription` variable remembers this. If you form another `creation`, you must unsubscribe the previous listener and subscribe to the new one. The `DiagnosticSource`/`DiagnosticListener` code is thread safe, but the @@ -140,7 +139,7 @@ Once the previous code is run, the next time a `Write()` is done on 'System.Net. the information will be logged to the console. Subscriptions are independent of one another. As a result, other code -can do exactly the same thing as the code example, and generate two 'pipes' of the logging +can do exactly the same thing as the code example and generate two 'pipes' of the logging information. #### Decode Payloads @@ -182,14 +181,14 @@ To decode the payload more fully, you could replace the `listener.Subscribe()` c Note that using reflection is relatively expensive. However, using reflection is the only option if the payloads were generated using anonymous types. This overhead can be reduced by -making fast, specialized property fetchers using either [`PropertyInfo.GetMethod.CreateDelegate`](/api/system.reflection.methodinfo?view=net-6.0#:~:text=CreateDelegate(Type)) or -[`System.Reflect.Emit`Namespace](/api/system.reflection.emit?view=net-6.0), but that's beyond the scope of this article. -(For an example of a fast, delegate-based property fetcher, see [PropertySpec](https://github.com/dotnet/runtime/blob/6de7147b9266d7730b0d73ba67632b0c198cb11e/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs#L1235) +making fast, specialized property fetchers using either [PropertyInfo.GetMethod.CreateDelegate()](xref:System.Reflection.MethodInfo.CreateDelegate%2A) or +xref namespace, but that's beyond the scope of this article. +(For an example of a fast, delegate-based property fetcher, see the [PropertySpec](https://github.com/dotnet/runtime/blob/6de7147b9266d7730b0d73ba67632b0c198cb11e/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs#L1235) class used in the `DiagnosticSourceEventSource`.) #### Filtering -In the example above, the code uses the `IObservable.Subscribe()` method to hook up the callback. This +In the previous example, the code uses the `IObservable.Subscribe()` method to hook up the callback. This causes all events to be given to the callback. However, `DiagnosticListener` has overloads of `Subscribe()` that allow the controller to control which events are given. @@ -218,7 +217,7 @@ method to return `false` and thus be efficiently filtered out. ##### Context-based filtering Some scenarios require advanced filtering based on extended context. -Producers can call xref:System.Diagnostics.DiagnosticSource.IsEnabled()%2A?displayProperty=nameWithType> overloads and supply additional event properties as shown in the following code. +Producers can call overloads and supply additional event properties as shown in the following code. ```csharp //aRequest and anActivity are the current request and activity about to be logged. From 2802ddc5a49e2bc21043a79fa9dcbeaf25bdfa12 Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Wed, 25 May 2022 14:34:17 -0600 Subject: [PATCH 27/28] Added missing character --- .../diagnostics/snippets/diagnosticsource/csharp/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/diagnostics/snippets/diagnosticsource/csharp/Program.cs b/docs/core/diagnostics/snippets/diagnosticsource/csharp/Program.cs index 1f134118a0e17..b5ad47fdb6fe3 100644 --- a/docs/core/diagnostics/snippets/diagnosticsource/csharp/Program.cs +++ b/docs/core/diagnostics/snippets/diagnosticsource/csharp/Program.cs @@ -80,5 +80,5 @@ public void Listening() // However when you no longer want your callback to be called, you can // call listenerSubscription.Dispose() to cancel your subscription to the IObservable. } -/ +// // From 5f0fc2c3b1ec10d6bc88b31d182e340e5067687b Mon Sep 17 00:00:00 2001 From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Thu, 2 Jun 2022 11:03:55 -0600 Subject: [PATCH 28/28] removing links --- docs/core/diagnostics/diagnosticsource-diagnosticlistener.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md index fdd9350a22231..5ff34080134dc 100644 --- a/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md +++ b/docs/core/diagnostics/diagnosticsource-diagnosticlistener.md @@ -20,8 +20,6 @@ This walkthrough shows how to create a DiagnosticSource event and instrument cod It then explains how to consume the event by finding interesting DiagnosticListeners, subscribing to their events, and decoding event data payloads. It finishes by describing *filtering*, which allows only specific events to pass through the system. -Click these links to find the code for the [.cs](./Program.cs) and [.csproj](./DiagnosticSource.csproj) files. - ## DiagnosticSource Implementation You will work with the following code. This code is an *HttpClient* class with a `SendWebRequest` method that sends an HTTP request to the URL and receives a reply.