How diagnostic HTTP requests and not only. Part 1.

Serhii Korol - Sep 10 '23 - - Dev Community

In this article, I want to show another helpful feature in .NET. You can subscribe to all HTTP requests. It might be beneficial for diagnostic, logging, or additional handling of HTTP requests. Let's go.

First, you need to create a simple console application. We don't need more.

dotnet new console -n EventListenerSample
Enter fullscreen mode Exit fullscreen mode

You might guessed will use EventListener class. Let's add to Program.cs this code:

sealed class HttpEventListener : EventListener
{
    protected override void OnEventSourceCreated(EventSource eventSource)
    {
        switch (eventSource.Name)
        {
            case "System.Net.Http":
                EnableEvents(eventSource, EventLevel.LogAlways, EventKeywords.All);
                break;
            case "System.Threading.Tasks.TplEventSource":
                const EventKeywords tasksFlowActivityIds = (EventKeywords)0x80;
                EnableEvents(eventSource, EventLevel.LogAlways, tasksFlowActivityIds);
                break;
        }

        base.OnEventSourceCreated(eventSource);
    }
Enter fullscreen mode Exit fullscreen mode

We'll be using only two event sources. These sources initiate events. However, there exist many other events, which I'll mention later.
In the same class, you need to implement the second method, where we handle events and read payload if it exists.

protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
        switch (eventData.EventId)
        {
            case 1:
            {
                if (eventData.EventName == "RequestStart")
                {
                    var scheme = (string)eventData.Payload![0]!;
                    var host = (string)eventData.Payload[1]!;
                    var port = (int)eventData.Payload[2]!;
                    var pathAndQuery = (string)eventData.Payload[3]!;
                    var versionMajor = (byte)eventData.Payload[4]!;
                    var versionMinor = (byte)eventData.Payload[5]!;
                    var policy = (HttpVersionPolicy)eventData.Payload[6]!;

                    Console.WriteLine($"{eventData.ActivityId} {eventData.EventName} {scheme}://{host}:{port}{pathAndQuery} HTTP/{versionMajor}.{versionMinor} Policy {policy.RequestVersionExact}");
                }
                break;
            }
            case 2:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName);
                break;
        }
    }
Enter fullscreen mode Exit fullscreen mode

We handle only two events: RequestStart and RequestEnd.
About other events, I'll mention it later.

And finally, add code for running the HTTP request.

using var eventListener = new HttpEventListener();
        var client = new HttpClient();
        await client.GetStringAsync("https://catfact.ninja/fact");
Enter fullscreen mode Exit fullscreen mode

If you'll run it, you get this message:

00000011-0000-0000-0000-000093889d59 RequestStart https://catfact.ninja:443/fact HTTP/1.1 Policy False
00000011-0000-0000-0000-000093889d59 RequestStop
Enter fullscreen mode Exit fullscreen mode

The event listener is allowed to observe which request was executed. However, the EventListener class has more event sources. Add this code to OnEventSourceCreated method:

case "Microsoft-Windows-DotNETRuntime":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "System.Runtime":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "Private.InternalDiagnostics.System.Net.Http":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "System.Buffers.ArrayPoolEventSource":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "System.Diagnostics.Eventing.FrameworkEventSource":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "Microsoft-Diagnostics-DiagnosticSource":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "Private.InternalDiagnostics.System.Net.Sockets":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "System.Net.Sockets":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "System.Net.NameResolution":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "Private.InternalDiagnostics.System.Net.Security":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "System.Net.Security":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "System.Collections.Concurrent.ConcurrentCollectionsEventSource":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
            case "Private.InternalDiagnostics.System.Net.Primitives":
                EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
                break;
Enter fullscreen mode Exit fullscreen mode

Let's explain shortly about these events:

  • Microsoft-Windows-DotNETRuntime. This provider emits various events from the .NET runtime, including GC, loader, JIT, exception, and other events.
  • System.Threading.Tasks.TplEventSource. This provider logs information on the Task Parallel Library, such as Task scheduler events.
  • System.Runtime. Information about runtime.
  • Private.InternalDiagnostics.System.Net.Http. It is needed for network tracing.
  • System.Buffers.ArrayPoolEventSource. This provider logs information from the ArrayPool.
  • System.Net.Http. This provider logs information from the HTTP stack.
  • System.Net.Sockets. This provider logs information from Socket.
  • System.Net.NameResolution. This provider logs information related to domain name resolution. If you run it, you get many other system events related to allocating and cleaning in memory:
00000000-0000-0000-0000-000000000000 GCEnd_V1
00000000-0000-0000-0000-000000000000 ProcessorCount
0000131c-0000-0000-0000-000026bb9d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 RequestStart https://catfact.ninja:443/fact HTTP/1.1 Policy False
0000391c-0000-0000-0000-000026c59d59 Event
0000391c-0000-0000-0000-000026c59d59 Event
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
00000000-0000-0000-0000-000000000000 GCEnd_V1
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
17c0391c-0000-0000-0000-000026c55d71 BufferAllocated
2fc0391c-0000-0000-0000-000026c55d89 ResolutionStop
2fc0391c-0010-0000-0000-000036c55d89 ConnectStop
32c0391c-0000-0000-0000-000026c55d8c BufferAllocated
69c0391c-0000-0000-0000-000026c55dc3 BufferAllocated
69c0391c-0000-0000-0000-000026c55dc3 BufferAllocated
69c0391c-0000-0000-0000-000026c55dc3 BufferAllocated
00000000-0000-0000-0000-000000000000 GCEnd_V1
69c0391c-0000-0000-0000-000026c55dc3 BufferAllocated
69c0391c-0000-0000-0000-000026c55dc3 BufferAllocated
69c0391c-0000-0000-0000-000026c55dc3 HandshakeStop
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 Event
0000391c-0000-0000-0000-000026c59d59 Event
6ec0391c-0000-0000-0000-000026c55dc8 BufferAllocated
0000391c-0000-0000-0000-000026c59d59 RequestStop
Enter fullscreen mode Exit fullscreen mode

But, is not all, you can add more events to OnEventWritten method:

case 3:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
            case 4:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
            case 5:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
            case 6:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
            case 7:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
            case 8:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
            case 9:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
            case 10:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
            case 11:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
            case 12:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
            case 13:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
            case 14:
                Console.WriteLine(eventData.ActivityId + " " + eventData.EventName + " " + eventData.EventId);
                break;
Enter fullscreen mode Exit fullscreen mode

Each event source has a different count of events and information. I won't stop detailed on this topic.
The result will be the bigger:

00000000-0000-0000-0000-000000000000 ProcessorCount 2
00000000-0000-0000-0000-000000000000 GCSuspendEEBegin_V1 9
00000000-0000-0000-0000-000000000000 GCSuspendEEEnd_V1 8
00000000-0000-0000-0000-000000000000 GCEnd_V1 2
00000000-0000-0000-0000-000000000000 GCHeapStats_V2 4
00000000-0000-0000-0000-000000000000 GCRestartEEBegin_V1 7
00000000-0000-0000-0000-000000000000 GCRestartEEEnd_V1 3
00000000-0000-0000-0000-000000000000 GCFinalizersBegin_V1 14
00000000-0000-0000-0000-000000000000 GCFinalizersEnd_V1 13
00000000-0000-0000-0000-000000000000 GCFinalizersBegin_V1 14
00000000-0000-0000-0000-000000000000 GCFinalizersEnd_V1 13
0000131c-0000-0000-0000-0000f38c9d59 BufferAllocated 2
00000000-0000-0000-0000-000000000000 Associate 3
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 RequestStart https://catfact.ninja:443/fact HTTP/1.1 Policy False
0000391c-0000-0000-0000-0000f3f29d59 NewDiagnosticListener 10
00000000-0000-0000-0000-000000000000 GCFinalizersBegin_V1 14
00000000-0000-0000-0000-000000000000 GCFinalizersEnd_V1 13
0000391c-0000-0000-0000-0000f3f29d59 Event 2
00000000-0000-0000-0000-000000000000 GCSuspendEEBegin_V1 9
00000000-0000-0000-0000-000000000000 GCSuspendEEEnd_V1 8
00000000-0000-0000-0000-000000000000 GCEnd_V1 2
00000000-0000-0000-0000-000000000000 GCHeapStats_V2 4
00000000-0000-0000-0000-000000000000 GCRestartEEBegin_V1 7
00000000-0000-0000-0000-000000000000 GCRestartEEEnd_V1 3
00000000-0000-0000-0000-000000000000 GCFinalizersBegin_V1 14
00000000-0000-0000-0000-000000000000 BufferTrimPoll 5
00000000-0000-0000-0000-000000000000 BufferTrimmed 4
00000000-0000-0000-0000-000000000000 BufferTrimPoll 5
00000000-0000-0000-0000-000000000000 BufferTrimmed 4
0000391c-0000-0000-0000-0000f3f29d59 Event 2
00000000-0000-0000-0000-000000000000 GCFinalizersEnd_V1 13
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
19c0391c-0000-0000-0000-0000f3f25d73 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 ConnectedAsyncDns 7
31c0391c-0000-0000-0000-0000f3f25d8b ResolutionStop 2
00000000-0000-0000-0000-000000000000 Connected 6
31c0391c-0010-0000-0000-0000e3f25d8b ConnectStop 2
6bc0391c-0000-0000-0000-0000f3f25dc5 NoDelegateNoClientCert 11
6bc0391c-0000-0000-0000-0000f3f25dc5 ConcurrentDictionary_AcquiringAllLocks 3
6bc0391c-0000-0000-0000-0000f3f25dc5 ConcurrentDictionary_AcquiringAllLocks 3
6bc0391c-0000-0000-0000-0000f3f25dc5 BufferAllocated 2
6bc0391c-0000-0000-0000-0000f3f25dc5 BufferAllocated 2
00000000-0000-0000-0000-000000000000 GCSuspendEEBegin_V1 9
00000000-0000-0000-0000-000000000000 GCSuspendEEEnd_V1 8
00000000-0000-0000-0000-000000000000 GCEnd_V1 2
00000000-0000-0000-0000-000000000000 GCHeapStats_V2 4
00000000-0000-0000-0000-000000000000 GCRestartEEBegin_V1 7
00000000-0000-0000-0000-000000000000 GCRestartEEEnd_V1 3
00000000-0000-0000-0000-000000000000 GCFinalizersBegin_V1 14
00000000-0000-0000-0000-000000000000 BufferTrimPoll 5
00000000-0000-0000-0000-000000000000 BufferTrimmed 4
00000000-0000-0000-0000-000000000000 BufferTrimmed 4
00000000-0000-0000-0000-000000000000 BufferTrimPoll 5
00000000-0000-0000-0000-000000000000 BufferTrimmed 4
00000000-0000-0000-0000-000000000000 BufferTrimmed 4
00000000-0000-0000-0000-000000000000 BufferTrimmed 4
00000000-0000-0000-0000-000000000000 BufferTrimmed 4
00000000-0000-0000-0000-000000000000 GCFinalizersEnd_V1 13
6bc0391c-0000-0000-0000-0000f3f25dc5 BufferAllocated 2
6bc0391c-0000-0000-0000-0000f3f25dc5 RemoteCertificate 9
6bc0391c-0000-0000-0000-0000f3f25dc5 BufferAllocated 2
6bc0391c-0000-0000-0000-0000f3f25dc5 HandshakeStop 2
0000391c-0000-0000-0000-0000f3f29d59 ConnectionEstablished 4
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 RequestLeftQueue 6
6cc0391c-0000-0000-0000-0000f3f25dc6 RequestHeadersStart 7
6cc0391c-0000-0000-0000-0000f3f25dc6 RequestHeadersStop 8
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 Associate 3
0000391c-0000-0000-0000-0000f3f29d59 Associate 3
6dc0391c-0000-0000-0000-0000f3f25dc7 ResponseHeadersStart 11
6dc0391c-0000-0000-0000-0000f3f25dc7 ResponseHeadersStop 12
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 BufferAllocated 2
0000391c-0000-0000-0000-0000f3f29d59 Event 2
0000391c-0000-0000-0000-0000f3f29d59 Event 2
70c0391c-0000-0000-0000-0000f3f25dca ResponseContentStart 13
70c0391c-0000-0000-0000-0000f3f25dca BufferAllocated 2
70c0391c-0000-0000-0000-0000f3f25dca ResponseContentStop 14
0000391c-0000-0000-0000-0000f3f29d59 RequestStop 2
Enter fullscreen mode Exit fullscreen mode

Conclusion. It's a great tool for diagnostic your HTTP requests, but it allows observe them and use how interceptor. The EventListener can be used as an attribute also. In the next article, I'll show how to get access to HTTP headers and responses. Stay tuned and happy coding!

The source code HERE.

Buy Me A Beer

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .