ETW是Windows内核级高性能事件追踪子系统,采用内核缓冲区、环形队列和延迟写入设计,EnableTrace启用后单次事件写入开销极低。

ETW 是 Windows 上真正低开销的事件采集机制
ETW(Event Tracing for Windows)不是 .NET 专属,而是 Windows 内核级的高性能事件追踪子系统。它用内核缓冲区 + 环形队列 + 延迟写入设计,EnableTrace 开启后,单次事件写入通常仅需 Console.WriteLine 或日志库的毫秒级开销。关键在于:它不依赖托管堆分配、不触发 GC、不走 .NET 日志管道。
实际使用中,EventSource 类只是 ETW 的 .NET 封装层,它在运行时自动生成 ETW provider GUID,并把 WriteEvent 调用翻译为 EventWriteTransfer 等原生 ETW API。这意味着你写的 EventSource 代码,最终跑的是 Windows 原生事件路径。
常见误区是认为“加了 [EventSource(Name = "MyApp")] 就自动高性能”——其实不然。如果在事件方法里做了字符串拼接、对象序列化、或调用了 ToString(),这些操作仍在用户态执行,会显著拖慢吞吐。性能收益只在“事件写入”环节,前置计算仍由你负责。
EventSource.WriteEvent 的参数传递必须是原始类型或结构体
EventSource 不支持任意对象序列化。它只接受 int、string、Guid、DateTime、long、枚举、以及标记 [EventData] 的简单 struct。传入 class 实例、Dictionary 或匿名类型会直接抛出 ArgumentException:“The event field type is not supported.”
典型错误写法:
public void LogRequest(HttpRequest req) {
WriteEvent(1, req.Path, req.Method); // ❌ req.Path 可能是 null 或复杂属性链
}
正确做法是提前提取值,且避免空引用:
- 用
req?.Path ?? "(null)"替代req.Path - 不要传
req.Headers,而应传req.Headers.Count或预提取关键 header 值 - 若需结构化数据,定义轻量
struct并用[NonEvent]方法做转换
用 PerfView 捕获 EventSource 事件时要注意 Provider 名称匹配
PerfView 默认只收集已知 provider(如 Microsoft-Windows-DotNETRuntime),你自定义的 EventSource 必须显式启用。Provider 名称默认是类名全限定名,但可通过构造函数覆盖:
public sealed class MyEventSource : EventSource
{
public static MyEventSource Log = new MyEventSource();
private MyEventSource() : base("MyCompany-MyApp") { } // ✅ 显式指定名称
}
启动 PerfView 时,必须在 “Collect → Additional Providers” 中填入:MyCompany-MyApp:0x10000:5(其中 0x10000 是 Level=Verbose,5 是 Keyword=All)。漏掉冒号或关键字位会导致事件完全不出现。
基于Intranet/Internet 的Web下的办公自动化系统,采用了当今最先进的PHP技术,是综合大量用户的需求,经过充分的用户论证的基础上开发出来的,独特的即时信息、短信、电子邮件系统、完善的工作流、数据库安全备份等功能使得信息在企业内部传递效率极大提高,信息传递过程中耗费降到最低。办公人员得以从繁杂的日常办公事务处理中解放出来,参与更多的富于思考性和创造性的工作。系统力求突出体系结构简明
另一个常见问题:程序启动后才打开 PerfView,会错过初始化阶段的事件(如 EventSource 自身的 ManifestData 事件)。建议先在 PerfView 中点击 “Collect”,再启动目标进程。
高频率场景下必须用 EventSourceMessageAttribute 控制字段裁剪
当每秒写入数千次事件时,即使参数是原始类型,字符串字段仍可能成为瓶颈。ETW 对每个事件的大小有限制(默认约 64KB),但更现实的瓶颈是内存拷贝和内核缓冲区竞争。
[EventSourceMessage] 不是装饰用的——它让编译器在生成 manifest 时把字段标记为可选(eventFieldAttr="Optional"),配合 PerfView 的 “Filter Events” 或 TraceEvent 库的 Filter,可在采集时跳过未启用的字段,减少序列化开销。
例如:
[Event(1, Level = EventLevel.Informational)]
public void RequestStarted(
string path,
[EventSourceMessage] string userAgent, // ✅ 可被过滤掉
int statusCode)
{
WriteEvent(1, path, userAgent, statusCode);
}
这样在低开销采集中,可只保留 path 和 statusCode,彻底跳过 userAgent 字符串的复制与写入。
真正容易被忽略的是:这种裁剪只在 ETW 层生效;如果你用 EventListener 在进程内监听,所有字段仍会传入,裁剪无效。所以生产环境高频打点,务必搭配外部工具(PerfView / Windows Performance Recorder)采集,而非依赖进程内监听。










