.NET 一直在穩定的增加和改善對應用程式進行跨平臺的診斷分析,在.NET Core 3.0, 我們看到了 EventCounters 的介紹,用於觀察和分析指標測量。
我最近在幾個 .NET Core 的應用程式中使用 counters,來跟蹤服務一段時間內 http 的請求數量。
.NET 5 一直在進步,我一直在關注 runtime repository 的動態和工作,在 http 發生外部呼叫時,新增了新的遙測計數器和一些核心元件的事件,包括 HttpClient, Sockets, DNS 和 Security。
在這篇文章中,我將展示如何在 runtime(執行時)消費這些資訊,需要注意的是,本文的程式碼僅僅是簡單的實現,如果在生產中使用話,你還需要考慮到效能開銷或者其他。
定義 EventListener
.NET 中已經有了 EventListener 抽象類,我們可以在程式碼中繼承這個類,來自定義一個 listener
internal sealed class TelemetryListener : EventListener
{
...
}
接下來,我們重寫 OnEventSourceCreated
方法,來處理下邊的幾種特定事件的訊息
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