惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Latest news
Latest news
T
Troy Hunt's Blog
V
Vulnerabilities – Threatpost
L
LINUX DO - 热门话题
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
V
V2EX
博客园 - 司徒正美
B
Blog RSS Feed
AWS News Blog
AWS News Blog
MyScale Blog
MyScale Blog
Scott Helme
Scott Helme
Cisco Talos Blog
Cisco Talos Blog
Last Week in AI
Last Week in AI
NISL@THU
NISL@THU
博客园 - Franky
P
Proofpoint News Feed
博客园_首页
C
CERT Recently Published Vulnerability Notes
雷峰网
雷峰网
S
Schneier on Security
P
Proofpoint News Feed
Hugging Face - Blog
Hugging Face - Blog
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
月光博客
月光博客
WordPress大学
WordPress大学
The Hacker News
The Hacker News
T
Threatpost
阮一峰的网络日志
阮一峰的网络日志
A
Arctic Wolf
Microsoft Azure Blog
Microsoft Azure Blog
T
The Exploit Database - CXSecurity.com
Engineering at Meta
Engineering at Meta
罗磊的独立博客
T
The Blog of Author Tim Ferriss
D
Darknet – Hacking Tools, Hacker News & Cyber Security
I
Intezer
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
K
Kaspersky official blog
SecWiki News
SecWiki News
云风的 BLOG
云风的 BLOG
美团技术团队
C
Cybersecurity and Infrastructure Security Agency CISA
博客园 - 【当耐特】
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Security Latest
Security Latest
C
Cyber Attacks, Cyber Crime and Cyber Security
B
Blog
S
Security Affairs

博客园 - China Soft

.NET 程序保护实战系列01-流水线架构与保护引擎总览 试官问:你用 AI 编程半年了,那怎么保证 Claude Code 写出来的代码是对的? C#实现控制台多区域输出 园友特惠| 1Panel 企业版 & AI 一体机限量放售,最高直降 5100! 上位机程序发布打包成安装包---Inno Setup 这是一篇学习笔记,分享给大家,如果大家喜欢,也会开启USB的专辑 我问了 AI 一个问题:编码能力贬值后,什么能力值钱? Visual Studio 2026(VS2026) 密钥/激活码 Dddify:给 ASP.NET Core 项目一套轻量、清晰、可落地的 DDD 基础设施 C# ESP32/STM32 轻量 Web 能力库:PicoServer.Nano 高性能百度OCR ONNX Runtime C#实现 别再说 AI 开发就是调接口了!5 种主流模式一次讲清 使用Cursor实现管理系统登录界面的快速开发 在 Avalonia 中编写高性能动画 傻子可懂的 Harness Engineering 入门教程 + 项目实战,一次搞懂 AI 编程工程化! 提升 Text2SQL 准确率 AI/Vibe Coding,本质是软件人工时代向软件工业时代发展 体验优先:十分钟使用 Python+LangChain 玩转阿里通义千问 大模型基础(二):必懂5大基础概念《Token、上下文窗口、Embedding、预训练、微调》 大模型基础(一):什么是LLM? Prompt、Agent、Function Call、Skill、MCP,傻傻分不清楚? 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 作为一线研发,我快被全网的 AI 焦虑逼疯了。 Qt QtWebEngine 白屏的解决方案 都是微软亲儿子,WPF凭啥干不掉WinForm?这3个场景说明白了 .NET 高级开发 | C# 中的动态代码:反射、EMIT、表达式树、Roslyn、Source Generators 大模型看大模型:推理Token的能耗用电量比对 WorkBuddy:从“我是谁”到“帮我干活” Prompt 焚诀——一个模板,终结你和 AI 的所有沟通问题 [项目]涉案资产信息化管理平台(山东某执法单位定制开发) 大模型RAG实战,从被骂不靠谱到成为部门MVP,这是我的踩坑全记录 白帽子为什么几乎都绕不开 httpx:一款 HTTP 资产探测工具的技术价值 从Prompt工程到Skill工程:Agent Skills开放标准彻底改变了AI协作方式 MAUI项目在Android平台通过U盘实现软件更新 微软竟然出了免费的 AI 应用开发课?!我已经学上了 一文彻底搞懂 OpenClaw 的架构设计与运行原理(万字图文) AI到底聪明在哪——从手机人脸识别说起 硬核科普:为何小米任天堂封杀 Root?揭秘操作系统从启动到“夺权”的底层游戏
.NET 高级开发 | 开发 .NET 诊断工具、链路追踪原理
China Soft · 2026-04-10 · via 博客园 - China Soft

https://www.cnblogs.com/whuanle/p/19809387

系列教程地址:https://docs.whuanle.cn/

开发 .NET 诊断工具

System.Diagnostics、Microsoft.Diagnostics

在 System.Diagnostics Microsoft.Diagnostics 命名空间中的接口用于诊断 .NET 程序,里面涉及到很多诊断技术,由于个人技术水平限制以及篇幅原因,笔者只介绍比较常用的几种诊断方法,不深入探讨原理。

Debug、Trace

在 System.Diagnostics 命名空间中有 Debug、Trace 两个类型,用来追踪代码的执行Debug、Trace 可以打印调试信息并使用断点检查逻辑,使代码更可靠,而不会影响发运程序的性能。System.Diagnostics.Debug 只在 Debug 环境下起作用,在 Release 环境下会失效,除此之外两者的接口几乎一样。

下面示例代码,当 sum 的值在 100 以内时程序正常执行,当 sum 的值大于等于 100时会触发断点,IDE 会跳转到该位置,此时会引起我们的注意。

static void Main()
{
	List<int> ls = new List<int> { 30, 40, 50 };
	Sum(ls);
}

static int Sum(List<int> ls)
{
	var sum = 0;
	foreach (var item in ls)
	{
		sum += item;
		

image-20230905191911465

.Assert() 会触发断点同时打印信息。.NET Runtime 源代码中就大量地使用了 Debug.Assert() ,笔者个人也常常在项目中使用 Debug.Assert,比如某个条件分支很少情况下会执行,如果该分支被执行,需要引起开发者关注。一方面 Debug 只在调试模式下有效,不会干扰正式发布的项目运行。IDE 断点正在当前设备环境中起效,不能跟其它设备共享断点位置,而 Debug 在代码中,所有人都可以使用。

此外通过 Debug、Trace 打印信息,方法有 Write 、WriteLine 、 WriteIf 、 WriteLineIf 、Print 等,默认打印到 IDE 的调试输出。

int value = -1;
Debug.Assert(value != -1, "值不应该为 -1.");
Debug.WriteLineIf(value == -1, "当前值居然为 -1.");
---- DEBUG ASSERTION FAILED ----
---- Assert Short Message ----                  
值不应该为 -1.
---- Assert Long Message ----

   at Demo2.Diagnostics.Program.Main(String[] args) in E:\demo\Program.cs:line 14
当前值居然为 -1.

也可以通过监听器将信息打印到控制台或文件中,如需将调试信息打印到控制台,可以注册相关的侦听器:

Trace.Listeners.Add(new ConsoleTraceListener();
// 或者 Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));

注意, Debug 没有 Listeners 属性,因为 Debug 使用的是 Trace 的侦听器,即给 Trace 配置之后,Debug 也会生效。

image-20230905192856029

.NET 中主要有以下监听器 DefaultTraceListener、TextWriterTraceListener、ConsoleTraceListener、DelimitedListTraceListener、EventLogTraceListener 等。

如果需要输出到文件中,可以自行继承 TextWriterTraceListener ,编写文件流输出,也可以使用 DelimitedListTraceListener。

示例:

// TraceListener listener = new TextWriterTraceListener(new FileStream(@"C:\debugfile.txt", FileMode.OpenOrCreate));
TraceListener listener = new DelimitedListTraceListener(@"D:\debugfile.txt");

Debug.Listeners.Add(listener);
Debug.WriteLine("打印调试信息");

为了格式化输出流,可以使用相关属性控制排版:

属性说明
AutoFlush 获取或设置一个值,通过该值指示每次写入后是否应在 Flush() 上调用 Listeners。
IndentLevel 获取或设置缩进级别。
IndentSize 获取或设置缩进的空格数。
Debug.WriteLine("One");
// 缩进
Debug.Indent();
Debug.WriteLine("Two");
Debug.WriteLine("Three");
One
    Two
    Three
Four

EventSource、EventListener

System.Diagnostics.Tracing 命名空间下的 EventSource 、DiagnosticCounter 都是抽象类,EventSource 称为事件源,用于定义事件和记录日志,运行时本身会有很多事件,比如 GC 回收事件、线程退出事件等,可以使用工具进行监听和分析。DiagnosticCounter 称为计数器,用于收集各种类型的性能指标,比如内存使用量、GC 触发次数。

从技术角度出发,我们需要关注两个部分,自定义 EventSource 、DiagnosticCounter ,以及如何监听 EventSource 、DiagnosticCounter 。

自定义 EventSource 、DiagnosticCounter

我们可以在程序中自定义事件源,然后通过代码监听或 .NET 中的诊断工具收集这些事件。

示例项目在 Demo2.ES 中,下面的代码定义了一个事件源 MyEventSource 类,其内部使用了一个计数器。其功能非常简单,每次循环时,触发 MyEventSource 内的计数器自动递增 1。

internal class Program
{
	private static readonly MyEventSource EventSource = new MyEventSource();
	public static void Main(string[] args)
	{
		int number = 0;
		while (true)
		{
			number++;
			EventSource.LogEvent("测试", number);
			Thread.Sleep(1000);
		}
	}
}

然后我们通过 dotnet-trace 工具收集事件信息。

我们在一个空目录中启动命令行工具,然后执行下面的命令。

安装 dotnet-trace 工具:

dotnet tool install -g dotnet-trace

列出系统中的 .NET 程序及其进程 id:

$> dotnet-trace ps
18064  Demo2.ES ...

收集进程 18064 中的事件以及指定事件名称包括 MyEvent:

dotnet-trace collect --process-id 18064 --providers MyEvent

一段时间之后按下回车键或 Ctrl+C,在目录中可以找到一个 .nettrace 文件,使用 Visual Studio 打开 .nettrace 文件。

然后点击表格头部 “提供程序名称/事件名称” 右侧的图标,选中 MyEvent 对事件进行筛选。

image-20230905204024958


然后点击具体的事件可以观察到该事件的信息,文本列显示的是使用 WriteEvent(1, message, favoriteNumber); 记录的事件的信息,时间戳列记录了事件发生的时间,右侧属性面板显示了事件的详细信息。

image-20230905204137262

自定义事件源需要继承 EventSource ,EventSource 中包含很多记录事件发生的函数,如 WriteEvent 方法,然后使用 dotnet-trace 工具可以捕获程序中发生的事件,获取事件记录的信息。

由于在自定义事件源中我们添加了计数器,所以我们还可以使用 dotnet-counters 工具收集计数信息。

dotnet-counters monitor  --process-id 18064  --counters MyEvent

image-20230906204124030


如上图所示,dotnet-counters 统计了 MyEvent 计数器在一秒钟之内触发的次数。

当然,在不指定计数器名称时, dotnet-counters 可以显示 CLR 中很多的信息,在 ASP.NET Core 中可以显示流量速率、并发量等。

dotnet-counters monitor  --process-id 18064

image-20230906204258069

我们也可以给计数器设置一些属性:

_incrementingEventCounter = new IncrementingEventCounter("MyEvent", this)
{
	// 以下两项只能从构造函数传入
	

除了 IncrementingEventCounter ,还存在其它类型的计数器:

  • EventCounter:事件计数器
  • IncrementingEventCounter :递增事件计数器
  • PollingCounter :轮询计数器
  • IncrementingPollingCounter :递增轮询计数器

通过以上的例子,我们可以看到 dotnet-trace、dotnet-counters 两个工具收集的信息是基于 EventSource、EventCounter 的,.NET CLI 诊断工具通过 .NET 内置或自定义的事件或计数器收集信息。

在 .NET 中本身提供一些事件源,线程池、类型系统、异常、运行时方法等事件,比如在 .NET Runtime 中主要提供以下两种事件:

  • Microsoft-Windows-DotNETRuntime 提供运行时发出的各种事件,如 GC、JIT、异常等事件;
  • Microsoft-DotNETCore-SampleProfiler 提供托管线程堆栈的快照;

.NET 还有其他内置的事件,读者感兴趣的话可以通过官方文档了解更多,这里就不再赘述。

编写收集器

上一节中,我们使用了 dotnet-trace、dotnet-counters 两个工具捕获程序中的事件和计数器,在本节中,笔者将介绍如何使用 EventListener 捕获程序内发生的事件。

在 System.Net.Http 包中,有着跟 http 请求相关的接口,例如 HttpClient ,System.Net.Http 也内置了一些事件,记录 HTTP 请求信息,列举部分 Http 事件如下:

事件名称说明
RequestStart HTTP 请求已启动。
RequestStop HTTP 请求已完成。
RequestFailed HTTP 请求失败。
ConnectionEstablished HTTP 连接已建立。
ConnectionClosed HTTP 连接已关闭。

那么,我们编写一个 HttpClient 程序,然后编写一个监听器监听程序发出的所有 Http 请求并记录状态码。

示例代码在 Demo2.ESTrace 中。

public static class Program
{
	public static async Task Main(string[] args)
	{
		


运行之后在控制台中可以看到事件信息。

image-20230906185820686


此外,通过 Visual Studio 的诊断工具也可以看到相关的事件,或者使用 dotnet-trace 工具进行收集。

image-20230906202624409


当然,当前使用的监听器还只能收集自身进程内的事件,在前面我们使用的 dotnet-trace、dotnet-counters 是如何通过跨进程收集的呢?接下来我们学习如何编写一个跨进程收集信息的诊断工具。

编写诊断工具

dotnet CLI 工具很多,除了前面提到的 dotnet-trace、dotnet-counters ,还有 dotnet-dump、dotnet-gcdump 等 CLI 工具,都可以通过跨进程的方式收集程序的信息。在本小节中,我们通过诊断工具包实现跨进程收集信息,实现 类似的工具。

写一个简单的控制台程序并启动:

private static readonly HttpClient Http = new();
public static async Task Main(string[] args)
{
	while (true)
	{
		await Http.GetAsync("https://www.baidu.com");
		await Task.Delay(1000);
		GC.Collect();
	}
}

然后编写一个诊断工具,示例项目在 Demo2.Diagnostics 中。

创建一个控制台,引入两个包:

  <ItemGroup>
    <PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.442301" />
    <PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.5" />
  </ItemGroup>

编写一个订阅事件信息的程序:

internal class Program
{
	static void Main(string[] args)
	{
		

接着启动 Demo2.Diagnostics,输入控制台的进程号,即可考察到监听的进程 GC 事件。

image-20231226205434863

我们也可以做一个像 dotnet-dump 的工具,截取进程快照。

static async Task Main()
{
	var processes = DiagnosticsClient.GetPublishedProcesses()
	.Select(Process.GetProcessById)
	.Where(process => process != null);
	Console.WriteLine("请输入进程 id");
	foreach (var item in processes)
	{
		Console.WriteLine($"{item.Id} ------ {item.ProcessName}");
	}

	var read = Console.ReadLine();
	ArgumentNullException.ThrowIfNullOrEmpty(read);
	var pid = int.Parse(read);

	var client = new DiagnosticsClient(pid);
	await client.WriteDumpAsync(
		dumpType: DumpType.Full,
		dumpPath: $"D:/{pid}_{DateTime.Now.Ticks}.dmp",
		logDumpGeneration: true,
		token: CancellationToken.None
	);
}

然后使用 Visual Studio 打开 .dmp 文件,可以看到很多快照信息。

image-20230916073556831

已经介绍了 System.Diagnostics 中的接口,以及介绍了部分 .NET CLI 工具的使用方法,因此不单独介绍 dotnet-gcdump、dotnet-dump 等诊断工具,读者可根据需要阅读官方文档。
https://learn.microsoft.com/en-us/dotnet/core/diagnostics/microsoft-diagnostics-netcore-client

目前,很多 C# 语言编写的可观测性框架是基于 System.Diagnostics、Microsoft.Diagnostics 的,由于本书不涉及微服务,因此对于这类框架在 C# 程序中的原理不再赘述,请参考官方文档。

也可以参考笔者的另一个 MQ 项目:https://mmq.whuanle.cn/7.opentelemetry.html