「譯」 .NET 5 新增的Http, Sockets, DNS 和 TLS 遙測

.NET 一直在穩定的增長和改善對應用程序進行跨平臺的診斷分析,在.NET Core 3.0, 咱們看到了 EventCounters 的介紹,用於觀察和分析指標測量。git

我最近在幾個 .NET Core 的應用程序中使用 counters,來跟蹤服務一段時間內 http 的請求數量。github

.NET 5 一直在進步,我一直在關注 runtime repository 的動態和工做,在 http 發生外部調用時,添加了新的遙測計數器和一些核心組件的事件,包括 HttpClient, Sockets, DNS 和 Security。服務器

在這篇文章中,我將展現如何在 runtime(運行時)消費這些信息,須要注意的是,本文的代碼僅僅是簡單的實現,若是在生產中使用話,你還須要考慮到性能開銷或者其餘。socket

定義 EventListener

.NET 中已經有了 EventListener 抽象類,咱們能夠在代碼中繼承這個類,來自定義一個 listenerasync

internal sealed class TelemetryListener : EventListener
{
    ...
}

接下來,咱們重寫 OnEventSourceCreated 方法,來處理下邊的幾種特定事件的消息ide

protected override void OnEventSourceCreated(EventSource eventSource)
{
    if (eventSource.Name.Equals("System.Net.Sockets") 
        || eventSource.Name.Equals("System.Net.Http")
        || eventSource.Name.Equals("System.Net.NameResolution")
        || eventSource.Name.Equals("System.Net.Security"))
    {
        EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All, new Dictionary<string, string>
        {
            {"EventCounterIntervalSec", "2"}
        });
    }
}

在上面的代碼中,咱們獲取到 eventSource.Name, 而後過濾咱們感興趣的類型的消息,例如, HttpTelemetry 類定義了 EventSource(事件源)的名字叫 System.Net.Http。工具

[EventSource(Name = "System.Net.Http")]
internal sealed class HttpTelemetry : EventSource
{
  ...
}

在這個例子中,咱們感興趣的 event (事件) 和 counters (計數器)來自四個 event sources (事件源)性能

  • NameResolution Telemetry – DNS lookups
  • Sockets Telemetry – Underlying network connections to a server
  • Security Telemetry – Establish TLS
  • Http Telemetry – HttpClient

當 EventSource 匹配一個咱們想要監聽的名字時,咱們調用 EnableEvents 方法,在這個代碼示例中,咱們接收全部等級的 event(事件)和關鍵字,咱們能夠定義一個字典,可能會有其餘額外的參數,當 EventCounters 開始消費時,咱們能夠設置頻率來更新計數器,上面的代碼表示咱們但願計數器每兩秒發送信息。翻譯

下邊的代碼咱們重寫 OnEventWritten 方法日誌

protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
    ...
}

在這種方法中,咱們將添加一些代碼,來監聽事件計數器,而後更新當前值,而且輸出到控制檯。

if (eventData.EventName == "EventCounters")
{
    if (eventData.Payload?.Count <= 0
                    || eventData.Payload?[0] is not IDictionary<string, object> data
                    || !data.TryGetValue("CounterType", out var ct)
                    || !data.TryGetValue("Name", out var n)
                    || ct is not string counterType
                    || n is not string name) return;
    
    var metricValue = metricType switch
    {
        "Sum" when data.TryGetValue("Increment", out var increment) => Convert.ToInt64(increment),
        "Mean" when data.TryGetValue("Mean", out var mean) => Convert.ToInt64(mean),
        _ => 0
    };

    switch (name)
    {
        case "dns-lookups-duration":
            Console.WriteLine($"Event Counter = Avg Duration of Lookup: {metricValue}ms");
            Console.WriteLine();
            break;
        case "dns-lookups-requested":
            Console.WriteLine($"Event Counter = Name Resolution Lookups: {metricValue}");
            Console.WriteLine();
            break;
        case "total-tls-handshakes":
            Console.WriteLine($"Event Counter = Total TLS Handshakes: {metricValue}");
            Console.WriteLine();
            break;
        case "requests-started":
            Console.WriteLine($"Event Counter = HTTP Requests Started: {metricValue}");
            Console.WriteLine();
            break;
        case "requests-failed":
            Console.WriteLine($"Event Counter = HTTP Requests Failed: {metricValue}");
            Console.WriteLine();
            break;
        case "outgoing-connections-established":
            Console.WriteLine($"Event Counter = Outgoing Connections Established: {metricValue}");
            Console.WriteLine();
            break;
        case "http11-connections-current-total":
            Console.WriteLine($"Event Counter = HTTP1.1 Connections Established: {metricValue}");
            Console.WriteLine();
            break;
        case "http20-connections-current-total":
            Console.WriteLine($"Event Counter = HTTP2.0 Connections Established: {metricValue}");
            Console.WriteLine();
            break;
        case "bytes-sent":
            Console.WriteLine($"Event Counter = Bytes Sent: {metricValue}");
            Console.WriteLine();
            break;
        case "bytes-received":
            Console.WriteLine($"Event Counter = Bytes Received: {metricValue}");
            Console.WriteLine();
            break;
    }
}

上面的代碼,我經過 eventData 的屬性過濾了我感興趣的日誌,你能夠注意到,上面我用了一些 C# 9.0 的語法 not 在判斷條件中。

if (eventData.Payload?.Count <= 0
  || eventData.Payload?[0] is not IDictionary<string, object> data
  || !data.TryGetValue("CounterType", out var ct)
  || !data.TryGetValue("Name", out var n)
  || ct is not string counterType
  || n is not string name) return;

下邊的代碼,我用了 C# 8.0的 switch 表達式來定義指標值,根據指標的類型,EventCounters 主體包含一個自增值或者時平均值。

var metricValue = counterType switch
  {
      "Sum" when data.TryGetValue("Increment", out var increment) => Convert.ToInt64(increment),
      "Mean" when data.TryGetValue("Mean", out var mean) => Convert.ToInt64(mean),
      _ => 0
  };

下邊的例子中,我使用 switch 指定了咱們感興趣的事件來源的名稱,而後記錄到控制檯

switch (name)
{
    case "dns-lookups-duration":
        Console.WriteLine($"Event Counter = Avg Duration of Lookup: {metricValue}ms");
        Console.WriteLine();
        break;
    case ...
}

咱們須要每過2s把但當前事件計數器的值輸出到控制檯,你能夠選擇把這些指標數據放到其餘的指標服務,在過去,我把一些事件計數器的值發送到了 Datadog。

下邊的這一塊代碼,我判斷了 EventName, 若是不是 EventCounters,爲了演示,我把這些信息都輸出到了控制檯

if (eventData.EventName != "EventCounters")
{
    Console.WriteLine($"Event = {eventData.EventSource.Name} - {eventData.EventId}:{eventData.EventName}");

    for (var i = 0; i < eventData.PayloadNames?.Count; i++)
    {
        Console.WriteLine(
            $" - {eventData.PayloadNames[i]}: {eventData.Payload?[i]?.ToString() ?? string.Empty}");
    }
}

使用 EventListener

咱們在一個簡單的控制檯應用程序使用 TelemetryListener

internal class Program
{
    private static async Task Main()
    {
        using var listener = new TelemetryListener();

        var client = new HttpClient();

        try
        {
            await client.GetAsync("https://www.stevejgordon.co.uk");
        }
        catch
        {
            // ignore
        }

        await Task.Delay(2000);
    }
}

在這個 main 方法中,我建立了一個 TelemetryListener 實例,開始監聽事件信息,我使用了 HttpClient 調用了個人博客主頁,而後程序等待2s,這樣咱們的 listener 有足夠的時間觸發事件和接受消息。

運行程序後,咱們能夠在控制檯看到這些信息

Event = System.Net.Http - 1:RequestStart
 - scheme: https
 - host: www.stevejgordon.co.uk
 - port: 443
 - pathAndQuery: /
 - versionMajor: 1
 - versionMinor: 1
 - versionPolicy: 0
Event = System.Net.NameResolution - 1:ResolutionStart
 - hostNameOrAddress:
Event = System.Net.NameResolution - 2:ResolutionStop
Event = System.Net.NameResolution - 1:ResolutionStart
 - hostNameOrAddress: www.stevejgordon.co.uk
Event = System.Net.NameResolution - 2:ResolutionStop
Event = System.Net.Sockets - 1:ConnectStart
 - address: InterNetworkV6:28:{1,187,0,0,0,0,32,1,8,216,16,15,240,0,0,0,0,0,0,0,2,127,0,0,0,0}
Event = System.Net.Sockets - 2:ConnectStop
Event = System.Net.Security - 1:HandshakeStart
 - isServer: False
 - targetHost: www.stevejgordon.co.uk
Event = System.Net.Security - 2:HandshakeStop
 - protocol: 3072
Event = System.Net.Http - 4:ConnectionEstablished
 - versionMajor: 1
 - versionMinor: 1
Event = System.Net.Http - 7:RequestHeadersStart
Event = System.Net.Http - 8:RequestHeadersStop
Event = System.Net.Http - 11:ResponseHeadersStart
Event = System.Net.Http - 12:ResponseHeadersStop
Event = System.Net.Http - 13:ResponseContentStart
Event = System.Net.Http - 14:ResponseContentStop
Event = System.Net.Http - 2:RequestStop

Event Counter = HTTP Requests Started: 1
Event Counter = HTTP Requests Failed: 0
Event Counter = HTTP1.1 Connections Established: 1
Event Counter = HTTP2.0 Connections Established: 0
Event Counter = Name Resolution Lookups: 2
Event Counter = Avg Duration of Lookup: 36ms
Event Counter = Outgoing Connections Established: 1
Event Counter = Bytes Received: 68222
Event Counter = Bytes Sent: 354
Event Counter = Total TLS Handshakes: 1

剛開始,咱們看到的事件信息來自與咱們訂閱的4個來源,HttpClient 開始請求個人博客主頁,這須要DNS來解析服務器的IP地址,Socket 鏈接建立,而後TLS握手開始,而後我有了一個TLS 鏈接,Http 請求發出信息而且接收到了響應,咱們能夠在控制檯看到這些輸出信息。

總結

這篇文章特別強調了.NET 的團隊正在積極的添加新的遙測事件和事件計數器時, 這些診斷工具對於咱們分析應用程序起到很關鍵的做用,這些事件和計數器能夠在運行時進程內收集, 而後把這些信息發送到外部的指標服務,他們也支持跨平臺進程跟蹤和監視的應用程序行爲,在將來的文章中,我但願將深刻研究跟蹤、可觀測性,而後使用這些數據。

原文連接:https://www.stevejgordon.co.uk/additional-http-sockets-dns-and-tls-telemetry-in-dotnet-5

最後

歡迎掃碼關注咱們的公衆號,專一國外優秀博客的翻譯和開源項目分享,也能夠添加QQ羣 897216102

相關文章
相關標籤/搜索