.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

「译」 .NET 5 新增的Http, Sockets, DNS 和 TLS 遥测的更多相关文章

  1. iOS 9,为前端世界都带来了些什么?「译」 - 高棋的博客

    2015 年 9 月,Apple 重磅发布了全新的 iPhone 6s/6s Plus.iPad Pro 与全新的操作系统 watchOS 2 与 tvOS 9(是的,这货居然是第 9 版),加上已经 ...

  2. 「译」JUnit 5 系列:条件测试

    原文地址:http://blog.codefx.org/libraries/junit-5-conditions/ 原文日期:08, May, 2016 译文首发:Linesh 的博客:「译」JUni ...

  3. 「译」JUnit 5 系列:扩展模型(Extension Model)

    原文地址:http://blog.codefx.org/design/architecture/junit-5-extension-model/ 原文日期:11, Apr, 2016 译文首发:Lin ...

  4. 「译」JavaScript 的怪癖 1:隐式类型转换

    原文:JavaScript quirk 1: implicit conversion of values 译文:「译」JavaScript 的怪癖 1:隐式类型转换 译者:justjavac 零:提要 ...

  5. jvm系列(十):如何优化Java GC「译」

    本文由CrowHawk翻译,是Java GC调优的经典佳作. 本文翻译自Sangmin Lee发表在Cubrid上的"Become a Java GC Expert"系列文章的第三 ...

  6. jvm系列(七):如何优化Java GC「译」

    本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作. Sangmin Lee发表在Cubrid上的”Become a Java GC Expert”系列文章 ...

  7. 「译」forEach循环中你不知道的3件事

    前言 本文925字,阅读大约需要7分钟. 总括: forEach循环中你不知道的3件事. 原文地址:3 things you didn't know about the forEach loop in ...

  8. 「译」setState如何知道它该做什么?

    本文翻译自:How Does setState Know What to Do? 原作者:Dan Abramov 如果有任何版权问题,请联系shuirong1997@icloud.com 当你在组件中 ...

  9. 「译」JUnit 5 系列:环境搭建

    原文地址:http://blog.codefx.org/libraries/junit-5-setup/ 原文日期:15, Feb, 2016 译文首发:Linesh 的博客:环境搭建 我的 Gith ...

随机推荐

  1. 老猿Python博文汇总目录--按标题排序

    ☞ ░ 前往老猿Python博文目录 ░ 本部分为老猿CSDN全部博文的汇总(含转载部分),所有文章在此未进行归类,仅按文章标题排序,方便关键字查找.本部分内容将至少以周为单位定期更新,可能不包含发布 ...

  2. Python 装饰器(装饰器的简单使用)

    简单介绍了装饰器的一些基本内容,包含定义.本质.原则.如何实现. 1.装饰器的定义 定义:一种增加函数功能的简单方法,可以快速地给不同的函数或类插入相同的功能. 简单点就是:高阶函数+嵌套函数 -&g ...

  3. CF850F Rainbow Balls 题解

    考虑最后变成哪一种颜色. 设 \(s = \sum\limits_{i=1}^n a_i\) 设现在有 \(k\) 种当前颜色, 需要全部变成该种颜色, 期望步数为 \(f_k\). 考虑状态转移.设 ...

  4. Linux 批量创建user和批量删除用户

    Linux 批量创建user和批量删除用户 以下为批量创建用户: #首先我们需要创建一个xxx.txt文件,把需要的我们创建的用户写在这个文本里面来,注意:每写完一个用户都需要换行. vim user ...

  5. Vue项目上线环境部署,项目优化策略,生成打包报告,及上线相关配置

    Node.js简介 Node.js是一个基于Chrome V8引擎的JavaScript运行环境,用来方便快速地搭建易于扩展的网络应用.Node.js使用了一个事件驱动.非阻塞式I/O的模型,使其轻量 ...

  6. Navicat操作Oracle

    1.出现connection to server failed, probable Oracle Net admin error 更换oci.dll,Navicat有自己默认的oci.dll,更换成你 ...

  7. STL——容器(Set & multiset)的迭代器

    1.set.insert(elem);     //在容器中插入元素. 2.set.begin();         //返回容器中第一个数据的迭代器. 3.set.end();          / ...

  8. STL—— 容器(vector)的内存分配,声明时的普通构造&带参构造

    vector 的几种带参构造 & 初始化与内存分配: 1. 普通的带参构造: vector 的相关对象可以在声明时通过 vector 的带参构造函数进行内存分配,如下: 1 #include ...

  9. MySQL主从重新同步

    主从数据不一致,重新配置主从同步也是一种解决方法. 1.从库停止主从复制 stop slave; 2.对主库数据库加锁 flush tables with read lock; 3.备份主库数据 my ...

  10. 系统类 字符串String类

    java编程语言中的字符串. 在java中,String类是不可变的,对String类的任何改变,都是返回一个新的String类对象. String 对象是 System.Char 对象的有序集合,用 ...