背景
最近在做一些性能优化的时候,发现很多应用经常会出现文件 I/O 爆高,但又没做啥事情。结果分析下来发现,都是在写日志。
于是就有了这么一篇,怎么利用 Windows 自己提供的 ETW 来记录应用日志。
通过 ETW 记录日志有什么好处
ETW(Event trace for Windows) 是 Windows 提供的一种事件跟踪机制,可以用来记录应用程序的运行时事件。
ETW Provider 提供的日志相比文件日志,主要有以下优点:
- 方便扩展:日志采用结构化数据,方便第三方解析
- 高性能、低开销:采用内核缓冲区,批量写入磁盘
- 实时记录:支持实时事件流
- 隐私性:采用二进制日志 + 资源 dll/exe 的方式,在不具备资源 dll 的前提下,无法解析
怎么做
作为普通应用程序,没法直接使用微软的标准库来做,这里会相对麻烦一点。但通过微软提供的一些工具,可以稍微提升一定的效率。
我们采取基于清单的方式来创建 provider。
- 创建清单文件
- 打开
ecmangen.exe
我们可以通过 SDK 中自带的图形化工具(ecmangen.exe
)来生成清单文件。
如果你安装了 8.1 的 SDK,你可以在 C:\Program Files (x86)\Windows Kits\8.1\bin\x64\
找到它。
如果你没安装,可以看下面这个:
打开它,会得到一个这样的应用:

然后就可以开始创建 provider 了。
- 创建 Provider
选择【Events Section】部分,右击,选择【New】-【Provider】
根据提示,填写好 Name
、Symbol
、GUID
(可以自动生成一个)、Decoding file locations
(这个可以后面再进行修改的)
如下图所示:

下一步,我们就要开始创建我们的 event 了。开始 Event 之前,我们需要先创建一些关键属性处理,以方便 Event 创建的时候进行选择。
- 创建基础属性
关于基础属性,详细介绍可以看:ETW:Windows 事件追踪
这里,我们需要创建或修改的有:
Channels
:主要用来区分不同权限的事件。比如 Admin 为管理员需要关注的事件;而 Debug 则是开发人员需要关注的事件Tasks
:主要用来对事件进行分类的。Tasks 主要针对不同的任务或者场景。这个需要结合业务来拆分Opcodes
:也是用来对事件进行分类的。Opcodes 主要针对的具体的行为。比如创建进程等Templates
:用于辅助事件显示结构化信息的关键属性。给与字段的解析和定义。比如,在场景激活这个事件中,我有这么一个 template:

还有额外的一些属性可以按需使用。
- 创建 Event
关键就是创建 Event 了,这是对日志的直接表达。
需要定义 Symbol、EventId、Event Version(主要用于版本管理)和一些基础属性,这个需要根据业务定义来。
如下是场景激活事件的事件详情,供参考。

然后按需完成事件的增加。满足需求后,通过【File】-【Save/Save As】将 man 文件保存即可。
于是我们就得到了我们的 xxx.man 清单文件。
- 创建日志宏
创建完清单文件,接下来就可以通过清单文件生成宏信息,在代码中调用更方便。
这里需要用到 mc.exe
,可以通过 man 文件来生成 .h
、.rc
、.bin
文件。
mc.exe 是跟随 win10 的 SDK 带下来的。
frend in ~\Desktop\testt λ mc -um .\MyProvider.man
frend in ~\Desktop\testt λ ls
Directory: C:\Users\frend\Desktop\testt
Mode LastWriteTime Length Name
---- ------------- ------ ----
----- 1/23/2025 4:28 PM 144 MSG00001.bin
----- 1/23/2025 4:28 PM 35741 MyProvider.h
-a--- 12/5/2024 11:52 AM 4912 MyProvider.man
----- 1/23/2025 4:28 PM 77 MyProvider.rc
----- 1/23/2025 4:28 PM 970 MyProviderTEMP.BIN
于是,我们就可以创建 VS 项目,然后引入 .h 和 .rc 进行使用了。
- 创建项目
这里需要把 .h 和 .rc 和 bin 文件加到项目中。
然后,就可以开始写代码了。.h 中有了较多的宏,所以写起来也非常的方便。
直接放代码:
#include <iostream>
#include <windows.h>
#include <evntprov.h>
#include "MyProvider.h"
REGHANDLE g_ProviderHandle = NULL;
int main()
{
ULONG status = EventRegisterProviderDemo();
if (status != ERROR_SUCCESS) {
std::cout << "Failed to register ETW provider. Error: " << status << std::endl;
return status;
}
// 写入事件
while (true) {
if (EventEnabledScene_Active()) {
// 准备参数
const wchar_t* sceneName = L"MainScene";
ULONG processId = GetCurrentProcessId();
EventWriteScene_Active(sceneName, processId);
}
std::cout << "Event written successfully!\n";
Sleep(1000);
}
// 程序结束前注销 provider
EventUnregisterProviderDemo();
return 0;
}
编译生成,即可将日志写到 ETW 中去了。
但这个时候,是没法看到这个 provider 和对应的 event 的。
这里建议将日志部分封装成一个 dll,将 bin 文件链接到 dll 中,用于解码 message
- 安装清单文件
由于截至到目前为止,我们还没有安装清单文件,所以系统中是没法查到这个 provider 的,也就没法获取日志了。
安装清单文件可以用 wevtutil
来安装。
wevtutil im .\MyProvider.man
安装完成后,就可以看到了。可以通过 logman
来获取:
frend in ~\Desktop\testt λ logman query providers "providerDemo"
Provider GUID
-------------------------------------------------------------------------------
ProviderDemo {16676E5F-DFC8-4A42-9F1E-14CF280F7AFD}
Value Keyword Description
-------------------------------------------------------------------------------
0x8000000000000000 Debug
0x4000000000000000 Admin
Value Level Description
-------------------------------------------------------------------------------
0x04 win:Informational Information
The command completed successfully.
怎么查看日志
记录了日志,接下来就可以看看怎么获取日志了。
- 实时观察
观察的话,可以通过 Microsoft Message Analyzer
实时观察。
如果在创建 provider 的时候,正确填写了 Decoding file locations
,那应该直接在 Microsoft Message Analyzer
就可以看到了。
如果 dll/exe 存放的位置跟预定的 Decoding file locations
不一致,可以通过注册表来进行修改。
注册表地址:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Publishers\<Provider GUID>
,例如:

- 通过 C++ 获取
还可以通过接口来获取日志,这样方便编写日志工具来获取、分析日志。
具体怎么做可以看:如何通过 C++ 实时监听 ETW 事件
0