引入
ETW(Event Tracing for Windows)是 Windows 平台上非常强大的事件跟踪机制,广泛用于调试、性能分析以及日志记录等场景。相较于传统的日志方式,ETW 在内核态和用户态都有提供良好的事件跟踪接口,可以帮助开发者更灵活地收集关键信息。
在许多应用场景下,我们不仅要监听某个 Provider(事件提供者)产生的所有事件,还需要进一步通过 Event ID 或关键字(Keyword)等进行过滤。ETW 本身提供了非常高效的内核态过滤功能,可以在事件触发时就直接按需过滤,减少数据传输和处理的开销。
步骤
以下是实现实时监听并根据 Event ID 进行过滤的核心步骤解析。
1. 启动 ETW Session
EVENT_TRACE_PROPERTIES
结构
这是配置 ETW 会话属性的核心结构,除了常用的缓冲区大小、标记位(WNODE_FLAG_TRACED_GUID
)等,还需要指定LogFileMode
为EVENT_TRACE_REAL_TIME_MODE
,才能开启实时模式。StartTrace
通过StartTrace
来启动一个新的 ETW Session,并返回一个TRACEHANDLE
会话句柄。在调用时,我们需要将EVENT_TRACE_PROPERTIES
与会话名称一起传入。
ULONG status = StartTrace(&hSession, sessionName, pSessionProperties);
如果调用成功,hSession
会被赋值,用于后续操作(如EnableTraceEx2
、ControlTrace
等)。
2. 配置内核态过滤器(EVENT_FILTER_EVENT_ID)
EVENT_FILTER_EVENT_ID
结构
这是一个用于表明我们想要捕获(或排除)哪些事件 ID 的结构。其主要成员包括:FilterIn
:TRUE
表示“包含”指定的事件 ID;若为FALSE
,则表示排除。Count
:要过滤的事件 ID 个数。Events
:指向实际的事件 ID 数组。
EVENT_FILTER_DESCRIPTOR
这个结构用于向 ETW API 传递过滤器的元信息,它包括指向过滤器数据的指针、数据大小以及过滤器的类型。
此处我们将Type
设置为EVENT_FILTER_TYPE_EVENT_ID
,表示按事件 ID 进行过滤。ENABLE_TRACE_PARAMETERS
在调用EnableTraceEx2
时,需要通过该结构把过滤器数组传进去(实际上可以传入多个过滤器)。
我们在示例中只用到一个过滤器,因此FilterDescCount = 1
。
3. 启用 Provider 并应用过滤规则
EnableTraceEx2
该函数是新的、较灵活的 Provider 启用接口。相比EnableTrace
,它支持传入更多参数,比如过滤描述符。
其中比较关键的参数有:EVENT_CONTROL_CODE_ENABLE_PROVIDER
:表示启用 Provider。TRACE_LEVEL_VERBOSE
:设置事件级别为最高级别。MatchAnyKeyword = 0xFFFFFFFF
和MatchAllKeyword = 0
:如果对关键字无特殊需求,可以这样表示“不限制”。enableParams
:我们前面构造好的过滤参数。
status = EnableTraceEx2(
hSession,
&providerGuid,
EVENT_CONTROL_CODE_ENABLE_PROVIDER,
TRACE_LEVEL_VERBOSE,
0xFFFFFFFF,
0,
0,
&enableParams
);
4. 打开 Trace 并实时消费事件
OpenTrace
打开指定会话的 Trace,用于后续调用ProcessTrace
读取事件数据。
我们通过设置logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD
来指定实时模式和EventRecord
回调类型。ProcessTrace
开始循环接收事件,直到会话停止或遇到其他终止条件。
在循环过程中,每个新到的事件都会调用我们在logFile.EventRecordCallback
中指定的回调函
EVENT_TRACE_LOGFILEW logFile = {};
logFile.LoggerName = (LPWSTR)sessionName;
logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME
| PROCESS_TRACE_MODE_EVENT_RECORD;
logFile.EventRecordCallback = (PEVENT_RECORD_CALLBACK)EventRecordCallback;
5. 回调函数:EventRecordCallback
该函数的签名固定,入参是 PEVENT_RECORD
。通过 EventRecord->EventHeader.EventDescriptor
,可以拿到事件的 ID、Level 等信息(在内核态已经过滤了指定 ID,因此进入回调的事件一定符合过滤条件)。
若需要进一步获取其他字段(如自定义字段、Payload 数据等),可以使用以下 TDH(Tracing Data Helper)API:
TdhGetEventInformation()
:获取事件元数据。TdhFormatProperty()
:获取并格式化指定属性。
6. 停止会话 & 资源清理
在不再需要监听时,通过 ControlTrace
并指定 EVENT_TRACE_CONTROL_STOP
来停止会话。随后可以调用 CloseTrace
关闭之前打开的 Trace。最后释放掉分配的内存等资源。
常见问题与注意事项
- 权限问题
启动 ETW Session 需要管理员权限,建议在“以管理员身份”运行时执行,否则可能会返回权限不足的错误。 - 重复启动会话
当会话名相同且已存在时,StartTrace
可能会失败或返回特定错误,需注意清理或改名。 - 过滤规则的上限
不同版本的 Windows 对过滤器支持可能略有差异,如果需要更复杂的过滤(例如同时根据多个关键字和事件级别过滤),需要仔细阅读相关文档,并在ENABLE_TRACE_PARAMETERS
中传入更多过滤描述符。 - 实时模式下的负载
如果流量巨大,实时模式可能会消耗较多 CPU 和内存。实际生产环境中要结合性能需求,必要时采用文件日志或分段模式进行持久化。 - 回调函数中的处理
在回调函数中尽量保证轻量级的操作,因为它是由系统在内部线程中同步回调。如果回调过于耗时,可能会导致事件堆积或丢失。
完整代码
#include <windows.h>
#include <evntrace.h>
#include <tdh.h> // 包含 TdhGetEventInformation 等
#include <iostream>
#include <string>
#pragma comment(lib, "tdh.lib")
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "ole32.lib")
//---------------------------------------------------------------------------
// 全局回调,用于处理已通过内核态过滤后的事件
//---------------------------------------------------------------------------
static VOID WINAPI EventRecordCallback(PEVENT_RECORD pEventRecord)
{
// 这里的事件一定是 Event ID = 100 或 200(因为已在内核态过滤)
USHORT eventId = pEventRecord->EventHeader.EventDescriptor.Id;
std::wcout << L"Received ETW event, ID = " << eventId << L"\n";
// 如果需要获取更多字段,可使用 TdhGetEventInformation()、TdhFormatProperty() 等
}
//---------------------------------------------------------------------------
// BufferCallback:可选,用于监测缓冲区情况,一般直接返回 TRUE
//---------------------------------------------------------------------------
static ULONG WINAPI BufferCallback(EVENT_TRACE_LOGFILEW* /*pLogFile*/)
{
return TRUE; // 返回 TRUE 继续处理
}
//---------------------------------------------------------------------------
// 主函数
//---------------------------------------------------------------------------
int wmain()
{
//------------------------------------------------------------------------
// 0. Provider GUID & Event ID
//------------------------------------------------------------------------
// 目标 Provider 的 GUID: {cfc18ec0-96b1-4eba-961b-622caee05b0a}
GUID providerGuid =
{ 0xcfc18ec0, 0x96b1, 0x4eba, {0x96, 0x1b, 0x62, 0x2c, 0xae, 0xe0, 0x5b, 0x0a} };
// 只想捕获的事件 ID 列表
USHORT desiredEvents[] = { 100, 200 };
//------------------------------------------------------------------------
// 1. 准备并启动 ETW Session(实时模式)
//------------------------------------------------------------------------
// 1.1 分配 EVENT_TRACE_PROPERTIES
size_t bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + 2 * MAX_PATH * sizeof(WCHAR);
EVENT_TRACE_PROPERTIES* pSessionProperties =
(EVENT_TRACE_PROPERTIES*)malloc(bufferSize);
if (!pSessionProperties)
{
std::wcerr << L"Failed to allocate memory for session properties.\n";
return 1;
}
ZeroMemory(pSessionProperties, bufferSize);
pSessionProperties->Wnode.BufferSize = (ULONG)bufferSize;
pSessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
pSessionProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
LPCWSTR sessionName = L"MyCppEtwSession";
TRACEHANDLE hSession = 0;
// 1.2 调用 StartTrace
ULONG status = StartTrace(&hSession, sessionName, pSessionProperties);
if (status != ERROR_SUCCESS)
{
std::wcerr << L"StartTrace failed, error: " << status << L"\n";
free(pSessionProperties);
return 1;
}
//------------------------------------------------------------------------
// 2. 准备用于 “内核态” 过滤 Event ID 的数据结构
// 关键在 EnableTraceEx2 + EVENT_FILTER_DESCRIPTOR
//------------------------------------------------------------------------
// 2.1 填充 EVENT_FILTER_EVENT_ID 结构(声明捕获的 Event ID 列表)
EVENT_FILTER_EVENT_ID eventIdFilter = {};
eventIdFilter.FilterIn = TRUE; // TRUE 表示 "只" 捕获这些事件(而不是排除)
eventIdFilter.Count = (USHORT)_countof(desiredEvents);
eventIdFilter.Events = desiredEvents;
// 2.2 包装成 EVENT_FILTER_DESCRIPTOR
EVENT_FILTER_DESCRIPTOR filterDesc = {};
ZeroMemory(&filterDesc, sizeof(filterDesc));
// 计算大小:EVENT_FILTER_EVENT_ID 本身 + (Count-1)*sizeof(USHORT)
filterDesc.Ptr = (ULONG_PTR)&eventIdFilter;
filterDesc.Size = sizeof(EVENT_FILTER_EVENT_ID)
+ (eventIdFilter.Count - 1) * sizeof(USHORT);
filterDesc.Type = EVENT_FILTER_TYPE_EVENT_ID; // 按 Event ID 过滤
// 2.3 构造 ENABLE_TRACE_PARAMETERS,用于 EnableTraceEx2
ENABLE_TRACE_PARAMETERS enableParams;
ZeroMemory(&enableParams, sizeof(enableParams));
enableParams.Version = ENABLE_TRACE_PARAMETERS_VERSION_2;
enableParams.EnableProperty = 0; // 不需要特殊属性
enableParams.ControlFlags = 0;
enableParams.SourceId = 0;
enableParams.FilterDescCount = 1; // 我们的过滤器个数
enableParams.EnableFilterDesc = &filterDesc;
//------------------------------------------------------------------------
// 3. 调用 EnableTraceEx2,传入过滤规则
//------------------------------------------------------------------------
status = EnableTraceEx2(
hSession, // 第一步 StartTrace 返回的会话句柄
&providerGuid, // 要启用的 Provider
EVENT_CONTROL_CODE_ENABLE_PROVIDER,
TRACE_LEVEL_VERBOSE, // 事件级别
0xFFFFFFFF, // MatchAnyKeyword
0, // MatchAllKeyword
0, // 超时时间(0 表示默认)
&enableParams // 我们的过滤参数
);
if (status != ERROR_SUCCESS)
{
std::wcerr << L"EnableTraceEx2 failed, error: " << status << L"\n";
goto Cleanup;
}
//------------------------------------------------------------------------
// 4. OpenTrace + ProcessTrace(实时消费事件)
//------------------------------------------------------------------------
EVENT_TRACE_LOGFILEW logFile = {};
logFile.LoggerName = (LPWSTR)sessionName;
logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME
| PROCESS_TRACE_MODE_EVENT_RECORD;
logFile.EventRecordCallback = (PEVENT_RECORD_CALLBACK)EventRecordCallback;
logFile.BufferCallback = (PEVENT_TRACE_BUFFER_CALLBACKW)BufferCallback;
TRACEHANDLE hTrace = OpenTraceW(&logFile);
if (hTrace == INVALID_PROCESSTRACE_HANDLE)
{
std::wcerr << L"OpenTrace failed.\n";
goto Cleanup;
}
std::wcout << L"Start capturing ETW events (only EventID=100 or 200)...\n";
status = ProcessTrace(&hTrace, 1, NULL, NULL);
if (status != ERROR_SUCCESS)
{
std::wcerr << L"ProcessTrace failed, error: " << status << L"\n";
}
//------------------------------------------------------------------------
// 5. 清理:停止会话 & 关闭 Trace
//------------------------------------------------------------------------
status = ControlTrace(hSession, sessionName, pSessionProperties, EVENT_TRACE_CONTROL_STOP);
if (status != ERROR_SUCCESS)
{
std::wcerr << L"ControlTrace (Stop) failed, error: " << status << L"\n";
}
CloseTrace(hTrace);
Cleanup:
if (pSessionProperties)
{
free(pSessionProperties);
pSessionProperties = nullptr;
}
std::wcout << L"ETW capturing finished.\n";
return 0;
}
1