master
c 588 lines 20.7 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "nd_log-internals.h"
4
5 #if defined(OS_WINDOWS) && (defined(HAVE_ETW) || defined(HAVE_WEL))
6
7 // --------------------------------------------------------------------------------------------------------------------
8 // construct an event id
9
10 // load message resources generated header
11 #include "wevt_netdata.h"
12
13 // include the common definitions with the message resources and manifest generator
14 #include "nd_log-to-windows-common.h"
15
16 #if defined(HAVE_ETW)
17 // we need the manifest, only in ETW mode
18
19 // eliminate compiler warnings and load manifest generated header
20 #undef EXTERN_C
21 #define EXTERN_C
22 #undef __declspec
23 #define __declspec(x)
24 #include "wevt_netdata_manifest.h"
25
26 static REGHANDLE regHandle;
27 #endif
28
29 // Function to construct EventID
30 static DWORD complete_event_id(DWORD facility, DWORD severity, DWORD event_code) {
31 DWORD event_id = 0;
32
33 // Set Severity
34 event_id |= ((DWORD)(severity) << EVENT_ID_SEV_SHIFT) & EVENT_ID_SEV_MASK;
35
36 // Set Customer Code Flag (C)
37 event_id |= (0x0 << EVENT_ID_C_SHIFT) & EVENT_ID_C_MASK;
38
39 // Set Reserved Bit (R) - typically 0
40 event_id |= (0x0 << EVENT_ID_R_SHIFT) & EVENT_ID_R_MASK;
41
42 // Set Facility
43 event_id |= ((DWORD)(facility) << EVENT_ID_FACILITY_SHIFT) & EVENT_ID_FACILITY_MASK;
44
45 // Set Code
46 event_id |= ((DWORD)(event_code) << EVENT_ID_CODE_SHIFT) & EVENT_ID_CODE_MASK;
47
48 return event_id;
49 }
50
51 DWORD construct_event_id(ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, MESSAGE_ID messageID) {
52 DWORD event_code = construct_event_code(source, priority, messageID);
53 return complete_event_id(FACILITY_NETDATA, get_severity_from_priority(priority), event_code);
54 }
55
56 static bool check_event_id(ND_LOG_SOURCES source __maybe_unused, ND_LOG_FIELD_PRIORITY priority __maybe_unused, MESSAGE_ID messageID __maybe_unused, DWORD event_code __maybe_unused) {
57 #ifdef NETDATA_INTERNAL_CHECKS
58 DWORD generated = construct_event_id(source, priority, messageID);
59 if(generated != event_code) {
60
61 // this is just used for a break point, to see the values in hex
62 char current[UINT64_HEX_MAX_LENGTH];
63 print_uint64_hex(current, generated);
64
65 char wanted[UINT64_HEX_MAX_LENGTH];
66 print_uint64_hex(wanted, event_code);
67
68 const char *got = current;
69 const char *good = wanted;
70 internal_fatal(true, "EventIDs mismatch, expected %s, got %s", good, got);
71 }
72 #endif
73
74 return true;
75 }
76
77 // --------------------------------------------------------------------------------------------------------------------
78 // initialization
79
80 // Define provider names per source (only when not using ETW)
81 static const wchar_t *wel_provider_per_source[_NDLS_MAX] = {
82 [NDLS_UNSET] = NULL, // not used, linked to NDLS_DAEMON
83 [NDLS_ACCESS] = NETDATA_WEL_PROVIDER_ACCESS_W, //
84 [NDLS_ACLK] = NETDATA_WEL_PROVIDER_ACLK_W, //
85 [NDLS_COLLECTORS] = NETDATA_WEL_PROVIDER_COLLECTORS_W,//
86 [NDLS_DAEMON] = NETDATA_WEL_PROVIDER_DAEMON_W, //
87 [NDLS_HEALTH] = NETDATA_WEL_PROVIDER_HEALTH_W, //
88 [NDLS_DEBUG] = NULL, // used, linked to NDLS_DAEMON
89 };
90
91 bool wel_replace_program_with_wevt_netdata_dll(wchar_t *str, size_t size) {
92 const wchar_t *replacement = L"\\wevt_netdata.dll";
93
94 // Find the last occurrence of '\\' to isolate the filename
95 wchar_t *lastBackslash = wcsrchr(str, L'\\');
96
97 if (lastBackslash != NULL) {
98 // Calculate new length after replacement
99 size_t newLen = (lastBackslash - str) + wcslen(replacement);
100
101 // Ensure new length does not exceed buffer size
102 if (newLen >= size)
103 return false; // Not enough space in the buffer
104
105 // Terminate the string at the last backslash
106 *lastBackslash = L'\0';
107
108 // Append the replacement filename
109 wcsncat(str, replacement, size - wcslen(str) - 1);
110
111 // Check if the new file exists
112 if (GetFileAttributesW(str) != INVALID_FILE_ATTRIBUTES)
113 return true; // The file exists
114 else
115 return false; // The file does not exist
116 }
117
118 return false; // No backslash found (likely invalid input)
119 }
120
121 static bool wel_add_to_registry(const wchar_t *channel, const wchar_t *provider, DWORD defaultMaxSize) {
122 // Build the registry path: SYSTEM\CurrentControlSet\Services\EventLog\<LogName>\<SourceName>
123 wchar_t key[MAX_PATH];
124 if(!provider)
125 swprintf(key, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\%ls", channel);
126 else
127 swprintf(key, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\%ls\\%ls", channel, provider);
128
129 HKEY hRegKey;
130 DWORD disposition;
131 LONG result = RegCreateKeyExW(HKEY_LOCAL_MACHINE, key,
132 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hRegKey, &disposition);
133
134 if (result != ERROR_SUCCESS)
135 return false; // Could not create the registry key
136
137 // Check if MaxSize is already set
138 DWORD maxSize = 0;
139 DWORD size = sizeof(maxSize);
140 if (RegQueryValueExW(hRegKey, L"MaxSize", NULL, NULL, (LPBYTE)&maxSize, &size) != ERROR_SUCCESS) {
141 // MaxSize is not set, set it to the default value
142 RegSetValueExW(hRegKey, L"MaxSize", 0, REG_DWORD, (const BYTE*)&defaultMaxSize, sizeof(defaultMaxSize));
143 }
144
145 wchar_t modulePath[MAX_PATH];
146 if (GetModuleFileNameW(NULL, modulePath, MAX_PATH) == 0) {
147 RegCloseKey(hRegKey);
148 return false;
149 }
150
151 if(wel_replace_program_with_wevt_netdata_dll(modulePath, _countof(modulePath))) {
152 RegSetValueExW(hRegKey, L"EventMessageFile", 0, REG_EXPAND_SZ,
153 (LPBYTE)modulePath, (wcslen(modulePath) + 1) * sizeof(wchar_t));
154
155 DWORD types_supported = EVENTLOG_SUCCESS | EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
156 RegSetValueExW(hRegKey, L"TypesSupported", 0, REG_DWORD, (LPBYTE)&types_supported, sizeof(DWORD));
157 }
158
159 RegCloseKey(hRegKey);
160 return true;
161 }
162
163 #if defined(HAVE_ETW)
164 static void etw_set_source_meta(struct nd_log_source *source, USHORT channelID, const EVENT_DESCRIPTOR *ed) {
165 // It turns out that the keyword varies per only per channel!
166 // so, to log with the right keyword, Task, Opcode we copy the ids from the header
167 // the messages compiler (mc.exe) generated from the manifest.
168
169 source->channelID = channelID;
170 source->Opcode = ed->Opcode;
171 source->Task = ed->Task;
172 source->Keyword = ed->Keyword;
173 }
174
175 // Callback for provider enable/disable notifications
176 static void NTAPI ProviderEnableCallback(
177 LPCGUID SourceId __maybe_unused,
178 ULONG IsEnabled,
179 UCHAR Level __maybe_unused,
180 ULONGLONG MatchAnyKeyword __maybe_unused,
181 ULONGLONG MatchAllKeyword __maybe_unused,
182 PEVENT_FILTER_DESCRIPTOR FilterData __maybe_unused,
183 PVOID CallbackContext __maybe_unused
184 ) {
185 spinlock_lock(&nd_log.eventlog.provider_lock);
186 nd_log.eventlog.provider_enabled = IsEnabled ? true : false;
187 spinlock_unlock(&nd_log.eventlog.provider_lock);
188 }
189
190 static bool etw_register_provider(void) {
191 spinlock_init(&nd_log.eventlog.provider_lock);
192 nd_log.eventlog.provider_enabled = false;
193
194 // Register the ETW provider
195 if (EventRegister(&NETDATA_ETW_PROVIDER_GUID, ProviderEnableCallback, NULL, &regHandle) != ERROR_SUCCESS)
196 return false;
197
198 etw_set_source_meta(&nd_log.sources[NDLS_DAEMON], CHANNEL_DAEMON, &ED_DAEMON_INFO_MESSAGE_ONLY);
199 etw_set_source_meta(&nd_log.sources[NDLS_COLLECTORS], CHANNEL_COLLECTORS, &ED_COLLECTORS_INFO_MESSAGE_ONLY);
200 etw_set_source_meta(&nd_log.sources[NDLS_ACCESS], CHANNEL_ACCESS, &ED_ACCESS_INFO_MESSAGE_ONLY);
201 etw_set_source_meta(&nd_log.sources[NDLS_HEALTH], CHANNEL_HEALTH, &ED_HEALTH_INFO_MESSAGE_ONLY);
202 etw_set_source_meta(&nd_log.sources[NDLS_ACLK], CHANNEL_ACLK, &ED_ACLK_INFO_MESSAGE_ONLY);
203 etw_set_source_meta(&nd_log.sources[NDLS_UNSET], CHANNEL_DAEMON, &ED_DAEMON_INFO_MESSAGE_ONLY);
204 etw_set_source_meta(&nd_log.sources[NDLS_DEBUG], CHANNEL_DAEMON, &ED_DAEMON_INFO_MESSAGE_ONLY);
205
206 DWORD wait_start = GetTickCount();
207 while(true) {
208 spinlock_lock(&nd_log.eventlog.provider_lock);
209 bool enabled = nd_log.eventlog.provider_enabled;
210 spinlock_unlock(&nd_log.eventlog.provider_lock);
211
212 if(enabled)
213 return true;
214
215 // Timeout after 5 seconds
216 if(GetTickCount() - wait_start > 5000) {
217 EventUnregister(regHandle);
218 return false;
219 }
220
221 Sleep(10); // Short sleep between checks
222 }
223 }
224 #endif
225
226 bool nd_log_init_windows(void) {
227 if(nd_log.eventlog.initialized)
228 return true;
229
230 // validate we have the right keys
231 if(
232 !check_event_id(NDLS_COLLECTORS, NDLP_INFO, MSGID_MESSAGE_ONLY, MC_COLLECTORS_INFO_MESSAGE_ONLY) ||
233 !check_event_id(NDLS_DAEMON, NDLP_ERR, MSGID_MESSAGE_ONLY, MC_DAEMON_ERR_MESSAGE_ONLY) ||
234 !check_event_id(NDLS_ACCESS, NDLP_WARNING, MSGID_ACCESS_USER, MC_ACCESS_WARN_ACCESS_USER) ||
235 !check_event_id(NDLS_HEALTH, NDLP_CRIT, MSGID_ALERT_TRANSITION, MC_HEALTH_CRIT_ALERT_TRANSITION) ||
236 !check_event_id(NDLS_DEBUG, NDLP_ALERT, MSGID_ACCESS_FORWARDER_USER, MC_DEBUG_ALERT_ACCESS_FORWARDER_USER))
237 return false;
238
239 #if defined(HAVE_ETW)
240 if(nd_log.eventlog.etw && !etw_register_provider())
241 return false;
242 #endif
243
244 // if(!nd_log.eventlog.etw && !wel_add_to_registry(NETDATA_WEL_CHANNEL_NAME_W, NULL, 50 * 1024 * 1024))
245 // return false;
246
247 // Loop through each source and add it to the registry
248 for(size_t i = 0; i < _NDLS_MAX; i++) {
249 nd_log.sources[i].source = i;
250
251 const wchar_t *sub_channel = wel_provider_per_source[i];
252
253 if(!sub_channel)
254 // we will map these to NDLS_DAEMON
255 continue;
256
257 DWORD defaultMaxSize = 0;
258 switch (i) {
259 case NDLS_ACLK:
260 defaultMaxSize = 5 * 1024 * 1024;
261 break;
262
263 case NDLS_HEALTH:
264 defaultMaxSize = 35 * 1024 * 1024;
265 break;
266
267 default:
268 case NDLS_ACCESS:
269 case NDLS_COLLECTORS:
270 case NDLS_DAEMON:
271 defaultMaxSize = 20 * 1024 * 1024;
272 break;
273 }
274
275 if(!nd_log.eventlog.etw) {
276 if(!wel_add_to_registry(NETDATA_WEL_CHANNEL_NAME_W, sub_channel, defaultMaxSize))
277 return false;
278
279 // when not using a manifest, each source is a provider
280 nd_log.sources[i].hEventLog = RegisterEventSourceW(NULL, sub_channel);
281 if (!nd_log.sources[i].hEventLog)
282 return false;
283 }
284 }
285
286 if(!nd_log.eventlog.etw) {
287 // Map the unset ones to NDLS_DAEMON
288 for (size_t i = 0; i < _NDLS_MAX; i++) {
289 if (!nd_log.sources[i].hEventLog)
290 nd_log.sources[i].hEventLog = nd_log.sources[NDLS_DAEMON].hEventLog;
291 }
292 }
293
294 nd_log.eventlog.initialized = true;
295 return true;
296 }
297
298 bool nd_log_init_etw(void) {
299 nd_log.eventlog.etw = true;
300 return nd_log_init_windows();
301 }
302
303 bool nd_log_init_wel(void) {
304 nd_log.eventlog.etw = false;
305 return nd_log_init_windows();
306 }
307
308 // --------------------------------------------------------------------------------------------------------------------
309 // we pass all our fields to the windows events logs
310 // numbered the same way we have them in memory.
311 //
312 // to avoid runtime memory allocations, we use a static allocations with ready to use buffers
313 // which are immediately available for logging.
314
315 #define SMALL_WIDE_BUFFERS_SIZE 256
316 #define MEDIUM_WIDE_BUFFERS_SIZE 2048
317 #define BIG_WIDE_BUFFERS_SIZE 16384
318 static wchar_t small_wide_buffers[_NDF_MAX][SMALL_WIDE_BUFFERS_SIZE];
319 static wchar_t medium_wide_buffers[2][MEDIUM_WIDE_BUFFERS_SIZE];
320 static wchar_t big_wide_buffers[2][BIG_WIDE_BUFFERS_SIZE];
321
322 static struct {
323 size_t size;
324 wchar_t *buf;
325 } fields_buffers[_NDF_MAX] = { 0 };
326
327 #if defined(HAVE_ETW)
328 static EVENT_DATA_DESCRIPTOR etw_eventData[_NDF_MAX - 1];
329 #endif
330
331 static LPCWSTR wel_messages[_NDF_MAX - 1];
332
333 __attribute__((constructor)) void wevents_initialize_buffers(void) {
334 for(size_t i = 0; i < _NDF_MAX ;i++) {
335 fields_buffers[i].buf = small_wide_buffers[i];
336 fields_buffers[i].size = SMALL_WIDE_BUFFERS_SIZE;
337 }
338
339 fields_buffers[NDF_NIDL_INSTANCE].buf = medium_wide_buffers[0];
340 fields_buffers[NDF_NIDL_INSTANCE].size = MEDIUM_WIDE_BUFFERS_SIZE;
341
342 fields_buffers[NDF_REQUEST].buf = big_wide_buffers[0];
343 fields_buffers[NDF_REQUEST].size = BIG_WIDE_BUFFERS_SIZE;
344 fields_buffers[NDF_MESSAGE].buf = big_wide_buffers[1];
345 fields_buffers[NDF_MESSAGE].size = BIG_WIDE_BUFFERS_SIZE;
346
347 for(size_t i = 1; i < _NDF_MAX ;i++)
348 wel_messages[i - 1] = fields_buffers[i].buf;
349 }
350
351 // --------------------------------------------------------------------------------------------------------------------
352
353 #define is_field_set(fields, fields_max, field) ((field) < (fields_max) && (fields)[field].entry.set)
354
355 static const char *get_field_value_unsafe(struct log_field *fields, ND_LOG_FIELD_ID i, size_t fields_max, BUFFER **tmp) {
356 if(!is_field_set(fields, fields_max, i) || !fields[i].eventlog)
357 return "";
358
359 static char number_str[MAX(MAX(UINT64_MAX_LENGTH, DOUBLE_MAX_LENGTH), UUID_STR_LEN)];
360
361 const char *s = NULL;
362 if (fields[i].logfmt_annotator)
363 s = fields[i].logfmt_annotator(&fields[i]);
364
365 else
366 switch (fields[i].entry.type) {
367 case NDFT_TXT:
368 s = fields[i].entry.txt;
369 break;
370 case NDFT_STR:
371 s = string2str(fields[i].entry.str);
372 break;
373 case NDFT_BFR:
374 s = buffer_tostring(fields[i].entry.bfr);
375 break;
376 case NDFT_U64:
377 print_uint64(number_str, fields[i].entry.u64);
378 s = number_str;
379 break;
380 case NDFT_I64:
381 print_int64(number_str, fields[i].entry.i64);
382 s = number_str;
383 break;
384 case NDFT_DBL:
385 print_netdata_double(number_str, fields[i].entry.dbl);
386 s = number_str;
387 break;
388 case NDFT_UUID:
389 if (!uuid_is_null(*fields[i].entry.uuid)) {
390 uuid_unparse_lower_compact(*fields[i].entry.uuid, number_str);
391 s = number_str;
392 }
393 break;
394 case NDFT_CALLBACK:
395 if (!*tmp)
396 *tmp = buffer_create(1024, NULL);
397 else
398 buffer_flush(*tmp);
399
400 if (fields[i].entry.cb.formatter(*tmp, fields[i].entry.cb.formatter_data))
401 s = buffer_tostring(*tmp);
402 else
403 s = NULL;
404 break;
405
406 default:
407 s = "UNHANDLED";
408 break;
409 }
410
411 if(!s || !*s) return "";
412 return s;
413 }
414 static void etw_replace_percent_with_unicode(wchar_t *s, size_t size) {
415 size_t original_len = wcslen(s);
416
417 // Traverse the string, replacing '%' with the Unicode fullwidth percent sign
418 for (size_t i = 0; i < original_len && i < size - 1; i++) {
419 if (s[i] == L'%' && iswdigit(s[i + 1])) {
420 // s[i] = 0xFF05; // Replace '%' with fullwidth percent sign '%'
421 // s[i] = 0x29BC; // ⦼
422 s[i] = 0x2105; // ℅
423 }
424 }
425
426 // Ensure null termination if needed
427 s[size - 1] = L'\0';
428 }
429
430 static void wevt_generate_all_fields_unsafe(struct log_field *fields, size_t fields_max, BUFFER **tmp) {
431 for (size_t i = 0; i < fields_max; i++) {
432 fields_buffers[i].buf[0] = L'\0';
433
434 if (!fields[i].entry.set || !fields[i].eventlog)
435 continue;
436
437 const char *s = get_field_value_unsafe(fields, i, fields_max, tmp);
438 if (s && *s) {
439 utf8_to_utf16(fields_buffers[i].buf, (int) fields_buffers[i].size, s, -1);
440
441 if(nd_log.eventlog.etw)
442 // UNBELIEVABLE! they do recursive parameter expansion in ETW...
443 etw_replace_percent_with_unicode(fields_buffers[i].buf, fields_buffers[i].size);
444 }
445 }
446 }
447
448 static bool has_user_role_permissions(struct log_field *fields, size_t fields_max, BUFFER **tmp) {
449 const char *t;
450
451 t = get_field_value_unsafe(fields, NDF_USER_NAME, fields_max, tmp);
452 if (*t) return true;
453
454 t = get_field_value_unsafe(fields, NDF_USER_ROLE, fields_max, tmp);
455 if (*t && strcmp(t, "none") != 0) return true;
456
457 t = get_field_value_unsafe(fields, NDF_USER_ACCESS, fields_max, tmp);
458 if (*t && strcmp(t, "0x0") != 0) return true;
459
460 return false;
461 }
462
463 static bool nd_logger_windows(struct nd_log_source *source, struct log_field *fields, size_t fields_max) {
464 if (!nd_log.eventlog.initialized)
465 return false;
466
467 ND_LOG_FIELD_PRIORITY priority = NDLP_INFO;
468 if (fields[NDF_PRIORITY].entry.set)
469 priority = (ND_LOG_FIELD_PRIORITY) fields[NDF_PRIORITY].entry.u64;
470
471 DWORD wType = get_event_type_from_priority(priority);
472 (void) wType;
473
474 CLEAN_BUFFER *tmp = NULL;
475
476 static SPINLOCK spinlock = SPINLOCK_INITIALIZER;
477 spinlock_lock(&spinlock);
478 wevt_generate_all_fields_unsafe(fields, fields_max, &tmp);
479
480 MESSAGE_ID messageID;
481 switch (source->source) {
482 default:
483 case NDLS_DEBUG:
484 case NDLS_DAEMON:
485 case NDLS_COLLECTORS:
486 messageID = MSGID_MESSAGE_ONLY;
487 break;
488
489 case NDLS_HEALTH:
490 messageID = MSGID_ALERT_TRANSITION;
491 break;
492
493 case NDLS_ACCESS:
494 if (is_field_set(fields, fields_max, NDF_MESSAGE)) {
495 messageID = MSGID_ACCESS_MESSAGE;
496
497 if (has_user_role_permissions(fields, fields_max, &tmp))
498 messageID = MSGID_ACCESS_MESSAGE_USER;
499 else if (*get_field_value_unsafe(fields, NDF_REQUEST, fields_max, &tmp))
500 messageID = MSGID_ACCESS_MESSAGE_REQUEST;
501 } else if (is_field_set(fields, fields_max, NDF_RESPONSE_CODE)) {
502 messageID = MSGID_ACCESS;
503
504 if (*get_field_value_unsafe(fields, NDF_SRC_FORWARDED_FOR, fields_max, &tmp))
505 messageID = MSGID_ACCESS_FORWARDER;
506
507 if (has_user_role_permissions(fields, fields_max, &tmp)) {
508 if (messageID == MSGID_ACCESS)
509 messageID = MSGID_ACCESS_USER;
510 else
511 messageID = MSGID_ACCESS_FORWARDER_USER;
512 }
513 } else
514 messageID = MSGID_REQUEST_ONLY;
515 break;
516
517 case NDLS_ACLK:
518 messageID = MSGID_MESSAGE_ONLY;
519 break;
520 }
521
522 if (messageID == MSGID_MESSAGE_ONLY && (
523 *get_field_value_unsafe(fields, NDF_ERRNO, fields_max, &tmp) ||
524 *get_field_value_unsafe(fields, NDF_WINERROR, fields_max, &tmp))) {
525 messageID = MSGID_MESSAGE_ERRNO;
526 }
527
528 DWORD eventID = construct_event_id(source->source, priority, messageID);
529
530 // wType
531 //
532 // without a manifest => this determines the Level of the event
533 // with a manifest => Level from the manifest is used (wType ignored)
534 // [however it is good to have, in case the manifest is not accessible somehow]
535 //
536
537 // wCategory
538 //
539 // without a manifest => numeric Task values appear
540 // with a manifest => Task from the manifest is used (wCategory ignored)
541
542 BOOL rc;
543 #if defined(HAVE_ETW)
544 if (nd_log.eventlog.etw) {
545 // metadata based logging - ETW
546
547 for (size_t i = 1; i < _NDF_MAX; i++)
548 EventDataDescCreate(&etw_eventData[i - 1], fields_buffers[i].buf,
549 (wcslen(fields_buffers[i].buf) + 1) * sizeof(WCHAR));
550
551 EVENT_DESCRIPTOR EventDesc = {
552 .Id = eventID & EVENT_ID_CODE_MASK, // ETW needs the raw event id
553 .Version = 0,
554 .Channel = source->channelID,
555 .Level = get_level_from_priority(priority),
556 .Opcode = source->Opcode,
557 .Task = source->Task,
558 .Keyword = source->Keyword,
559 };
560
561 rc = ERROR_SUCCESS == EventWrite(regHandle, &EventDesc, _NDF_MAX - 1, etw_eventData);
562
563 }
564 else
565 #endif
566 {
567 // eventID based logging - WEL
568 rc = ReportEventW(source->hEventLog, wType, 0, eventID, NULL, _NDF_MAX - 1, 0, wel_messages, NULL);
569 }
570
571 spinlock_unlock(&spinlock);
572
573 return rc == TRUE;
574 }
575
576 #if defined(HAVE_ETW)
577 bool nd_logger_etw(struct nd_log_source *source, struct log_field *fields, size_t fields_max) {
578 return nd_logger_windows(source, fields, fields_max);
579 }
580 #endif
581
582 #if defined(HAVE_WEL)
583 bool nd_logger_wel(struct nd_log_source *source, struct log_field *fields, size_t fields_max) {
584 return nd_logger_windows(source, fields, fields_max);
585 }
586 #endif
587
588 #endif