16#pragma comment(lib, "wevtapi.lib")
28 : m_bListening(false), m_sListeningChannel(
""), m_pSubscriptionHandle(nullptr), m_fnCallback(nullptr) {
35 void EtwLogReader::RegisterChannelIfNew(
const String& sChannelName) {
38 if (ch.Equals(sChannelName))
return;
57 if (hContext == NULL || hEvt == NULL)
return false;
58 DWORD dwBufferUsed = 0, dwPropertyCount = 0;
59 if (::EvtRender(hContext, hEvt, EvtRenderEventValues, 0, NULL, &dwBufferUsed, &dwPropertyCount))
return false;
60 if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return false;
62 vBuffer.resize(dwBufferUsed);
63 return ::EvtRender(hContext, hEvt, EvtRenderEventValues, dwBufferUsed, vBuffer.data(), &dwBufferUsed, &dwPropertyCount) != FALSE;
68 if (pValues[EvtSystemProviderName].Type == EvtVarTypeString && pValues[EvtSystemProviderName].StringVal != NULL) {
72 if (pValues[EvtSystemEventID].Type == EvtVarTypeUInt16) {
73 evt.
iEventId =
static_cast<int>(pValues[EvtSystemEventID].UInt16Val);
74 }
else if (pValues[EvtSystemEventID].Type == EvtVarTypeUInt32) {
75 evt.
iEventId =
static_cast<int>(pValues[EvtSystemEventID].UInt32Val);
81 if (pValues[EvtSystemLevel].Type == EvtVarTypeByte) {
82 evt.
iLevel =
static_cast<int>(pValues[EvtSystemLevel].ByteVal);
83 }
else if (pValues[EvtSystemLevel].Type == EvtVarTypeUInt16) {
84 evt.
iLevel =
static_cast<int>(pValues[EvtSystemLevel].UInt16Val);
85 }
else if (pValues[EvtSystemLevel].Type == EvtVarTypeUInt32) {
86 evt.
iLevel =
static_cast<int>(pValues[EvtSystemLevel].UInt32Val);
87 }
else if (pValues[EvtSystemLevel].Type == EvtVarTypeNull) {
90 if (pValues[EvtSystemTimeCreated].Type == EvtVarTypeFileTime) {
91 int64_t iTicks =
static_cast<int64_t
>(pValues[EvtSystemTimeCreated].FileTimeVal) + 504911232000000000LL;
98 std::vector<BYTE> vBuffer;
101 PEVT_VARIANT pValues =
reinterpret_cast<PEVT_VARIANT
>(vBuffer.data());
106 void EtwLogReader::FormatEtwEventXml(EVT_HANDLE hEvt, EtwEvent& evt) {
108 DWORD dwUsed = 0, dwProps = 0;
109 WCHAR wXmlBuffer[4096] = { 0 };
111 if (::EvtRender(NULL, hEvt, EvtRenderEventXml, 4096, wXmlBuffer, &dwUsed, &dwProps)) {
113 evt.sRawXml =
String(sNarrowXml.c_str());
115 evt.sRawXml =
"<Event><System><EventID>100</EventID></System></Event>";
119 void EtwLogReader::FormatEtwEventMessage(EVT_HANDLE hEvt,
EtwEvent& evt) {
122 WCHAR wMsgBuf[2048] = { 0 };
124 if (::EvtFormatMessage(NULL, hEvt, 0, 0, NULL, EvtFormatMessageEvent, 2048, wMsgBuf, &dwUsed) && dwUsed > 0) {
126 evt.sMessage =
String(sNarrowMsg.c_str());
130 evt.sMessage = evt.sRawXml.IsEmpty() ?
String(
"ETW System Event") : evt.sRawXml;
133 EtwEvent EtwLogReader::ProcessSingleEtwEvent(EVT_HANDLE hContext, EVT_HANDLE hEvt,
const String& sChannelName) {
139 evt.sProviderName =
"Windows-ETW-Provider";
143 FormatEtwEventXml(hEvt, evt);
144 FormatEtwEventMessage(hEvt, evt);
148 DWORD WINAPI EtwLogReader::Win32EvtSubscribeCallback(EVT_SUBSCRIBE_NOTIFY_ACTION action, PVOID pUserContext, EVT_HANDLE hEvent) {
150 if (action != EvtSubscribeActionDeliver || pUserContext ==
nullptr || hEvent == NULL)
return 0;
152 auto pCallback =
static_cast<Action<const EtwEvent&>*
>(pUserContext);
153 if (!pCallback || !(*pCallback))
return 0;
156 EVT_HANDLE hContext = ::EvtCreateRenderContext(0, NULL, EvtRenderContextSystem);
157 EtwEvent evt = ProcessSingleEtwEvent(hContext, hEvent,
"Windows-ETW");
158 if (hContext) ::EvtClose(hContext);
163 EVT_HANDLE EtwLogReader::SubscribeWin32Channel(
const String& sChannelName, Action<const EtwEvent&>* pCallback) {
165 const char* pszRaw = sChannelName.GetRawString() ? sChannelName.GetRawString() :
"";
167 EVT_HANDLE hSub = ::EvtSubscribe(NULL, NULL, wChannel.c_str(), L
"*", NULL, pCallback, (EVT_SUBSCRIBE_CALLBACK)Win32EvtSubscribeCallback, EvtSubscribeToFutureEvents);
169 DWORD err = ::GetLastError();
170 if (err == ERROR_ACCESS_DENIED) {
171 throw UnauthorizedAccessException(
"Access denied subscribing to ETW channel. Administrator or Performance Log Users membership required.");
173 char buf[256] = { 0 };
174 snprintf(buf,
sizeof(buf),
"EvtSubscribe failed with error code %lu", err);
180 void EtwLogReader::EnumerateWin32Channels(Collections::Generic::List<String>& lstChannels) {
182 EVT_HANDLE hEnum = ::EvtOpenChannelEnum(NULL, 0);
183 if (hEnum == NULL)
return;
185 WCHAR wBuffer[512] = { 0 };
186 DWORD dwReturned = 0;
187 while (::EvtNextChannelPath(hEnum, 512, wBuffer, &dwReturned)) {
189 lstChannels.Add(
String(sPath.c_str()));
206 for (DWORD idx = 0; idx < dwReturned; idx++) {
207 EtwEvent evt = ProcessSingleEtwEvent(hContext, arrEvents[idx], sChannelName);
208 ::EvtClose(arrEvents[idx]);
211 if (iMaxEvents > 0 && lstEvents.
GetCount() >= iMaxEvents)
return true;
218 if (err == ERROR_EVT_CHANNEL_NOT_FOUND || err == ERROR_FILE_NOT_FOUND || err == ERROR_NOT_FOUND || err == ERROR_EVT_INVALID_CHANNEL_PATH)
return;
220 char szBuf[128] = { 0 };
221 snprintf(szBuf,
sizeof(szBuf),
"EvtQuery failed with error code %lu", err);
227 if (!hContext || !hResults)
return;
228 EVT_HANDLE hEvents[10] = { 0 };
229 DWORD dwReturned = 0;
231 while (::EvtNext(hResults, 10, hEvents, INFINITE, 0, &dwReturned)) {
232 if (IterateEvtBatch(hContext, hEvents, dwReturned, sChannelName, iMaxEvents, level, lstEvents))
break;
236 void EtwLogReader::ReadWin32EvtChannel(
const String& sChannelName,
int iMaxEvents,
int iStartIndex,
bool bReverseDirection,
EtwEventLevel level, Collections::Generic::List<EtwEvent>& lstEvents) {
238 const char* pszRaw = sChannelName.GetRawString() ? sChannelName.GetRawString() :
"";
240 DWORD dwFlags = EvtQueryChannelPath | EvtQueryTolerateQueryErrors | (bReverseDirection ? EvtQueryReverseDirection : EvtQueryForwardDirection);
241 EVT_HANDLE hResults = ::EvtQuery(NULL, wChannel.c_str(),
BuildLevelQuery(level).c_str(), dwFlags);
244 EVT_HANDLE hContext = ::EvtCreateRenderContext(0, NULL, EvtRenderContextSystem);
246 ::EvtClose(hResults);
250 if (iStartIndex > 0) ::EvtSeek(hResults, iStartIndex, NULL, 0, EvtSeekRelativeToFirst);
251 IterateEvtResults(hContext, hResults, sChannelName, iMaxEvents, level, lstEvents);
252 ::EvtClose(hContext);
253 ::EvtClose(hResults);
260 EVT_HANDLE hLog = ::EvtOpenLog(NULL, wChannel.c_str(), EvtOpenChannelPath);
261 if (!hLog)
return false;
262 DWORD dwBufferUsed = 0;
263 BYTE buf[
sizeof(EVT_VARIANT) +
sizeof(UINT64)] = { 0 };
264 auto pVar =
reinterpret_cast<PEVT_VARIANT
>(buf);
265 bool bOk = ::EvtGetLogInfo(hLog, EvtLogNumberOfLogRecords,
sizeof(buf), pVar, &dwBufferUsed) != FALSE;
266 if (bOk) uCount =
static_cast<unsigned long long>(pVar->UInt64Val);
274 if (sChannelName.
IsEmpty())
return 0;
275 std::lock_guard<std::mutex> lock(
s_mtxEtw);
276#if defined(_WIN32) || defined(_WIN64)
279 unsigned long long uCount = 0;
286 unsigned long long EtwLogReader::FastQueryLevelCount(
const std::wstring& wChannel,
const wchar_t* pwszFilter) {
288 EVT_HANDLE hResults = ::EvtQuery(NULL, wChannel.c_str(), pwszFilter, EvtQueryChannelPath | EvtQueryTolerateQueryErrors);
289 if (hResults == NULL)
return 0;
290 EVT_HANDLE hEvents[100];
291 DWORD dwReturned = 0;
292 unsigned long long uCount = 0;
293 while (::EvtNext(hResults, 100, hEvents, 100, 0, &dwReturned)) {
294 uCount += dwReturned;
295 for (DWORD i = 0; i < dwReturned; i++) ::EvtClose(hEvents[i]);
297 ::EvtClose(hResults);
301 void EtwLogReader::CountWin32EventsByLevel(
const std::wstring& wChannel, EtwEventLevelCounts& counts) {
303 counts.uCriticalCount = FastQueryLevelCount(wChannel, L
"*[System[(Level=1)]]");
304 counts.uErrorCount = FastQueryLevelCount(wChannel, L
"*[System[(Level=2)]]");
305 counts.uWarningCount = FastQueryLevelCount(wChannel, L
"*[System[(Level=3)]]");
306 counts.uInfoCount = FastQueryLevelCount(wChannel, L
"*[System[(Level=4 or Level=0)]]");
307 counts.uVerboseCount = FastQueryLevelCount(wChannel, L
"*[System[(Level=5)]]");
314 if (sChannelName.
IsEmpty())
return counts;
315 std::lock_guard<std::mutex> lock(
s_mtxEtw);
316#if defined(_WIN32) || defined(_WIN64)
319 CountWin32EventsByLevel(wChannel, counts);
326 std::lock_guard<std::mutex> lock(
s_mtxEtw);
329 EnumerateWin32Channels(lstChannels);
332 bool bExists =
false;
333 for (
int i = 0; i < lstChannels.
GetCount(); i++) {
334 if (lstChannels[i].
Equals(ch)) { bExists =
true;
break; }
336 if (!bExists) lstChannels.
Add(ch);
349 for (
const auto& evt : events) {
351 if (iStartIndex > 0 && iSkipped++ < iStartIndex)
continue;
353 if (iMaxEvents > 0 && lstEvents.
GetCount() >= iMaxEvents)
break;
360 std::lock_guard<std::mutex> lock(
s_mtxEtw);
361 RegisterChannelIfNew(sChannelName);
366 ReadWin32EvtChannel(sChannelName, iMaxEvents, iStartIndex, bReverseDirection, level, lstEvents);
380 std::lock_guard<std::mutex> lock(
s_mtxEtw);
381 RegisterChannelIfNew(sChannelName);
383 m_sListeningChannel = sChannelName;
384 m_fnCallback = fnCallback;
387 EVT_HANDLE hSub = SubscribeWin32Channel(sChannelName, &m_fnCallback);
388 m_pSubscriptionHandle = (
void*)hSub;
394 std::lock_guard<std::mutex> lock(
s_mtxEtw);
395 if (!m_bListening)
return;
398 if (m_pSubscriptionHandle !=
nullptr) {
399 ::EvtClose((EVT_HANDLE)m_pSubscriptionHandle);
400 m_pSubscriptionHandle =
nullptr;
404 m_bListening =
false;
405 m_sListeningChannel =
"";
406 m_fnCallback =
nullptr;
Defines the exception thrown when an invalid argument is provided to a method.
Defines the exception thrown when a method call is invalid for the object's current state.
Utility routines for high-performance UTF-8, UTF-16, and wide-character string conversions.
Provides an abstraction for time, timestamps, and elapsed time calculation.
The exception that is thrown when the operating system denies access because of an I/O error or a spe...
Encapsulates a method that has parameters and does not return a value.
ArgumentException(const String &sMessage)
Initializes a new instance of the ArgumentException class with a specified error message.
Represents a strongly typed list of objects accessible by index.
int GetCount() const
Gets the number of elements contained in the List.
void Add(const T &item)
Adds an object to the end of the List.
static DateTimeOffset Now()
Gets a DateTimeOffset object that is set to the current date and time on the current computer,...
DateTimeOffset()
Initializes a new instance of DateTimeOffset to 0 ticks.
EtwLogReader()
Initializes a new instance of EtwLogReader.
void StopListening()
Terminates active real-time event listening and closes subscription handles.
static Collections::Generic::List< String > GetEventChannels()
Enumerates all registered ETW and Windows Event Log channel paths.
virtual ~EtwLogReader()
Destructor ensuring active asynchronous event subscriptions are terminated.
static EtwEventLevelCounts GetChannelEventLevelCounts(const String &sChannelName)
Aggregates the count of events in a channel broken down by severity level.
static Collections::Generic::List< EtwEvent > ReadEvents(const String &sChannelName)
Reads all available events from the specified channel.
void StartListening(const String &sChannelName, Action< const EtwEvent & > fnCallback)
Initiates an asynchronous real-time subscription to events on the specified channel.
static unsigned long long GetChannelEventCount(const String &sChannelName)
Retrieves total number of recorded events in a channel.
InvalidOperationException(const String &sMessage)
Initializes a new instance of the InvalidOperationException class with a specified error message.
virtual bool Equals(const Object &obj) const
Determines whether the specified Object is equal to the current Object.
Represents text as a sequence of UTF-8 code units with culture-invariant operations.
String()
Initializes a new instance of the String class to an empty string.
const char * GetRawString() const
SystemException()
Initializes a new instance of the SystemException class with a default message.
UnauthorizedAccessException()
Initializes a new instance of the UnauthorizedAccessException class with a default message.
static std::string WCharToUtf8(const wchar_t *pWStr)
Converts a null-terminated UTF-16 wchar_t string into a UTF-8 std::string.
static std::wstring Utf8ToWChar(const char *pUtf8Str)
Converts a null-terminated UTF-8 char string into a UTF-16 std::wstring.
static void FilterChannelEvents(const std::vector< EtwEvent > &events, EtwEventLevel level, int iStartIndex, int iMaxEvents, Collections::Generic::List< EtwEvent > &lstEvents)
static bool MatchEventLevelFilter(const EtwEvent &evt, EtwEventLevel level)
static void ExtractLevelAndTime(PEVT_VARIANT pValues, EtwEvent &evt)
static std::map< String, std::vector< EtwEvent > > s_mapChannelEvents
static std::vector< String > s_vRegisteredChannels
EtwEventLevel
Filter levels corresponding to standard Windows ETW severity classifications.
@ Warning
Non-critical condition that indicates potential future problems.
@ Critical
Abnormal exit or severe failure requiring immediate intervention.
@ Info
Normal operational informational events.
@ Error
Significant problem that indicates a runtime failure.
@ All
All events regardless of level.
@ Verbose
Detailed developer or diagnostic trace information.
static std::mutex s_mtxEtw
static void PopulateEventProperties(EVT_HANDLE hContext, EVT_HANDLE hEvt, EtwEvent &evt)
static void HandleQueryFailure(DWORD err)
static void ExtractProviderAndId(PEVT_VARIANT pValues, EtwEvent &evt)
static bool RenderSystemProperties(EVT_HANDLE hContext, EVT_HANDLE hEvt, std::vector< BYTE > &vBuffer)
static bool QueryWin32LogRecordCount(const std::wstring &wChannel, unsigned long long &uCount)
static std::wstring BuildLevelQuery(EtwEventLevel level)
Represents an individual Event Tracing for Windows (ETW) event record.
int iLevel
Severity level of the event.
int iEventId
Numeric identifier for the event type.
DateTimeOffset dtTimeCreated
Precise timestamp when the event was generated.
String sChannelName
Name of the event log channel (e.g. "Application", "System").
String sProviderName
Name or GUID of the publishing event provider.
Aggregate counts of events partitioned by severity level within a channel.