summaryrefslogtreecommitdiffstats
path: root/src/collectors/windows-events.plugin
diff options
context:
space:
mode:
Diffstat (limited to 'src/collectors/windows-events.plugin')
-rw-r--r--src/collectors/windows-events.plugin/README.md289
-rw-r--r--src/collectors/windows-events.plugin/windows-events-fields-cache.c158
-rw-r--r--src/collectors/windows-events.plugin/windows-events-fields-cache.h22
-rw-r--r--src/collectors/windows-events.plugin/windows-events-providers.c678
-rw-r--r--src/collectors/windows-events.plugin/windows-events-providers.h41
-rw-r--r--src/collectors/windows-events.plugin/windows-events-query-builder.c107
-rw-r--r--src/collectors/windows-events.plugin/windows-events-query-builder.h10
-rw-r--r--src/collectors/windows-events.plugin/windows-events-query-evt-variant.c354
-rw-r--r--src/collectors/windows-events.plugin/windows-events-query.c717
-rw-r--r--src/collectors/windows-events.plugin/windows-events-query.h296
-rw-r--r--src/collectors/windows-events.plugin/windows-events-sources.c644
-rw-r--r--src/collectors/windows-events.plugin/windows-events-sources.h78
-rw-r--r--src/collectors/windows-events.plugin/windows-events-unicode.c46
-rw-r--r--src/collectors/windows-events.plugin/windows-events-unicode.h42
-rw-r--r--src/collectors/windows-events.plugin/windows-events-xml.c344
-rw-r--r--src/collectors/windows-events.plugin/windows-events-xml.h12
-rw-r--r--src/collectors/windows-events.plugin/windows-events.c1402
-rw-r--r--src/collectors/windows-events.plugin/windows-events.h262
18 files changed, 5502 insertions, 0 deletions
diff --git a/src/collectors/windows-events.plugin/README.md b/src/collectors/windows-events.plugin/README.md
new file mode 100644
index 000000000..ecaa4349a
--- /dev/null
+++ b/src/collectors/windows-events.plugin/README.md
@@ -0,0 +1,289 @@
+# Windows Events plugin
+
+[KEY FEATURES](#key-features) | [EVENTS SOURCES](#events-sources) | [EVENT FIELDS](#event-fields) |
+[PLAY MODE](#play-mode) | [FULL TEXT SEARCH](#full-text-search) | [PERFORMANCE](#query-performance) |
+[CONFIGURATION](#configuration-and-maintenance) | [FAQ](#faq)
+
+The Windows Events plugin by Netdata makes viewing, exploring and analyzing Windows Events simple and
+efficient.
+
+![image](https://github.com/user-attachments/assets/71a1ab1d-5b7b-477e-a4e6-a30275a5710b)
+
+## Key features
+
+- Supports **Windows Event Logs (WEL)**.
+- Supports **Event Tracing for Windows (ETW)** and **TraceLogging (TL)**, when events are routed to Event Log.
+- Allows filtering on all System Events fields.
+- Allows **full text search** (`grep`) on all System and User fields.
+- Provides a **histogram** for log entries over time, with a break down per field-value, for any System Event field and any
+ time-frame.
+- Supports coloring log entries based on severity.
+- In PLAY mode it "tails" all the Events, showing new log entries immediately after they are received.
+
+### Prerequisites
+
+`windows-events.plugin` is a Netdata Function Plugin.
+
+To protect your privacy, as with all Netdata Functions, a free Netdata Cloud user account is required to access it.
+For more information check [this discussion](https://github.com/netdata/netdata/discussions/16136).
+
+## Events Sources
+
+The plugin automatically detects all the available channels and offers a list of "Event Channels".
+
+By default, it aggregates events from all event channels, providing a unified view of all events.
+
+> To improve query performance, we recommend selecting the relevant event channels, before doing more
+> analysis on the events.
+
+In the list of events channels, several shortcuts are added, aggregating events according to various attributes:
+
+- `All`, aggregates events from all available channels. This provides a holistic view of all events in the system.
+- `All-Admin`, `All-Operational`, `All-Analytic` and `All-Debug` aggregates events from channels marked `Admin`, `Operational`, `Analytic` and `Debug`, respectively.
+- `All-Windows`, aggregates events from `Application`, `Security`, `System` and `Setup`.
+- `All-Enabled` and `All-Disabled` aggregates events from channels depending on their status.
+- `All-Forwarded` aggregates events from channels owned by `Microsoft-Windows-EventCollector`.
+- `All-Classic` aggregates events from channels using the Classic Event Log API.
+- `All-Of-X`, where `X` is a provider name, is offered for all providers having more than a channel.
+- `All-In-X`, where `X` is `Backup-Mode`, `Overwrite-Mode`, `StopWhenFull-Mode` and `RetainAndBackup-Mode`, aggregate events based on their channel retention policy.
+
+Channels that are configured but are not queryable, and channels that do not have any events in them, are automatically excluded from the channels list.
+
+## Event Fields
+
+Windows Events are structured with both system-defined fields and user-defined fields.
+The Windows Events plugin primarily works with the system-defined fields, which are consistently available
+across all event types.
+
+### System-defined fields
+
+The system-defined fields are:
+
+1. **EventRecordID**
+ A unique, sequential identifier for the event within the channel. This ID increases as new events are logged.
+
+2. **Version**
+ The version of the event, indicating possible structural changes or updates to the event definition.
+
+ Netdata adds this field automatically when it is not zero.
+
+3. **Level**
+ The severity or importance of the event. Levels can include:
+ - 0: LogAlways (reserved)
+ - 1: Critical
+ - 2: Error
+ - 3: Warning
+ - 4: Informational
+ - 5: Verbose
+
+ Additionally, applications may define their own levels.
+
+ Netdata provides 2 fields: `Level` and `LevelID` for the text and numeric representation of it.
+
+4. **Opcode**
+ The action or state within a provider when the event was logged.
+
+ Netdata provides 2 fields: `Opcode` and `OpcodeID` for the text and numeric representation of it.
+
+5. **EventID**
+ This identifies the event template, linking it to a specific message or event type. Event IDs are provider-specific.
+
+6. **Task**
+ Defines a higher-level categorization or logical grouping for the event, often related to a specific function within the application or provider.
+
+ Netdata provides 2 fields: `Task` and `TaskID` for the text and numeric representation of it.
+
+7. **Qualifiers**
+ Provides additional detail for interpreting the event and is often specific to the event source.
+
+ Netdata adds this field automatically when it is not zero.
+
+8. **ProcessID**
+ The ID of the process that generated the event, useful for pinpointing the source of the event within the system.
+
+9. **ThreadID**
+ The ID of the thread within the process that generated the event, which helps in more detailed debugging scenarios.
+
+10. **Keywords**
+ A categorization field that can be used for event filtering. Keywords are bit flags that represent categories or purposes of the event, providing additional context.
+
+ Netdata provides 2 fields: `Keywords` and `keywordsID` for the text and numeric representation of it.
+
+11. **Provider**
+ The unique identifier (GUID) of the event provider. This is essential for knowing which application or system component generated the event.
+
+ Netdata provides 2 fields: `Provider` and `ProviderGUID` for its name and GUID of it.
+
+12. **ActivityID**
+ A GUID that correlates events generated as part of the same operation or transaction, helping to track activities across different components or stages.
+
+ Netdata adds this field automatically when it is not zero.
+
+13. **RelatedActivityID**
+ A GUID that links related operations or transactions, allowing for tracing complex workflows where one event triggers or relates to another.
+
+ Netdata adds this field automatically when it is not zero.
+
+14. **Timestamp**
+ The timestamp when the event was created. This provides precise timing information about when the event occurred.
+
+15. **User**
+ The system user who logged this event.
+
+ Netdata provides 3 fields: `UserAccount`, `UserDomain` and `UserSID`.
+
+### User-defined fields
+Each event log entry can include up to 100 user-defined fields (per event-id).
+
+Unfortunately, accessing these fields is significantly slower, to a level that is not practical to do so
+when there are more than few thousand log entries to explore. So, Netdata presents them
+with lazy loading.
+
+This prevents Netdata for offering filtering for user-defined fields, although Netdata does support
+full text search on user-defined field values.
+
+### Event fields as columns in the table
+
+The system fields mentioned above are offered as columns on the UI. Use the gear button above the table to
+select visible columns.
+
+### Event fields as filters
+
+The plugin presents the system fields as filters for the query, with counters for each of the possible values
+for the field. This list can be used to quickly check which fields and values are available for the entire
+time-frame of the query, across multiple providers and channels.
+
+### Event fields as histogram sources
+
+The histogram can be based on any of the system fields that are available as filters. This allows you to
+visualize the distribution of events over time based on different criteria such as Level, Provider, or EventID.
+
+## PLAY mode
+
+The PLAY mode in this plugin allows real-time monitoring of new events as they are added to the Windows Event
+Log. This feature works by continuously querying for new events and updating the display.
+
+## Full-text search
+
+The plugin supports searching for text within all system and user fields of the events. This means that while
+user-defined fields are not directly filterable, they are searchable through the full-text search feature.
+
+Keep in mind that query performance is slower while doing full text search, mainly because the plugin
+needs to ask from the system to provide all the user fields values.
+
+## Query performance
+
+The plugin is optimized to work efficiently with Event Logs. It uses several layers of caching and
+similar techniques to offload as much work as possible from the system, offering quick responses even when
+hundreds of thousands of events are within the visible timeframe.
+
+To achieve this level of efficiency, the plugin:
+
+- pre-loads ETW providers' manifests for resolving numeric Levels, Opcodes, Tasks and Keywords to text.
+- caches number to text maps for Levels, Opcodes, Tasks and Keywords per provider for WEL providers.
+- caches user SID to account and domain maps.
+- lazy loads the "expensive" event Message and XML, so that the system is queried only for the visible events.
+
+For Full Text Search:
+
+- requests only the Message and the values of the user-fields from the system, avoiding the "expensive" XML call (which is still lazy-loaded).
+
+The result is a system that is highly efficient for working with moderate volumes (hundreds of thousands) of events.
+
+## Configuration and maintenance
+
+This Netdata plugin does not require any specific configuration. It automatically detects available event logs
+on the system.
+
+## FAQ
+
+### Can I use this plugin on event centralization servers?
+
+Yes. You can centralize your Windows Events using Windows Event Forwarding (WEF) or other event collection
+mechanisms, and then install Netdata on this events centralization server to explore the events of all your
+infrastructure.
+
+This plugin will automatically provide multi-node views of your events and also give you the ability to
+combine the events of multiple servers, as you see fit.
+
+### Can I use this plugin from a parent Netdata?
+
+Yes. When your nodes are connected to a Netdata parent, all their functions are available via the parent's UI.
+So, from the parent UI, you can access the functions of all your nodes.
+
+Keep in mind that to protect your privacy, in order to access Netdata functions, you need a free Netdata Cloud
+account.
+
+### Is any of my data exposed to Netdata Cloud from this plugin?
+
+No. When you access the agent directly, none of your data passes through Netdata Cloud. You need a free Netdata
+Cloud account only to verify your identity and enable the use of Netdata Functions. Once this is done, all the
+data flow directly from your Netdata agent to your web browser.
+
+When you access Netdata via https://app.netdata.cloud, your data travel via Netdata Cloud, but they are not stored
+in Netdata Cloud. This is to allow you access your Netdata agents from anywhere. All communication from/to
+Netdata Cloud is encrypted.
+
+### What are the different types of event logs supported by this plugin?
+
+The plugin supports all the kinds of event logs currently supported by the Windows Event Viewer:
+
+- Windows Event Logs (WEL): The traditional event logging system in Windows.
+- Event Tracing for Windows (ETW): A more detailed and efficient event tracing system.
+- TraceLogging (TL): An extension of ETW that simplifies the process of adding events to your code.
+
+The plugin can access all of these when they are routed to the Windows Event Log.
+
+### How does this plugin handle user-defined fields in Windows Events?
+
+User-defined fields are not directly exposed as table columns or filters in the plugin interface. However,
+they are included in the XML representation of each event, which can be viewed in the info sidebar when
+clicking on an event entry. Additionally, the full-text search feature does search through these
+user-defined fields, allowing you to find specific information even if it's not in the main system fields.
+
+### Can I use this plugin to monitor real-time events?
+
+Yes, the plugin supports a PLAY mode that allows you to monitor events in real-time. When activated, it
+continuously updates to show new events as they are logged, similar to the "tail" functionality in
+Unix-like systems.
+
+### How does the plugin handle large volumes of events?
+
+The plugin is designed to handle moderate volumes of events (hundreds of thousands of events) efficiently.
+
+It is in our roadmap to port the `systemd-journal` sampling techniques to it, for working with very large
+datasets to provide quick responses while still giving accurate representations of the data. However, for
+the best performance, we recommend querying smaller time frames or using more specific filters when dealing
+with extremely large event volumes.
+
+### Can I use this plugin to analyze events from multiple servers?
+
+Yes, if you have set up Windows Event Forwarding (WEF) or another method of centralizing your Windows Events,
+you can use this plugin on the central server to analyze events from multiple sources. The plugin will
+automatically detect the available event sources.
+
+### How does the histogram feature work in this plugin?
+
+The histogram feature provides a visual representation of event frequency over time. You can base the
+histogram on any of the system fields available as filters (such as Level, Provider, or EventID). This
+allows you to quickly identify patterns or anomalies in your event logs.
+
+### Is it possible to export or share the results from this plugin?
+
+While the plugin doesn't have a direct export feature, you can use browser-based methods to save or share
+the results. This could include taking screenshots, using browser print/save as PDF functionality, or
+copying data from the table view. For more advanced data export needs, you might need to use the Windows
+Event Log API directly or other Windows administrative tools.
+
+### How often does the plugin update its data?
+
+The plugin updates its data in real-time when in PLAY mode. In normal mode, it refreshes data based on the
+query you've submitted. The plugin is designed to provide the most up-to-date information available in the
+Windows Event Logs at the time of the query.
+
+## TODO
+
+1. Support Sampling, so that the plugin can respond faster even on very busy systems (millions of events visible).
+2. Support exploring events from live Tracing sessions.
+3. Support exploring events in saved Event Trace Log files (`.etl` files).
+4. Support exploring events in saved Event Logs files (`.evtx` files).
diff --git a/src/collectors/windows-events.plugin/windows-events-fields-cache.c b/src/collectors/windows-events.plugin/windows-events-fields-cache.c
new file mode 100644
index 000000000..4b4b72fa4
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-fields-cache.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "windows-events-fields-cache.h"
+
+typedef struct field_key {
+ uint64_t value;
+ ND_UUID provider;
+} WEVT_FIELD_KEY;
+
+typedef struct field_value {
+ WEVT_FIELD_KEY key;
+ uint32_t name_size;
+ char name[];
+} WEVT_FIELD_VALUE;
+
+#define SIMPLE_HASHTABLE_NAME _FIELDS_CACHE
+#define SIMPLE_HASHTABLE_VALUE_TYPE WEVT_FIELD_VALUE
+#define SIMPLE_HASHTABLE_KEY_TYPE WEVT_FIELD_KEY
+#define SIMPLE_HASHTABLE_VALUE2KEY_FUNCTION field_cache_value_to_key
+#define SIMPLE_HASHTABLE_COMPARE_KEYS_FUNCTION field_cache_cache_compar
+#define SIMPLE_HASHTABLE_SAMPLE_IMPLEMENTATION 1
+#include "libnetdata/simple_hashtable/simple_hashtable.h"
+
+static inline WEVT_FIELD_KEY *field_cache_value_to_key(WEVT_FIELD_VALUE *p) {
+ return &p->key;
+}
+
+static inline bool field_cache_cache_compar(WEVT_FIELD_KEY *a, WEVT_FIELD_KEY *b) {
+ return memcmp(a, b, sizeof(WEVT_FIELD_KEY)) == 0;
+}
+
+struct ht {
+ SPINLOCK spinlock;
+ size_t allocations;
+ size_t bytes;
+ struct simple_hashtable_FIELDS_CACHE ht;
+};
+
+static struct {
+ bool initialized;
+ struct ht ht[WEVT_FIELD_TYPE_MAX];
+} fdc = {
+ .initialized = false,
+};
+
+void field_cache_init(void) {
+ for(size_t type = 0; type < WEVT_FIELD_TYPE_MAX ; type++) {
+ spinlock_init(&fdc.ht[type].spinlock);
+ simple_hashtable_init_FIELDS_CACHE(&fdc.ht[type].ht, 10000);
+ }
+}
+
+static inline bool should_zero_provider(WEVT_FIELD_TYPE type, uint64_t value) {
+ switch(type) {
+ case WEVT_FIELD_TYPE_LEVEL:
+ return !is_valid_provider_level(value, true);
+
+ case WEVT_FIELD_TYPE_KEYWORD:
+ return !is_valid_provider_keyword(value, true);
+
+ case WEVT_FIELD_TYPE_OPCODE:
+ return !is_valid_provider_opcode(value, true);
+
+ case WEVT_FIELD_TYPE_TASK:
+ return !is_valid_provider_task(value, true);
+
+ default:
+ return false;
+ }
+}
+
+bool field_cache_get(WEVT_FIELD_TYPE type, const ND_UUID *uuid, uint64_t value, TXT_UTF8 *dst) {
+ fatal_assert(type < WEVT_FIELD_TYPE_MAX);
+
+ struct ht *ht = &fdc.ht[type];
+
+ WEVT_FIELD_KEY t = {
+ .value = value,
+ .provider = should_zero_provider(type, value) ? UUID_ZERO : *uuid,
+ };
+ XXH64_hash_t hash = XXH3_64bits(&t, sizeof(t));
+
+ spinlock_lock(&ht->spinlock);
+ SIMPLE_HASHTABLE_SLOT_FIELDS_CACHE *slot = simple_hashtable_get_slot_FIELDS_CACHE(&ht->ht, hash, &t, true);
+ WEVT_FIELD_VALUE *v = SIMPLE_HASHTABLE_SLOT_DATA(slot);
+ spinlock_unlock(&ht->spinlock);
+
+ if(v) {
+ txt_utf8_resize(dst, v->name_size, false);
+ memcpy(dst->data, v->name, v->name_size);
+ dst->used = v->name_size;
+ dst->src = TXT_SOURCE_FIELD_CACHE;
+ return true;
+ }
+
+ return false;
+}
+
+static WEVT_FIELD_VALUE *wevt_create_cache_entry(WEVT_FIELD_KEY *t, TXT_UTF8 *name, size_t *bytes) {
+ *bytes = sizeof(WEVT_FIELD_VALUE) + name->used;
+ WEVT_FIELD_VALUE *v = callocz(1, *bytes);
+ v->key = *t;
+ memcpy(v->name, name->data, name->used);
+ v->name_size = name->used;
+ return v;
+}
+
+//static bool is_numeric(const char *s) {
+// while(*s) {
+// if(!isdigit((uint8_t)*s++))
+// return false;
+// }
+//
+// return true;
+//}
+
+void field_cache_set(WEVT_FIELD_TYPE type, const ND_UUID *uuid, uint64_t value, TXT_UTF8 *name) {
+ fatal_assert(type < WEVT_FIELD_TYPE_MAX);
+
+ struct ht *ht = &fdc.ht[type];
+
+ WEVT_FIELD_KEY t = {
+ .value = value,
+ .provider = should_zero_provider(type, value) ? UUID_ZERO : *uuid,
+ };
+ XXH64_hash_t hash = XXH3_64bits(&t, sizeof(t));
+
+ spinlock_lock(&ht->spinlock);
+ SIMPLE_HASHTABLE_SLOT_FIELDS_CACHE *slot = simple_hashtable_get_slot_FIELDS_CACHE(&ht->ht, hash, &t, true);
+ WEVT_FIELD_VALUE *v = SIMPLE_HASHTABLE_SLOT_DATA(slot);
+ if(!v) {
+ size_t bytes;
+ v = wevt_create_cache_entry(&t, name, &bytes);
+ simple_hashtable_set_slot_FIELDS_CACHE(&ht->ht, slot, hash, v);
+
+ ht->allocations++;
+ ht->bytes += bytes;
+ }
+// else {
+// if((v->name_size == 1 && name->used > 0) || is_numeric(v->name)) {
+// size_t bytes;
+// WEVT_FIELD_VALUE *nv = wevt_create_cache_entry(&t, name, &bytes);
+// simple_hashtable_set_slot_FIELDS_CACHE(&ht->ht, slot, hash, nv);
+// ht->bytes += name->used;
+// ht->bytes -= v->name_size;
+// freez(v);
+// }
+// else if(name->used > 2 && !is_numeric(name->data) && (v->name_size != name->used || strcasecmp(v->name, name->data) != 0)) {
+// const char *a = v->name;
+// const char *b = name->data;
+// int x = 0;
+// x++;
+// }
+// }
+
+ spinlock_unlock(&ht->spinlock);
+}
+
diff --git a/src/collectors/windows-events.plugin/windows-events-fields-cache.h b/src/collectors/windows-events.plugin/windows-events-fields-cache.h
new file mode 100644
index 000000000..a76170d68
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-fields-cache.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_WINDOWS_EVENTS_FIELDS_CACHE_H
+#define NETDATA_WINDOWS_EVENTS_FIELDS_CACHE_H
+
+#include "windows-events.h"
+
+typedef enum __attribute__((packed)) {
+ WEVT_FIELD_TYPE_LEVEL = 0,
+ WEVT_FIELD_TYPE_OPCODE,
+ WEVT_FIELD_TYPE_KEYWORD,
+ WEVT_FIELD_TYPE_TASK,
+
+ // terminator
+ WEVT_FIELD_TYPE_MAX,
+} WEVT_FIELD_TYPE;
+
+void field_cache_init(void);
+bool field_cache_get(WEVT_FIELD_TYPE type, const ND_UUID *uuid, uint64_t value, TXT_UTF8 *dst);
+void field_cache_set(WEVT_FIELD_TYPE type, const ND_UUID *uuid, uint64_t value, TXT_UTF8 *name);
+
+#endif //NETDATA_WINDOWS_EVENTS_FIELDS_CACHE_H
diff --git a/src/collectors/windows-events.plugin/windows-events-providers.c b/src/collectors/windows-events.plugin/windows-events-providers.c
new file mode 100644
index 000000000..d4c4d35ea
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-providers.c
@@ -0,0 +1,678 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "windows-events-providers.h"
+
+#define MAX_OPEN_HANDLES_PER_PROVIDER 5
+
+struct provider;
+
+// typedef as PROVIDER_META_HANDLE in include file
+struct provider_meta_handle {
+ pid_t owner; // the owner of the handle, or zero
+ uint32_t locks; // the number of locks the owner has on this handle
+ EVT_HANDLE hMetadata; // the handle
+ struct provider *provider; // a pointer back to the provider
+
+ usec_t created_monotonic_ut; // the monotonic timestamp this handle was created
+
+ // double linked list
+ PROVIDER_META_HANDLE *prev;
+ PROVIDER_META_HANDLE *next;
+};
+
+struct provider_data {
+ uint64_t value; // the mask of the keyword
+ XXH64_hash_t hash; // the hash of the name
+ uint32_t len; // the length of the name
+ char *name; // the name of the keyword in UTF-8
+};
+
+struct provider_list {
+ uint64_t min, max, mask;
+ bool exceeds_data_type; // true when the manifest values exceed the capacity of the EvtXXX() API
+ uint32_t total; // the number of entries in the array
+ struct provider_data *array; // the array of entries, sorted (for binary search)
+};
+
+typedef struct provider_key {
+ ND_UUID uuid; // the Provider GUID
+ DWORD len; // the length of the Provider Name
+ const wchar_t *wname; // the Provider wide-string Name (UTF-16)
+} PROVIDER_KEY;
+
+typedef struct provider {
+ PROVIDER_KEY key;
+ const char *name; // the Provider Name (UTF-8)
+ uint32_t total_handles; // the number of handles allocated
+ uint32_t available_handles; // the number of available handles
+ uint32_t deleted_handles; // the number of deleted handles
+ PROVIDER_META_HANDLE *handles; // a double linked list of all the handles
+
+ WEVT_PROVIDER_PLATFORM platform;
+
+ struct provider_list keyword;
+ struct provider_list tasks;
+ struct provider_list opcodes;
+ struct provider_list levels;
+} PROVIDER;
+
+// A hashtable implementation for Providers
+// using the Provider GUID as key and PROVIDER as value
+#define SIMPLE_HASHTABLE_NAME _PROVIDER
+#define SIMPLE_HASHTABLE_VALUE_TYPE PROVIDER
+#define SIMPLE_HASHTABLE_KEY_TYPE PROVIDER_KEY
+#define SIMPLE_HASHTABLE_VALUE2KEY_FUNCTION provider_value_to_key
+#define SIMPLE_HASHTABLE_COMPARE_KEYS_FUNCTION provider_cache_compar
+#define SIMPLE_HASHTABLE_SAMPLE_IMPLEMENTATION 1
+#include "libnetdata/simple_hashtable/simple_hashtable.h"
+
+static struct {
+ SPINLOCK spinlock;
+ uint32_t total_providers;
+ uint32_t total_handles;
+ uint32_t deleted_handles;
+ struct simple_hashtable_PROVIDER hashtable;
+ ARAL *aral_providers;
+ ARAL *aral_handles;
+} pbc = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+};
+
+static void provider_load_list(PROVIDER_META_HANDLE *h, WEVT_VARIANT *content, WEVT_VARIANT *property,
+ TXT_UTF16 *dst, struct provider_list *l, EVT_PUBLISHER_METADATA_PROPERTY_ID property_id);
+
+const char *provider_get_name(PROVIDER_META_HANDLE *p) {
+ return (p && p->provider && p->provider->name) ? p->provider->name : "__UNKNOWN PROVIDER__";
+}
+
+ND_UUID provider_get_uuid(PROVIDER_META_HANDLE *p) {
+ return (p && p->provider) ? p->provider->key.uuid : UUID_ZERO;
+}
+
+static inline PROVIDER_KEY *provider_value_to_key(PROVIDER *p) {
+ return &p->key;
+}
+
+static inline bool provider_cache_compar(PROVIDER_KEY *a, PROVIDER_KEY *b) {
+ return a->len == b->len && UUIDeq(a->uuid, b->uuid) && memcmp(a->wname, b->wname, a->len) == 0;
+}
+
+void provider_cache_init(void) {
+ simple_hashtable_init_PROVIDER(&pbc.hashtable, 100000);
+ pbc.aral_providers = aral_create("wevt_providers", sizeof(PROVIDER), 0, 4096, NULL, NULL, NULL, false, true);
+ pbc.aral_handles = aral_create("wevt_handles", sizeof(PROVIDER_META_HANDLE), 0, 4096, NULL, NULL, NULL, false, true);
+}
+
+static bool provider_property_get(PROVIDER_META_HANDLE *h, WEVT_VARIANT *content, EVT_PUBLISHER_METADATA_PROPERTY_ID property_id) {
+ DWORD bufferUsed = 0;
+
+ if(!EvtGetPublisherMetadataProperty(h->hMetadata, property_id, 0, 0, NULL, &bufferUsed)) {
+ DWORD status = GetLastError();
+ if (status != ERROR_INSUFFICIENT_BUFFER) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtGetPublisherMetadataProperty() failed");
+ goto cleanup;
+ }
+ }
+
+ wevt_variant_resize(content, bufferUsed);
+ if (!EvtGetPublisherMetadataProperty(h->hMetadata, property_id, 0, content->size, content->data, &bufferUsed)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtGetPublisherMetadataProperty() failed after resize");
+ goto cleanup;
+ }
+
+ return true;
+
+cleanup:
+ return false;
+}
+
+static bool provider_string_property_exists(PROVIDER_META_HANDLE *h, WEVT_VARIANT *content, EVT_PUBLISHER_METADATA_PROPERTY_ID property_id) {
+ if(!provider_property_get(h, content, property_id))
+ return false;
+
+ if(content->data->Type != EvtVarTypeString)
+ return false;
+
+ if(!content->data->StringVal[0])
+ return false;
+
+ return true;
+}
+
+static void provider_detect_platform(PROVIDER_META_HANDLE *h, WEVT_VARIANT *content) {
+ if(UUIDiszero(h->provider->key.uuid))
+ h->provider->platform = WEVT_PLATFORM_WEL;
+ else if(h->hMetadata) {
+ if (provider_string_property_exists(h, content, EvtPublisherMetadataMessageFilePath) ||
+ provider_string_property_exists(h, content, EvtPublisherMetadataResourceFilePath) ||
+ provider_string_property_exists(h, content, EvtPublisherMetadataParameterFilePath))
+ h->provider->platform = WEVT_PLATFORM_ETW;
+ else
+ // The provider cannot be opened, does not have any resource files (message, resource, parameter)
+ h->provider->platform = WEVT_PLATFORM_TL;
+ }
+ else h->provider->platform = WEVT_PLATFORM_ETW;
+}
+
+WEVT_PROVIDER_PLATFORM provider_get_platform(PROVIDER_META_HANDLE *p) {
+ return p->provider->platform;
+}
+
+PROVIDER_META_HANDLE *provider_get(ND_UUID uuid, LPCWSTR providerName) {
+ if(!providerName || !providerName[0])
+ return NULL;
+
+ PROVIDER_KEY key = {
+ .uuid = uuid,
+ .len = wcslen(providerName),
+ .wname = providerName,
+ };
+ XXH64_hash_t hash = XXH3_64bits(providerName, wcslen(key.wname) * sizeof(*key.wname));
+
+ spinlock_lock(&pbc.spinlock);
+
+ SIMPLE_HASHTABLE_SLOT_PROVIDER *slot =
+ simple_hashtable_get_slot_PROVIDER(&pbc.hashtable, hash, &key, true);
+
+ bool load_it = false;
+ PROVIDER *p = SIMPLE_HASHTABLE_SLOT_DATA(slot);
+ if(!p) {
+ p = aral_callocz(pbc.aral_providers);
+ p->key.uuid = key.uuid;
+ p->key.len = key.len;
+ p->key.wname = wcsdup(key.wname);
+ p->name = strdupz(provider2utf8(key.wname));
+ simple_hashtable_set_slot_PROVIDER(&pbc.hashtable, slot, hash, p);
+ load_it = true;
+ pbc.total_providers++;
+ }
+
+ pid_t me = gettid_cached();
+ PROVIDER_META_HANDLE *h;
+ for(h = p->handles; h ;h = h->next) {
+ // find the first that is mine,
+ // or the first not owned by anyone
+ if(!h->owner || h->owner == me)
+ break;
+ }
+
+ if(!h) {
+ h = aral_callocz(pbc.aral_handles);
+ h->provider = p;
+ h->created_monotonic_ut = now_monotonic_usec();
+ h->hMetadata = EvtOpenPublisherMetadata(
+ NULL, // Local machine
+ providerName, // Provider name
+ NULL, // Log file path (NULL for default)
+ 0, // Locale (0 for default locale)
+ 0 // Flags
+ );
+
+ // we put it at the beginning of the list
+ // to find it first if the same owner needs more locks on it
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(p->handles, h, prev, next);
+ pbc.total_handles++;
+ p->total_handles++;
+ p->available_handles++;
+ }
+
+ if(!h->owner) {
+ fatal_assert(p->available_handles > 0);
+ p->available_handles--;
+ h->owner = me;
+ }
+
+ h->locks++;
+
+ if(load_it) {
+ WEVT_VARIANT content = { 0 };
+ WEVT_VARIANT property = { 0 };
+ TXT_UTF16 unicode = { 0 };
+
+ provider_detect_platform(h, &content);
+ provider_load_list(h, &content, &property, &unicode, &p->keyword, EvtPublisherMetadataKeywords);
+ provider_load_list(h, &content, &property, &unicode, &p->levels, EvtPublisherMetadataLevels);
+ provider_load_list(h, &content, &property, &unicode, &p->opcodes, EvtPublisherMetadataOpcodes);
+ provider_load_list(h, &content, &property, &unicode, &p->tasks, EvtPublisherMetadataTasks);
+
+ txt_utf16_cleanup(&unicode);
+ wevt_variant_cleanup(&content);
+ wevt_variant_cleanup(&property);
+ }
+
+ spinlock_unlock(&pbc.spinlock);
+
+ return h;
+}
+
+EVT_HANDLE provider_handle(PROVIDER_META_HANDLE *h) {
+ return h ? h->hMetadata : NULL;
+}
+
+PROVIDER_META_HANDLE *provider_dup(PROVIDER_META_HANDLE *h) {
+ if(h) h->locks++;
+ return h;
+}
+
+static void provider_meta_handle_delete(PROVIDER_META_HANDLE *h) {
+ PROVIDER *p = h->provider;
+
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(p->handles, h, prev, next);
+
+ if(h->hMetadata)
+ EvtClose(h->hMetadata);
+
+ aral_freez(pbc.aral_handles, h);
+
+ fatal_assert(pbc.total_handles && p->total_handles && p->available_handles);
+
+ pbc.total_handles--;
+ p->total_handles--;
+
+ pbc.deleted_handles++;
+ p->deleted_handles++;
+
+ p->available_handles--;
+}
+
+void providers_release_unused_handles(void) {
+ usec_t now_ut = now_monotonic_usec();
+
+ spinlock_lock(&pbc.spinlock);
+ for(size_t i = 0; i < pbc.hashtable.size ; i++) {
+ SIMPLE_HASHTABLE_SLOT_PROVIDER *slot = &pbc.hashtable.hashtable[i];
+ PROVIDER *p = SIMPLE_HASHTABLE_SLOT_DATA(slot);
+ if(!p) continue;
+
+ PROVIDER_META_HANDLE *h = p->handles;
+ while(h) {
+ PROVIDER_META_HANDLE *next = h->next;
+
+ if(!h->locks && (now_ut - h->created_monotonic_ut) >= WINDOWS_EVENTS_RELEASE_IDLE_PROVIDER_HANDLES_TIME_UT)
+ provider_meta_handle_delete(h);
+
+ h = next;
+ }
+ }
+ spinlock_unlock(&pbc.spinlock);
+}
+
+void provider_release(PROVIDER_META_HANDLE *h) {
+ if(!h) return;
+ pid_t me = gettid_cached();
+ fatal_assert(h->owner == me);
+ fatal_assert(h->locks > 0);
+ if(--h->locks == 0) {
+ PROVIDER *p = h->provider;
+
+ spinlock_lock(&pbc.spinlock);
+ h->owner = 0;
+
+ if(++p->available_handles > MAX_OPEN_HANDLES_PER_PROVIDER) {
+ // there are too many idle handles on this provider
+ provider_meta_handle_delete(h);
+ }
+ else if(h->next) {
+ // it is not the last, put it at the end
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(p->handles, h, prev, next);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(p->handles, h, prev, next);
+ }
+
+ spinlock_unlock(&pbc.spinlock);
+ }
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// load provider lists
+
+static bool wevt_get_property_from_array(WEVT_VARIANT *property, EVT_HANDLE handle, DWORD dwIndex, EVT_PUBLISHER_METADATA_PROPERTY_ID PropertyId) {
+ DWORD used = 0;
+
+ if (!EvtGetObjectArrayProperty(handle, PropertyId, dwIndex, 0, property->size, property->data, &used)) {
+ DWORD status = GetLastError();
+ if (status != ERROR_INSUFFICIENT_BUFFER) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtGetObjectArrayProperty() failed");
+ return false;
+ }
+
+ wevt_variant_resize(property, used);
+ if (!EvtGetObjectArrayProperty(handle, PropertyId, dwIndex, 0, property->size, property->data, &used)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtGetObjectArrayProperty() failed");
+ return false;
+ }
+ }
+
+ property->used = used;
+ return true;
+}
+
+// Comparison function for ascending order (for Levels, Opcodes, Tasks)
+static int compare_ascending(const void *a, const void *b) {
+ struct provider_data *d1 = (struct provider_data *)a;
+ struct provider_data *d2 = (struct provider_data *)b;
+
+ if (d1->value < d2->value) return -1;
+ if (d1->value > d2->value) return 1;
+ return 0;
+}
+
+//// Comparison function for descending order (for Keywords)
+//static int compare_descending(const void *a, const void *b) {
+// struct provider_data *d1 = (struct provider_data *)a;
+// struct provider_data *d2 = (struct provider_data *)b;
+//
+// if (d1->value > d2->value) return -1;
+// if (d1->value < d2->value) return 1;
+// return 0;
+//}
+
+static void provider_load_list(PROVIDER_META_HANDLE *h, WEVT_VARIANT *content, WEVT_VARIANT *property,
+ TXT_UTF16 *dst, struct provider_list *l, EVT_PUBLISHER_METADATA_PROPERTY_ID property_id) {
+ if(!h || !h->hMetadata) return;
+
+ EVT_PUBLISHER_METADATA_PROPERTY_ID name_id, message_id, value_id;
+ uint8_t value_bits = 32;
+ int (*compare_func)(const void *, const void *);
+ bool (*is_valid)(uint64_t, bool);
+
+ switch(property_id) {
+ case EvtPublisherMetadataLevels:
+ name_id = EvtPublisherMetadataLevelName;
+ message_id = EvtPublisherMetadataLevelMessageID;
+ value_id = EvtPublisherMetadataLevelValue;
+ value_bits = 32;
+ compare_func = compare_ascending;
+ is_valid = is_valid_provider_level;
+ break;
+
+ case EvtPublisherMetadataOpcodes:
+ name_id = EvtPublisherMetadataOpcodeName;
+ message_id = EvtPublisherMetadataOpcodeMessageID;
+ value_id = EvtPublisherMetadataOpcodeValue;
+ value_bits = 32;
+ is_valid = is_valid_provider_opcode;
+ compare_func = compare_ascending;
+ break;
+
+ case EvtPublisherMetadataTasks:
+ name_id = EvtPublisherMetadataTaskName;
+ message_id = EvtPublisherMetadataTaskMessageID;
+ value_id = EvtPublisherMetadataTaskValue;
+ value_bits = 32;
+ is_valid = is_valid_provider_task;
+ compare_func = compare_ascending;
+ break;
+
+ case EvtPublisherMetadataKeywords:
+ name_id = EvtPublisherMetadataKeywordName;
+ message_id = EvtPublisherMetadataKeywordMessageID;
+ value_id = EvtPublisherMetadataKeywordValue;
+ value_bits = 64;
+ is_valid = is_valid_provider_keyword;
+ compare_func = NULL;
+ break;
+
+ default:
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Internal Error: Can't handle property id %u", property_id);
+ return;
+ }
+
+ EVT_HANDLE hMetadata = h->hMetadata;
+ EVT_HANDLE hArray = NULL;
+ DWORD itemCount = 0;
+
+ // Get the metadata array for the list (e.g., opcodes, tasks, or levels)
+ if(!provider_property_get(h, content, property_id))
+ goto cleanup;
+
+ // Get the number of items (e.g., levels, tasks, or opcodes)
+ hArray = content->data->EvtHandleVal;
+ if (!EvtGetObjectArraySize(hArray, &itemCount)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtGetObjectArraySize() failed");
+ goto cleanup;
+ }
+
+ if (itemCount == 0) {
+ l->total = 0;
+ l->array = NULL;
+ goto cleanup;
+ }
+
+ // Allocate memory for the list items
+ l->array = callocz(itemCount, sizeof(struct provider_data));
+ l->total = itemCount;
+
+ uint64_t min = UINT64_MAX, max = 0, mask = 0;
+
+ // Iterate over the list and populate the entries
+ for (DWORD i = 0; i < itemCount; ++i) {
+ struct provider_data *d = &l->array[i];
+
+ // Get the value (e.g., opcode, task, or level)
+ if (wevt_get_property_from_array(property, hArray, i, value_id)) {
+ switch(value_bits) {
+ case 64:
+ d->value = wevt_field_get_uint64(property->data);
+ break;
+
+ case 32:
+ d->value = wevt_field_get_uint32(property->data);
+ break;
+ }
+
+ if(d->value < min)
+ min = d->value;
+
+ if(d->value > max)
+ max = d->value;
+
+ mask |= d->value;
+
+ if(!is_valid(d->value, false))
+ l->exceeds_data_type = true;
+ }
+
+ // Get the message ID
+ if (wevt_get_property_from_array(property, hArray, i, message_id)) {
+ uint32_t messageID = wevt_field_get_uint32(property->data);
+
+ if (messageID != (uint32_t)-1) {
+ if (EvtFormatMessage_utf16(dst, hMetadata, NULL, messageID, EvtFormatMessageId)) {
+ size_t len;
+ d->name = utf16_to_utf8_strdupz(dst->data, &len);
+ d->len = len;
+ }
+ }
+ }
+
+ // Get the name if the message is missing
+ if (!d->name && wevt_get_property_from_array(property, hArray, i, name_id)) {
+ fatal_assert(property->data->Type == EvtVarTypeString);
+ size_t len;
+ d->name = utf16_to_utf8_strdupz(property->data->StringVal, &len);
+ d->len = len;
+ }
+
+ // Calculate the hash for the name
+ if (d->name)
+ d->hash = XXH3_64bits(d->name, d->len);
+ }
+
+ l->min = min;
+ l->max = max;
+ l->mask = mask;
+
+ if(itemCount > 1 && compare_func != NULL) {
+ // Sort the array based on the value (ascending for all except keywords, descending for keywords)
+ qsort(l->array, itemCount, sizeof(struct provider_data), compare_func);
+ }
+
+cleanup:
+ if (hArray)
+ EvtClose(hArray);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// lookup functions
+
+// lookup bitmap metdata (returns a comma separated list of strings)
+static bool provider_bitmap_metadata(TXT_UTF8 *dst, struct provider_list *l, uint64_t value) {
+ if(!(value & l->mask) || !l->total || !l->array || l->exceeds_data_type)
+ return false;
+
+ // do not empty the buffer, there may be reserved keywords in it
+ // dst->used = 0;
+
+ if(dst->used)
+ dst->used--;
+
+ size_t added = 0;
+ for(size_t k = 0; value && k < l->total; k++) {
+ struct provider_data *d = &l->array[k];
+
+ if(d->value && (value & d->value) == d->value && d->name && d->len) {
+ const char *s = d->name;
+ size_t slen = d->len;
+
+ // remove the mask from the value
+ value &= ~(d->value);
+
+ txt_utf8_resize(dst, dst->used + slen + 2 + 1, true);
+
+ if(dst->used) {
+ // add a comma and a space
+ dst->data[dst->used++] = ',';
+ dst->data[dst->used++] = ' ';
+ }
+
+ memcpy(&dst->data[dst->used], s, slen);
+ dst->used += slen;
+ dst->src = TXT_SOURCE_PROVIDER;
+ added++;
+ }
+ }
+
+ if(dst->used > 1) {
+ txt_utf8_resize(dst, dst->used + 1, true);
+ dst->data[dst->used++] = 0;
+ }
+
+ fatal_assert(dst->used <= dst->size);
+ return added;
+}
+
+//// lookup a single value (returns its string)
+//static bool provider_value_metadata_linear(TXT_UTF8 *dst, struct provider_list *l, uint64_t value) {
+// if(value < l->min || value > l->max || !l->total || !l->array || l->exceeds_data_type)
+// return false;
+//
+// dst->used = 0;
+//
+// for(size_t k = 0; k < l->total; k++) {
+// struct provider_data *d = &l->array[k];
+//
+// if(d->value == value && d->name && d->len) {
+// const char *s = d->name;
+// size_t slen = d->len;
+//
+// txt_utf8_resize(dst, slen + 1, false);
+//
+// memcpy(dst->data, s, slen);
+// dst->used = slen;
+// dst->src = TXT_SOURCE_PROVIDER;
+//
+// break;
+// }
+// }
+//
+// if(dst->used) {
+// txt_utf8_resize(dst, dst->used + 1, true);
+// dst->data[dst->used++] = 0;
+// }
+//
+// fatal_assert(dst->used <= dst->size);
+//
+// return (dst->used > 0);
+//}
+
+static bool provider_value_metadata(TXT_UTF8 *dst, struct provider_list *l, uint64_t value) {
+ if(value < l->min || value > l->max || !l->total || !l->array || l->exceeds_data_type)
+ return false;
+
+ // if(l->total < 3) return provider_value_metadata_linear(dst, l, value);
+
+ dst->used = 0;
+
+ size_t left = 0;
+ size_t right = l->total - 1;
+
+ // Binary search within bounds
+ while (left <= right) {
+ size_t mid = left + (right - left) / 2;
+ struct provider_data *d = &l->array[mid];
+
+ if (d->value == value) {
+ // Value found, now check if it has a valid name and length
+ if (d->name && d->len) {
+ const char *s = d->name;
+ size_t slen = d->len;
+
+ txt_utf8_resize(dst, slen + 1, false);
+ memcpy(dst->data, s, slen);
+ dst->used = slen;
+ dst->data[dst->used++] = 0;
+ dst->src = TXT_SOURCE_PROVIDER;
+ }
+ break;
+ }
+
+ if (d->value < value)
+ left = mid + 1;
+ else {
+ if (mid == 0) break;
+ right = mid - 1;
+ }
+ }
+
+ fatal_assert(dst->used <= dst->size);
+ return (dst->used > 0);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// public API to lookup metadata
+
+bool provider_keyword_cacheable(PROVIDER_META_HANDLE *h) {
+ return h && !h->provider->keyword.exceeds_data_type;
+}
+
+bool provider_tasks_cacheable(PROVIDER_META_HANDLE *h) {
+ return h && !h->provider->tasks.exceeds_data_type;
+}
+
+bool is_useful_provider_for_levels(PROVIDER_META_HANDLE *h) {
+ return h && !h->provider->levels.exceeds_data_type;
+}
+
+bool provider_opcodes_cacheable(PROVIDER_META_HANDLE *h) {
+ return h && !h->provider->opcodes.exceeds_data_type;
+}
+
+bool provider_get_keywords(TXT_UTF8 *dst, PROVIDER_META_HANDLE *h, uint64_t value) {
+ if(!h) return false;
+ return provider_bitmap_metadata(dst, &h->provider->keyword, value);
+}
+
+bool provider_get_level(TXT_UTF8 *dst, PROVIDER_META_HANDLE *h, uint64_t value) {
+ if(!h) return false;
+ return provider_value_metadata(dst, &h->provider->levels, value);
+}
+
+bool provider_get_task(TXT_UTF8 *dst, PROVIDER_META_HANDLE *h, uint64_t value) {
+ if(!h) return false;
+ return provider_value_metadata(dst, &h->provider->tasks, value);
+}
+
+bool provider_get_opcode(TXT_UTF8 *dst, PROVIDER_META_HANDLE *h, uint64_t value) {
+ if(!h) return false;
+ return provider_value_metadata(dst, &h->provider->opcodes, value);
+}
diff --git a/src/collectors/windows-events.plugin/windows-events-providers.h b/src/collectors/windows-events.plugin/windows-events-providers.h
new file mode 100644
index 000000000..b6d476c5c
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-providers.h
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_WINDOWS_EVENTS_PROVIDERS_H
+#define NETDATA_WINDOWS_EVENTS_PROVIDERS_H
+
+typedef enum __attribute__((packed)) {
+ WEVT_PLATFORM_UNKNOWN = 0,
+ WEVT_PLATFORM_WEL,
+ WEVT_PLATFORM_ETW,
+ WEVT_PLATFORM_TL,
+} WEVT_PROVIDER_PLATFORM;
+
+#include "windows-events.h"
+
+struct provider_meta_handle;
+typedef struct provider_meta_handle PROVIDER_META_HANDLE;
+
+PROVIDER_META_HANDLE *provider_get(ND_UUID uuid, LPCWSTR providerName);
+void provider_release(PROVIDER_META_HANDLE *h);
+EVT_HANDLE provider_handle(PROVIDER_META_HANDLE *h);
+PROVIDER_META_HANDLE *provider_dup(PROVIDER_META_HANDLE *h);
+
+void providers_release_unused_handles(void);
+
+const char *provider_get_name(PROVIDER_META_HANDLE *p);
+ND_UUID provider_get_uuid(PROVIDER_META_HANDLE *p);
+
+void provider_cache_init(void);
+
+bool provider_keyword_cacheable(PROVIDER_META_HANDLE *h);
+bool provider_tasks_cacheable(PROVIDER_META_HANDLE *h);
+bool is_useful_provider_for_levels(PROVIDER_META_HANDLE *h);
+bool provider_opcodes_cacheable(PROVIDER_META_HANDLE *h);
+
+bool provider_get_keywords(TXT_UTF8 *dst, PROVIDER_META_HANDLE *h, uint64_t value);
+bool provider_get_level(TXT_UTF8 *dst, PROVIDER_META_HANDLE *h, uint64_t value);
+bool provider_get_task(TXT_UTF8 *dst, PROVIDER_META_HANDLE *h, uint64_t value);
+bool provider_get_opcode(TXT_UTF8 *dst, PROVIDER_META_HANDLE *h, uint64_t value);
+WEVT_PROVIDER_PLATFORM provider_get_platform(PROVIDER_META_HANDLE *p);
+
+#endif //NETDATA_WINDOWS_EVENTS_PROVIDERS_H
diff --git a/src/collectors/windows-events.plugin/windows-events-query-builder.c b/src/collectors/windows-events.plugin/windows-events-query-builder.c
new file mode 100644
index 000000000..75c6fbdca
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-query-builder.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "windows-events-query-builder.h"
+
+// --------------------------------------------------------------------------------------------------------------------
+// query without XPath
+
+typedef struct static_utf8_8k {
+ char buffer[8192];
+ size_t size;
+ size_t len;
+} STATIC_BUF_8K;
+
+typedef struct static_unicode_16k {
+ wchar_t buffer[16384];
+ size_t size;
+ size_t len;
+} STATIC_UNI_16K;
+
+static bool wevt_foreach_selected_value_cb(FACETS *facets __maybe_unused, size_t id, const char *key, const char *value, void *data) {
+ STATIC_BUF_8K *b = data;
+
+ b->len += snprintfz(&b->buffer[b->len], b->size - b->len,
+ "%s%s=%s",
+ id ? " or " : "", key, value);
+
+ return b->len < b->size;
+}
+
+wchar_t *wevt_generate_query_no_xpath(LOGS_QUERY_STATUS *lqs, BUFFER *wb) {
+ static __thread STATIC_UNI_16K q = {
+ .size = sizeof(q.buffer) / sizeof(wchar_t),
+ .len = 0,
+ };
+ static __thread STATIC_BUF_8K b = {
+ .size = sizeof(b.buffer) / sizeof(char),
+ .len = 0,
+ };
+
+ lqs_query_timeframe(lqs, ANCHOR_DELTA_UT);
+
+ usec_t seek_to = lqs->query.start_ut;
+ if(lqs->rq.direction == FACETS_ANCHOR_DIRECTION_BACKWARD)
+ // windows events queries are limited to millisecond resolution
+ // so, in order not to lose data, we have to add
+ // a millisecond when the direction is backward
+ seek_to += USEC_PER_MS;
+
+ // Convert the microseconds since Unix epoch to FILETIME (used in Windows APIs)
+ FILETIME fileTime = os_unix_epoch_ut_to_filetime(seek_to);
+
+ // Convert FILETIME to SYSTEMTIME for use in XPath
+ SYSTEMTIME systemTime;
+ if (!FileTimeToSystemTime(&fileTime, &systemTime)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "FileTimeToSystemTime() failed");
+ return NULL;
+ }
+
+ // Format SYSTEMTIME into ISO 8601 format (YYYY-MM-DDTHH:MM:SS.sssZ)
+ q.len = swprintf(q.buffer, q.size,
+ L"Event/System[TimeCreated[@SystemTime%ls\"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ\"]",
+ lqs->rq.direction == FACETS_ANCHOR_DIRECTION_BACKWARD ? L"<=" : L">=",
+ systemTime.wYear, systemTime.wMonth, systemTime.wDay,
+ systemTime.wHour, systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds);
+
+ if(lqs->rq.slice) {
+ b.len = snprintf(b.buffer, b.size, " and (");
+ if (facets_foreach_selected_value_in_key(
+ lqs->facets,
+ WEVT_FIELD_LEVEL,
+ sizeof(WEVT_FIELD_LEVEL) - 1,
+ used_hashes_registry,
+ wevt_foreach_selected_value_cb,
+ &b)) {
+ b.len += snprintf(&b.buffer[b.len], b.size - b.len, ")");
+ if (b.len < b.size) {
+ utf82unicode(&q.buffer[q.len], q.size - q.len, b.buffer);
+ q.len = wcslen(q.buffer);
+ }
+ }
+
+ b.len = snprintf(b.buffer, b.size, " and (");
+ if (facets_foreach_selected_value_in_key(
+ lqs->facets,
+ WEVT_FIELD_EVENTID,
+ sizeof(WEVT_FIELD_EVENTID) - 1,
+ used_hashes_registry,
+ wevt_foreach_selected_value_cb,
+ &b)) {
+ b.len += snprintf(&b.buffer[b.len], b.size - b.len, ")");
+ if (b.len < b.size) {
+ utf82unicode(&q.buffer[q.len], q.size - q.len, b.buffer);
+ q.len = wcslen(q.buffer);
+ }
+ }
+ }
+
+ q.len += swprintf(&q.buffer[q.len], q.size - q.len, L"]");
+
+ buffer_json_member_add_string(wb, "_query", channel2utf8(q.buffer));
+
+ return q.buffer;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// query with XPath
+
diff --git a/src/collectors/windows-events.plugin/windows-events-query-builder.h b/src/collectors/windows-events.plugin/windows-events-query-builder.h
new file mode 100644
index 000000000..80136e0aa
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-query-builder.h
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_WINDOWS_EVENTS_QUERY_BUILDER_H
+#define NETDATA_WINDOWS_EVENTS_QUERY_BUILDER_H
+
+#include "windows-events.h"
+
+wchar_t *wevt_generate_query_no_xpath(LOGS_QUERY_STATUS *lqs, BUFFER *wb);
+
+#endif //NETDATA_WINDOWS_EVENTS_QUERY_BUILDER_H
diff --git a/src/collectors/windows-events.plugin/windows-events-query-evt-variant.c b/src/collectors/windows-events.plugin/windows-events-query-evt-variant.c
new file mode 100644
index 000000000..ee3aa382b
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-query-evt-variant.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "windows-events.h"
+#include <sddl.h> // For SID string conversion
+
+// Function to append the separator if the buffer is not empty
+static inline void append_separator_if_needed(BUFFER *b, const char *separator) {
+ if (buffer_strlen(b) > 0 && separator != NULL)
+ buffer_strcat(b, separator);
+}
+
+// Helper function to convert UTF16 strings to UTF8 and append to the buffer
+static inline void append_utf16(BUFFER *b, LPCWSTR utf16Str, const char *separator) {
+ if (!utf16Str || !*utf16Str) return;
+
+ append_separator_if_needed(b, separator);
+
+ size_t remaining = b->size - b->len;
+ if(remaining < 128) {
+ buffer_need_bytes(b, 128);
+ remaining = b->size - b->len;
+ }
+
+ bool truncated = false;
+ size_t used = utf16_to_utf8(&b->buffer[b->len], remaining, utf16Str, -1, &truncated);
+ if(truncated) {
+ // we need to resize
+ size_t needed = utf16_to_utf8(NULL, 0, utf16Str, -1, NULL); // find the size needed
+ buffer_need_bytes(b, needed);
+ remaining = b->size - b->len;
+ used = utf16_to_utf8(&b->buffer[b->len], remaining, utf16Str, -1, NULL);
+ }
+
+ if(used) {
+ b->len += used - 1;
+
+ internal_fatal(buffer_strlen(b) != strlen(buffer_tostring(b)),
+ "Buffer length mismatch.");
+ }
+}
+
+// Function to append binary data to the buffer
+static inline void append_binary(BUFFER *b, PBYTE data, DWORD size, const char *separator) {
+ if (data == NULL || size == 0) return;
+
+ append_separator_if_needed(b, separator);
+
+ buffer_need_bytes(b, size * 4);
+ for (DWORD i = 0; i < size; i++) {
+ uint8_t value = data[i];
+ b->buffer[b->len++] = hex_digits[(value & 0xf0) >> 4];
+ b->buffer[b->len++] = hex_digits[(value & 0x0f)];
+ }
+}
+
+// Function to append size_t to the buffer
+static inline void append_size_t(BUFFER *b, size_t size, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_uint64(b, size);
+}
+
+// Function to append HexInt32 in hexadecimal format
+static inline void append_uint32_hex(BUFFER *b, UINT32 n, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_uint64_hex(b, n);
+}
+
+// Function to append HexInt64 in hexadecimal format
+static inline void append_uint64_hex(BUFFER *b, UINT64 n, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_uint64_hex(b, n);
+}
+
+// Function to append various data types to the buffer
+static inline void append_uint64(BUFFER *b, UINT64 n, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_uint64(b, n);
+}
+
+static inline void append_int64(BUFFER *b, INT64 n, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_int64(b, n);
+}
+
+static inline void append_double(BUFFER *b, double n, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_netdata_double(b, n);
+}
+
+static inline void append_guid(BUFFER *b, GUID *guid, const char *separator) {
+ fatal_assert(sizeof(GUID) == sizeof(nd_uuid_t));
+
+ append_separator_if_needed(b, separator);
+
+ ND_UUID *uuid = (ND_UUID *)guid;
+ buffer_need_bytes(b, UUID_STR_LEN);
+ uuid_unparse_lower(uuid->uuid, &b->buffer[b->len]);
+ b->len += UUID_STR_LEN - 1;
+
+ internal_fatal(buffer_strlen(b) != strlen(buffer_tostring(b)),
+ "Buffer length mismatch.");
+}
+
+static inline void append_systime(BUFFER *b, SYSTEMTIME *st, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_sprintf(b, "%04d-%02d-%02d %02d:%02d:%02d",
+ st->wYear, st->wMonth, st->wDay, st->wHour, st->wMinute, st->wSecond);
+}
+
+static inline void append_filetime(BUFFER *b, FILETIME *ft, const char *separator) {
+ SYSTEMTIME st;
+ if (FileTimeToSystemTime(ft, &st))
+ append_systime(b, &st, separator);
+}
+
+static inline void append_sid(BUFFER *b, PSID sid, const char *separator) {
+ cached_sid_to_buffer_append(sid, b, separator);
+}
+
+static inline void append_sbyte(BUFFER *b, INT8 n, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_int64(b, n);
+}
+
+static inline void append_byte(BUFFER *b, UINT8 n, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_uint64(b, n);
+}
+
+static inline void append_int16(BUFFER *b, INT16 n, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_int64(b, n);
+}
+
+static inline void append_uint16(BUFFER *b, UINT16 n, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_uint64(b, n);
+}
+
+static inline void append_int32(BUFFER *b, INT32 n, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_int64(b, n);
+}
+
+static inline void append_uint32(BUFFER *b, UINT32 n, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_uint64(b, n);
+}
+
+// Function to append EVT_HANDLE to the buffer
+static inline void append_evt_handle(BUFFER *b, EVT_HANDLE h, const char *separator) {
+ append_separator_if_needed(b, separator);
+ buffer_print_uint64_hex(b, (uintptr_t)h);
+}
+
+// Function to append XML data (UTF-16) to the buffer
+static inline void append_evt_xml(BUFFER *b, LPCWSTR xmlData, const char *separator) {
+ append_utf16(b, xmlData, separator); // XML data is essentially UTF-16 string
+}
+
+void evt_variant_to_buffer(BUFFER *b, EVT_VARIANT *ev, const char *separator) {
+ if(ev->Type == EvtVarTypeNull) return;
+
+ if (ev->Type & EVT_VARIANT_TYPE_ARRAY) {
+ for (DWORD i = 0; i < ev->Count; i++) {
+ switch (ev->Type & EVT_VARIANT_TYPE_MASK) {
+ case EvtVarTypeString:
+ append_utf16(b, ev->StringArr[i], separator);
+ break;
+
+ case EvtVarTypeAnsiString:
+ if (ev->AnsiStringArr[i] != NULL) {
+ append_utf16(b, (LPCWSTR)ev->AnsiStringArr[i], separator);
+ }
+ break;
+
+ case EvtVarTypeSByte:
+ append_sbyte(b, ev->SByteArr[i], separator);
+ break;
+
+ case EvtVarTypeByte:
+ append_byte(b, ev->ByteArr[i], separator);
+ break;
+
+ case EvtVarTypeInt16:
+ append_int16(b, ev->Int16Arr[i], separator);
+ break;
+
+ case EvtVarTypeUInt16:
+ append_uint16(b, ev->UInt16Arr[i], separator);
+ break;
+
+ case EvtVarTypeInt32:
+ append_int32(b, ev->Int32Arr[i], separator);
+ break;
+
+ case EvtVarTypeUInt32:
+ append_uint32(b, ev->UInt32Arr[i], separator);
+ break;
+
+ case EvtVarTypeInt64:
+ append_int64(b, ev->Int64Arr[i], separator);
+ break;
+
+ case EvtVarTypeUInt64:
+ append_uint64(b, ev->UInt64Arr[i], separator);
+ break;
+
+ case EvtVarTypeSingle:
+ append_double(b, ev->SingleArr[i], separator);
+ break;
+
+ case EvtVarTypeDouble:
+ append_double(b, ev->DoubleArr[i], separator);
+ break;
+
+ case EvtVarTypeGuid:
+ append_guid(b, &ev->GuidArr[i], separator);
+ break;
+
+ case EvtVarTypeFileTime:
+ append_filetime(b, &ev->FileTimeArr[i], separator);
+ break;
+
+ case EvtVarTypeSysTime:
+ append_systime(b, &ev->SysTimeArr[i], separator);
+ break;
+
+ case EvtVarTypeSid:
+ append_sid(b, ev->SidArr[i], separator);
+ break;
+
+ case EvtVarTypeBinary:
+ append_binary(b, ev->BinaryVal, ev->Count, separator);
+ break;
+
+ case EvtVarTypeSizeT:
+ append_size_t(b, ev->SizeTArr[i], separator);
+ break;
+
+ case EvtVarTypeHexInt32:
+ append_uint32_hex(b, ev->UInt32Arr[i], separator);
+ break;
+
+ case EvtVarTypeHexInt64:
+ append_uint64_hex(b, ev->UInt64Arr[i], separator);
+ break;
+
+ case EvtVarTypeEvtHandle:
+ append_evt_handle(b, ev->EvtHandleVal, separator);
+ break;
+
+ case EvtVarTypeEvtXml:
+ append_evt_xml(b, ev->XmlValArr[i], separator);
+ break;
+
+ default:
+ // Skip unknown array types
+ break;
+ }
+ }
+ } else {
+ switch (ev->Type & EVT_VARIANT_TYPE_MASK) {
+ case EvtVarTypeNull:
+ // Do nothing for null types
+ break;
+
+ case EvtVarTypeString:
+ append_utf16(b, ev->StringVal, separator);
+ break;
+
+ case EvtVarTypeAnsiString:
+ append_utf16(b, (LPCWSTR)ev->AnsiStringVal, separator);
+ break;
+
+ case EvtVarTypeSByte:
+ append_sbyte(b, ev->SByteVal, separator);
+ break;
+
+ case EvtVarTypeByte:
+ append_byte(b, ev->ByteVal, separator);
+ break;
+
+ case EvtVarTypeInt16:
+ append_int16(b, ev->Int16Val, separator);
+ break;
+
+ case EvtVarTypeUInt16:
+ append_uint16(b, ev->UInt16Val, separator);
+ break;
+
+ case EvtVarTypeInt32:
+ append_int32(b, ev->Int32Val, separator);
+ break;
+
+ case EvtVarTypeUInt32:
+ append_uint32(b, ev->UInt32Val, separator);
+ break;
+
+ case EvtVarTypeInt64:
+ append_int64(b, ev->Int64Val, separator);
+ break;
+
+ case EvtVarTypeUInt64:
+ append_uint64(b, ev->UInt64Val, separator);
+ break;
+
+ case EvtVarTypeSingle:
+ append_double(b, ev->SingleVal, separator);
+ break;
+
+ case EvtVarTypeDouble:
+ append_double(b, ev->DoubleVal, separator);
+ break;
+
+ case EvtVarTypeBoolean:
+ append_separator_if_needed(b, separator);
+ buffer_strcat(b, ev->BooleanVal ? "true" : "false");
+ break;
+
+ case EvtVarTypeGuid:
+ append_guid(b, ev->GuidVal, separator);
+ break;
+
+ case EvtVarTypeBinary:
+ append_binary(b, ev->BinaryVal, ev->Count, separator);
+ break;
+
+ case EvtVarTypeSizeT:
+ append_size_t(b, ev->SizeTVal, separator);
+ break;
+
+ case EvtVarTypeHexInt32:
+ append_uint32_hex(b, ev->UInt32Val, separator);
+ break;
+
+ case EvtVarTypeHexInt64:
+ append_uint64_hex(b, ev->UInt64Val, separator);
+ break;
+
+ case EvtVarTypeEvtHandle:
+ append_evt_handle(b, ev->EvtHandleVal, separator);
+ break;
+
+ case EvtVarTypeEvtXml:
+ append_evt_xml(b, ev->XmlVal, separator);
+ break;
+
+ default:
+ // Skip unknown types
+ break;
+ }
+ }
+}
diff --git a/src/collectors/windows-events.plugin/windows-events-query.c b/src/collectors/windows-events.plugin/windows-events-query.c
new file mode 100644
index 000000000..fefa72829
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-query.c
@@ -0,0 +1,717 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "windows-events.h"
+
+static void wevt_event_done(WEVT_LOG *log);
+
+static uint64_t wevt_log_file_size(const wchar_t *channel);
+
+// --------------------------------------------------------------------------------------------------------------------
+
+static const char *EvtGetExtendedStatus_utf8(void) {
+ static __thread wchar_t wbuf[4096];
+ static __thread char buf[4096];
+ DWORD wbuf_used = 0;
+
+ if(EvtGetExtendedStatus(sizeof(wbuf) / sizeof(wchar_t), wbuf, &wbuf_used) == ERROR_SUCCESS) {
+ wbuf[sizeof(wbuf) / sizeof(wchar_t) - 1] = 0;
+ unicode2utf8(buf, sizeof(buf), wbuf);
+ }
+ else
+ buf[0] = '\0';
+
+ // the EvtGetExtendedStatus() may be successful with an empty message
+ if(!buf[0])
+ strncpyz(buf, "no additional information", sizeof(buf) - 1);
+
+ return buf;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+bool EvtFormatMessage_utf16(
+ TXT_UTF16 *dst, EVT_HANDLE hMetadata, EVT_HANDLE hEvent, DWORD dwMessageId, EVT_FORMAT_MESSAGE_FLAGS flags) {
+ dst->used = 0;
+
+ DWORD size = 0;
+ if(!dst->data) {
+ EvtFormatMessage(hMetadata, hEvent, dwMessageId, 0, NULL, flags, 0, NULL, &size);
+ if(!size) {
+ // nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtFormatMessage() to get message size failed.");
+ goto cleanup;
+ }
+ txt_utf16_resize(dst, size, false);
+ }
+
+ // First, try to get the message using the existing buffer
+ if (!EvtFormatMessage(hMetadata, hEvent, dwMessageId, 0, NULL, flags, dst->size, dst->data, &size) || !dst->data) {
+ if (dst->data && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ // nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtFormatMessage() failed.");
+ goto cleanup;
+ }
+
+ // Try again with the resized buffer
+ txt_utf16_resize(dst, size, false);
+ if (!EvtFormatMessage(hMetadata, hEvent, dwMessageId, 0, NULL, flags, dst->size, dst->data, &size)) {
+ // nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtFormatMessage() failed after resizing buffer.");
+ goto cleanup;
+ }
+ }
+
+ // make sure it is null terminated
+ if(size <= dst->size)
+ dst->data[size - 1] = 0;
+ else
+ dst->data[dst->size - 1] = 0;
+
+ // unfortunately we have to calculate the length every time
+ // the size returned may not be the length of the dst string
+ dst->used = wcslen(dst->data) + 1;
+
+ return true;
+
+cleanup:
+ dst->used = 0;
+ return false;
+}
+
+static bool EvtFormatMessage_utf8(
+ TXT_UTF16 *tmp, PROVIDER_META_HANDLE *p, EVT_HANDLE hEvent,
+ TXT_UTF8 *dst, EVT_FORMAT_MESSAGE_FLAGS flags) {
+
+ dst->src = TXT_SOURCE_EVENT_LOG;
+
+ if(EvtFormatMessage_utf16(tmp, provider_handle(p), hEvent, 0, flags))
+ return txt_utf16_to_utf8(dst, tmp);
+
+ txt_utf8_empty(dst);
+ return false;
+}
+
+bool EvtFormatMessage_Event_utf8(TXT_UTF16 *tmp, PROVIDER_META_HANDLE *p, EVT_HANDLE hEvent, TXT_UTF8 *dst) {
+ return EvtFormatMessage_utf8(tmp, p, hEvent, dst, EvtFormatMessageEvent);
+}
+
+bool EvtFormatMessage_Xml_utf8(TXT_UTF16 *tmp, PROVIDER_META_HANDLE *p, EVT_HANDLE hEvent, TXT_UTF8 *dst) {
+ return EvtFormatMessage_utf8(tmp, p, hEvent, dst, EvtFormatMessageXml);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+static void wevt_get_field_from_cache(
+ WEVT_LOG *log, uint64_t value, PROVIDER_META_HANDLE *h,
+ TXT_UTF8 *dst, const ND_UUID *provider,
+ WEVT_FIELD_TYPE cache_type, EVT_FORMAT_MESSAGE_FLAGS flags) {
+
+ if (field_cache_get(cache_type, provider, value, dst))
+ return;
+
+ EvtFormatMessage_utf8(&log->ops.unicode, h, log->hEvent, dst, flags);
+ field_cache_set(cache_type, provider, value, dst);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// Level
+
+#define SET_LEN_AND_RETURN(constant) *len = sizeof(constant) - 1; return constant
+
+static inline const char *wevt_level_hardcoded(uint64_t level, size_t *len) {
+ switch(level) {
+ case WEVT_LEVEL_NONE: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_NONE);
+ case WEVT_LEVEL_CRITICAL: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_CRITICAL);
+ case WEVT_LEVEL_ERROR: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_ERROR);
+ case WEVT_LEVEL_WARNING: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_WARNING);
+ case WEVT_LEVEL_INFORMATION: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_INFORMATION);
+ case WEVT_LEVEL_VERBOSE: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_VERBOSE);
+ default: *len = 0; return NULL;
+ }
+}
+
+static void wevt_get_level(WEVT_LOG *log, WEVT_EVENT *ev, PROVIDER_META_HANDLE *h) {
+ TXT_UTF8 *dst = &log->ops.level;
+ uint64_t value = ev->level;
+
+ txt_utf8_empty(dst);
+
+ EVT_FORMAT_MESSAGE_FLAGS flags = EvtFormatMessageLevel;
+ WEVT_FIELD_TYPE cache_type = WEVT_FIELD_TYPE_LEVEL;
+ bool is_provider = is_valid_provider_level(value, true);
+
+ if(!is_provider) {
+ size_t len;
+ const char *hardcoded = wevt_level_hardcoded(value, &len);
+ if(hardcoded) {
+ txt_utf8_set(dst, hardcoded, len);
+ dst->src = TXT_SOURCE_HARDCODED;
+ }
+ else {
+ // since this is not a provider value
+ // we expect to get the system description of it
+ wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags);
+ }
+ }
+ else if (!provider_get_level(dst, h, value)) {
+ // not found in the manifest, get it from the cache
+ wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags);
+ }
+
+ txt_utf8_set_numeric_if_empty(
+ dst, WEVT_PREFIX_LEVEL, sizeof(WEVT_PREFIX_LEVEL) - 1, ev->level);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// Opcode
+
+static inline const char *wevt_opcode_hardcoded(uint64_t opcode, size_t *len) {
+ switch(opcode) {
+ case WEVT_OPCODE_INFO: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_INFO);
+ case WEVT_OPCODE_START: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_START);
+ case WEVT_OPCODE_STOP: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_STOP);
+ case WEVT_OPCODE_DC_START: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_DC_START);
+ case WEVT_OPCODE_DC_STOP: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_DC_STOP);
+ case WEVT_OPCODE_EXTENSION: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_EXTENSION);
+ case WEVT_OPCODE_REPLY: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_REPLY);
+ case WEVT_OPCODE_RESUME: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_RESUME);
+ case WEVT_OPCODE_SUSPEND: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_SUSPEND);
+ case WEVT_OPCODE_SEND: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_SEND);
+ case WEVT_OPCODE_RECEIVE: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_RECEIVE);
+ default: *len = 0; return NULL;
+ }
+}
+
+static void wevt_get_opcode(WEVT_LOG *log, WEVT_EVENT *ev, PROVIDER_META_HANDLE *h) {
+ TXT_UTF8 *dst = &log->ops.opcode;
+ uint64_t value = ev->opcode;
+
+ txt_utf8_empty(dst);
+
+ EVT_FORMAT_MESSAGE_FLAGS flags = EvtFormatMessageOpcode;
+ WEVT_FIELD_TYPE cache_type = WEVT_FIELD_TYPE_OPCODE;
+ bool is_provider = is_valid_provider_opcode(value, true);
+
+ if(!is_provider) {
+ size_t len;
+ const char *hardcoded = wevt_opcode_hardcoded(value, &len);
+ if(hardcoded) {
+ txt_utf8_set(dst, hardcoded, len);
+ dst->src = TXT_SOURCE_HARDCODED;
+ }
+ else {
+ // since this is not a provider value
+ // we expect to get the system description of it
+ wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags);
+ }
+ }
+ else if (!provider_get_opcode(dst, h, value)) {
+ // not found in the manifest, get it from the cache
+ wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags);
+ }
+
+ txt_utf8_set_numeric_if_empty(
+ dst, WEVT_PREFIX_OPCODE, sizeof(WEVT_PREFIX_OPCODE) - 1, ev->opcode);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// Task
+
+static const char *wevt_task_hardcoded(uint64_t task, size_t *len) {
+ switch(task) {
+ case WEVT_TASK_NONE: SET_LEN_AND_RETURN(WEVT_TASK_NAME_NONE);
+ default: *len = 0; return NULL;
+ }
+}
+
+static void wevt_get_task(WEVT_LOG *log, WEVT_EVENT *ev, PROVIDER_META_HANDLE *h) {
+ TXT_UTF8 *dst = &log->ops.task;
+ uint64_t value = ev->task;
+
+ txt_utf8_empty(dst);
+
+ EVT_FORMAT_MESSAGE_FLAGS flags = EvtFormatMessageTask;
+ WEVT_FIELD_TYPE cache_type = WEVT_FIELD_TYPE_TASK;
+ bool is_provider = is_valid_provider_task(value, true);
+
+ if(!is_provider) {
+ size_t len;
+ const char *hardcoded = wevt_task_hardcoded(value, &len);
+ if(hardcoded) {
+ txt_utf8_set(dst, hardcoded, len);
+ dst->src = TXT_SOURCE_HARDCODED;
+ }
+ else {
+ // since this is not a provider value
+ // we expect to get the system description of it
+ wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags);
+ }
+ }
+ else if (!provider_get_task(dst, h, value)) {
+ // not found in the manifest, get it from the cache
+ wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags);
+ }
+
+ txt_utf8_set_numeric_if_empty(
+ dst, WEVT_PREFIX_TASK, sizeof(WEVT_PREFIX_TASK) - 1, ev->task);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// Keyword
+
+#define SET_BITS(msk, txt) { .mask = msk, .name = txt, .len = sizeof(txt) - 1, }
+
+static uint64_t wevt_keyword_handle_reserved(uint64_t value, TXT_UTF8 *dst) {
+ struct {
+ uint64_t mask;
+ const char *name;
+ size_t len;
+ } bits[] = {
+ SET_BITS(WEVT_KEYWORD_EVENTLOG_CLASSIC, WEVT_KEYWORD_NAME_EVENTLOG_CLASSIC),
+ SET_BITS(WEVT_KEYWORD_CORRELATION_HINT, WEVT_KEYWORD_NAME_CORRELATION_HINT),
+ SET_BITS(WEVT_KEYWORD_AUDIT_SUCCESS, WEVT_KEYWORD_NAME_AUDIT_SUCCESS),
+ SET_BITS(WEVT_KEYWORD_AUDIT_FAILURE, WEVT_KEYWORD_NAME_AUDIT_FAILURE),
+ SET_BITS(WEVT_KEYWORD_SQM, WEVT_KEYWORD_NAME_SQM),
+ SET_BITS(WEVT_KEYWORD_WDI_DIAG, WEVT_KEYWORD_NAME_WDI_DIAG),
+ SET_BITS(WEVT_KEYWORD_WDI_CONTEXT, WEVT_KEYWORD_NAME_WDI_CONTEXT),
+ SET_BITS(WEVT_KEYWORD_RESPONSE_TIME, WEVT_KEYWORD_NAME_RESPONSE_TIME),
+ };
+
+ txt_utf8_empty(dst);
+
+ for(size_t i = 0; i < sizeof(bits) / sizeof(bits[0]) ;i++) {
+ if((value & bits[i].mask) == bits[i].mask) {
+ txt_utf8_add_keywords_separator_if_needed(dst);
+ txt_utf8_append(dst, bits[i].name, bits[i].len);
+ value &= ~(bits[i].mask);
+ dst->src = TXT_SOURCE_HARDCODED;
+ }
+ }
+
+ // return it without any remaining reserved bits
+ return value & 0x0000FFFFFFFFFFFF;
+}
+
+static void wevt_get_keyword(WEVT_LOG *log, WEVT_EVENT *ev, PROVIDER_META_HANDLE *h) {
+ TXT_UTF8 *dst = &log->ops.keywords;
+
+ if(ev->keywords == WEVT_KEYWORD_NONE) {
+ txt_utf8_set(dst, WEVT_KEYWORD_NAME_NONE, sizeof(WEVT_KEYWORD_NAME_NONE) - 1);
+ dst->src = TXT_SOURCE_HARDCODED;
+ }
+
+ uint64_t value = wevt_keyword_handle_reserved(ev->keywords, dst);
+
+ EVT_FORMAT_MESSAGE_FLAGS flags = EvtFormatMessageKeyword;
+ WEVT_FIELD_TYPE cache_type = WEVT_FIELD_TYPE_KEYWORD;
+
+ if(!value && dst->used <= 1) {
+ // no hardcoded info in the buffer, make it None
+ txt_utf8_set(dst, WEVT_KEYWORD_NAME_NONE, sizeof(WEVT_KEYWORD_NAME_NONE) - 1);
+ dst->src = TXT_SOURCE_HARDCODED;
+ }
+ else if (value && !provider_get_keywords(dst, h, value) && dst->used <= 1) {
+ // the provider did not provide any info and the description is still empty.
+ // the system returns 1 keyword, the highest bit, not a list
+ // so, when we call the system, we pass the original value (ev->keywords)
+ wevt_get_field_from_cache(log, ev->keywords, h, dst, &ev->provider, cache_type, flags);
+ }
+
+ txt_utf8_set_hex_if_empty(
+ dst, WEVT_PREFIX_KEYWORDS, sizeof(WEVT_PREFIX_KEYWORDS) - 1, ev->keywords);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// Fetching Events
+
+static inline bool wEvtRender(WEVT_LOG *log, EVT_HANDLE context, WEVT_VARIANT *raw) {
+ DWORD bytes_used = 0, property_count = 0;
+ if (!EvtRender(context, log->hEvent, EvtRenderEventValues, raw->size, raw->data, &bytes_used, &property_count)) {
+ // information exceeds the allocated space
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "EvtRender() failed, hRenderSystemContext: 0x%lx, hEvent: 0x%lx, content: 0x%lx, size: %u, extended info: %s",
+ (uintptr_t)context, (uintptr_t)log->hEvent, (uintptr_t)raw->data, raw->size,
+ EvtGetExtendedStatus_utf8());
+ return false;
+ }
+
+ wevt_variant_resize(raw, bytes_used);
+ if (!EvtRender(context, log->hEvent, EvtRenderEventValues, raw->size, raw->data, &bytes_used, &property_count)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "EvtRender() failed, after bytes_used increase, extended info: %s",
+ EvtGetExtendedStatus_utf8());
+ return false;
+ }
+ }
+ raw->used = bytes_used;
+ raw->count = property_count;
+
+ return true;
+}
+
+static bool wevt_get_next_event_one(WEVT_LOG *log, WEVT_EVENT *ev) {
+ bool ret = false;
+
+ if(!wEvtRender(log, log->hRenderSystemContext, &log->ops.raw.system))
+ goto cleanup;
+
+ EVT_VARIANT *content = log->ops.raw.system.data;
+
+ ev->id = wevt_field_get_uint64(&content[EvtSystemEventRecordId]);
+ ev->event_id = wevt_field_get_uint16(&content[EvtSystemEventID]);
+ ev->level = wevt_field_get_uint8(&content[EvtSystemLevel]);
+ ev->opcode = wevt_field_get_uint8(&content[EvtSystemOpcode]);
+ ev->keywords = wevt_field_get_uint64_hex(&content[EvtSystemKeywords]);
+ ev->version = wevt_field_get_uint8(&content[EvtSystemVersion]);
+ ev->task = wevt_field_get_uint16(&content[EvtSystemTask]);
+ ev->qualifiers = wevt_field_get_uint16(&content[EvtSystemQualifiers]);
+ ev->process_id = wevt_field_get_uint32(&content[EvtSystemProcessID]);
+ ev->thread_id = wevt_field_get_uint32(&content[EvtSystemThreadID]);
+ ev->created_ns = wevt_field_get_filetime_to_ns(&content[EvtSystemTimeCreated]);
+
+ if(log->type & WEVT_QUERY_EXTENDED) {
+ wevt_field_get_string_utf8(&content[EvtSystemChannel], &log->ops.channel);
+ wevt_field_get_string_utf8(&content[EvtSystemComputer], &log->ops.computer);
+ wevt_field_get_string_utf8(&content[EvtSystemProviderName], &log->ops.provider);
+ wevt_get_uuid_by_type(&content[EvtSystemProviderGuid], &ev->provider);
+ wevt_get_uuid_by_type(&content[EvtSystemActivityID], &ev->activity_id);
+ wevt_get_uuid_by_type(&content[EvtSystemRelatedActivityID], &ev->related_activity_id);
+ wevt_field_get_sid(&content[EvtSystemUserID], &log->ops.account, &log->ops.domain, &log->ops.sid);
+
+ PROVIDER_META_HANDLE *p = log->provider =
+ provider_get(ev->provider, content[EvtSystemProviderName].StringVal);
+
+ ev->platform = provider_get_platform(p);
+
+ wevt_get_level(log, ev, p);
+ wevt_get_task(log, ev, p);
+ wevt_get_opcode(log, ev, p);
+ wevt_get_keyword(log, ev, p);
+
+ if(log->type & WEVT_QUERY_EVENT_DATA && wEvtRender(log, log->hRenderUserContext, &log->ops.raw.user)) {
+#if (ON_FTS_PRELOAD_MESSAGE == 1)
+ EvtFormatMessage_Event_utf8(&log->ops.unicode, log->provider, log->hEvent, &log->ops.event);
+#endif
+#if (ON_FTS_PRELOAD_XML == 1)
+ EvtFormatMessage_Xml_utf8(&log->ops.unicode, log->provider, log->hEvent, &log->ops.xml);
+#endif
+#if (ON_FTS_PRELOAD_EVENT_DATA == 1)
+ for(size_t i = 0; i < log->ops.raw.user.count ;i++)
+ evt_variant_to_buffer(log->ops.event_data, &log->ops.raw.user.data[i], " ||| ");
+#endif
+ }
+ }
+
+ ret = true;
+
+cleanup:
+ return ret;
+}
+
+bool wevt_get_next_event(WEVT_LOG *log, WEVT_EVENT *ev) {
+ DWORD size = (log->type & WEVT_QUERY_EXTENDED) ? BATCH_NEXT_EVENT : 1;
+ DWORD max_failures = 10;
+
+ fatal_assert(log && log->hQuery && log->hRenderSystemContext);
+
+ while(max_failures > 0) {
+ if (log->batch.used >= log->batch.size) {
+ log->batch.size = 0;
+ log->batch.used = 0;
+ DWORD err;
+ if(!EvtNext(log->hQuery, size, log->batch.hEvents, INFINITE, 0, &log->batch.size)) {
+ err = GetLastError();
+ if(err == ERROR_NO_MORE_ITEMS)
+ return false; // no data available, return failure
+ }
+
+ if(!log->batch.size) {
+ if(size == 1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "EvtNext() failed, hQuery: 0x%lx, size: %zu, extended info: %s",
+ (uintptr_t)log->hQuery, (size_t)size, EvtGetExtendedStatus_utf8());
+ return false;
+ }
+
+ // EvtNext() returns true when it can full the array
+ // so, let's retry with a smaller array.
+ size /= 2;
+ if(size < 1) size = 1;
+ continue;
+ }
+ }
+
+ log->query_stats.event_count++;
+ log->log_stats.event_count++;
+
+ // cleanup any previous event data
+ wevt_event_done(log);
+
+ log->hEvent = log->batch.hEvents[log->batch.used];
+ log->batch.hEvents[log->batch.used] = NULL;
+ log->batch.used++;
+
+ if(wevt_get_next_event_one(log, ev))
+ return true;
+ else {
+ log->query_stats.failed_count++;
+ log->log_stats.failed_count++;
+ max_failures--;
+ }
+ }
+
+ return false;
+}
+
+static void wevt_event_done(WEVT_LOG *log) {
+ if (log->provider) {
+ provider_release(log->provider);
+ log->provider = NULL;
+ }
+
+ if (log->hEvent) {
+ EvtClose(log->hEvent);
+ log->hEvent = NULL;
+ }
+
+ log->ops.channel.src = TXT_SOURCE_UNKNOWN;
+ log->ops.provider.src = TXT_SOURCE_UNKNOWN;
+ log->ops.computer.src = TXT_SOURCE_UNKNOWN;
+ log->ops.account.src = TXT_SOURCE_UNKNOWN;
+ log->ops.domain.src = TXT_SOURCE_UNKNOWN;
+ log->ops.sid.src = TXT_SOURCE_UNKNOWN;
+
+ log->ops.event.src = TXT_SOURCE_UNKNOWN;
+ log->ops.level.src = TXT_SOURCE_UNKNOWN;
+ log->ops.keywords.src = TXT_SOURCE_UNKNOWN;
+ log->ops.opcode.src = TXT_SOURCE_UNKNOWN;
+ log->ops.task.src = TXT_SOURCE_UNKNOWN;
+ log->ops.xml.src = TXT_SOURCE_UNKNOWN;
+
+ log->ops.channel.used = 0;
+ log->ops.provider.used = 0;
+ log->ops.computer.used = 0;
+ log->ops.account.used = 0;
+ log->ops.domain.used = 0;
+ log->ops.sid.used = 0;
+
+ log->ops.event.used = 0;
+ log->ops.level.used = 0;
+ log->ops.keywords.used = 0;
+ log->ops.opcode.used = 0;
+ log->ops.task.used = 0;
+ log->ops.xml.used = 0;
+
+ if(log->ops.event_data)
+ log->ops.event_data->len = 0;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// Query management
+
+bool wevt_query(WEVT_LOG *log, LPCWSTR channel, LPCWSTR query, EVT_QUERY_FLAGS direction) {
+ wevt_query_done(log);
+ log->log_stats.queries_count++;
+
+ EVT_HANDLE hQuery = EvtQuery(NULL, channel, query, EvtQueryChannelPath | (direction & (EvtQueryReverseDirection | EvtQueryForwardDirection)) | EvtQueryTolerateQueryErrors);
+ if (!hQuery) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtQuery() failed, query: %s | extended info: %s",
+ query2utf8(query), EvtGetExtendedStatus_utf8());
+
+ log->log_stats.queries_failed++;
+ return false;
+ }
+
+ log->hQuery = hQuery;
+ return true;
+}
+
+void wevt_query_done(WEVT_LOG *log) {
+ // close the last working hEvent
+ wevt_event_done(log);
+
+ // close all batched hEvents
+ for(DWORD i = log->batch.used; i < log->batch.size ;i++) {
+ if(log->batch.hEvents[i])
+ EvtClose(log->batch.hEvents[i]);
+
+ log->batch.hEvents[i] = NULL;
+ }
+ log->batch.used = 0;
+ log->batch.size = 0;
+
+ if (log->hQuery) {
+ EvtClose(log->hQuery);
+ log->hQuery = NULL;
+ }
+
+ log->query_stats.event_count = 0;
+ log->query_stats.failed_count = 0;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// Log management
+
+WEVT_LOG *wevt_openlog6(WEVT_QUERY_TYPE type) {
+ WEVT_LOG *log = callocz(1, sizeof(*log));
+ log->type = type;
+
+ // create the system render
+ log->hRenderSystemContext = EvtCreateRenderContext(0, NULL, EvtRenderContextSystem);
+ if (!log->hRenderSystemContext) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "EvtCreateRenderContext() on system context failed, extended info: %s",
+ EvtGetExtendedStatus_utf8());
+ goto cleanup;
+ }
+
+ if(type & WEVT_QUERY_EVENT_DATA) {
+ log->hRenderUserContext = EvtCreateRenderContext(0, NULL, EvtRenderContextUser);
+ if (!log->hRenderUserContext) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "EvtCreateRenderContext failed, on user context failed, extended info: %s",
+ EvtGetExtendedStatus_utf8());
+ goto cleanup;
+ }
+
+ log->ops.event_data = buffer_create(4096, NULL);
+ }
+
+ return log;
+
+cleanup:
+ wevt_closelog6(log);
+ return NULL;
+}
+
+void wevt_closelog6(WEVT_LOG *log) {
+ wevt_query_done(log);
+
+ if (log->hRenderSystemContext)
+ EvtClose(log->hRenderSystemContext);
+
+ if (log->hRenderUserContext)
+ EvtClose(log->hRenderUserContext);
+
+ wevt_variant_cleanup(&log->ops.raw.system);
+ wevt_variant_cleanup(&log->ops.raw.user);
+ txt_utf16_cleanup(&log->ops.unicode);
+ txt_utf8_cleanup(&log->ops.channel);
+ txt_utf8_cleanup(&log->ops.provider);
+ txt_utf8_cleanup(&log->ops.computer);
+ txt_utf8_cleanup(&log->ops.account);
+ txt_utf8_cleanup(&log->ops.domain);
+ txt_utf8_cleanup(&log->ops.sid);
+
+ txt_utf8_cleanup(&log->ops.event);
+ txt_utf8_cleanup(&log->ops.level);
+ txt_utf8_cleanup(&log->ops.keywords);
+ txt_utf8_cleanup(&log->ops.opcode);
+ txt_utf8_cleanup(&log->ops.task);
+ txt_utf8_cleanup(&log->ops.xml);
+
+ buffer_free(log->ops.event_data);
+
+ freez(log);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// Retention
+
+bool wevt_channel_retention(WEVT_LOG *log, const wchar_t *channel, const wchar_t *query, EVT_RETENTION *retention) {
+ bool ret = false;
+
+ // get the number of the oldest record in the log
+ // "EvtGetLogInfo()" does not work properly with "EvtLogOldestRecordNumber"
+ // we have to get it from the first EventRecordID
+
+ // query the eventlog
+ log->hQuery = EvtQuery(NULL, channel, query, EvtQueryChannelPath | EvtQueryForwardDirection | EvtQueryTolerateQueryErrors);
+ if (!log->hQuery) {
+ if (GetLastError() == ERROR_EVT_CHANNEL_NOT_FOUND)
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtQuery() for retention failed, channel '%s' not found, cannot get retention, extended info: %s",
+ channel2utf8(channel), EvtGetExtendedStatus_utf8());
+ else
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtQuery() for retention on channel '%s' failed, cannot get retention, extended info: %s",
+ channel2utf8(channel), EvtGetExtendedStatus_utf8());
+
+ goto cleanup;
+ }
+
+ if (!wevt_get_next_event(log, &retention->first_event))
+ goto cleanup;
+
+ if (!retention->first_event.id) {
+ // no data in the event log
+ retention->first_event = retention->last_event = WEVT_EVENT_EMPTY;
+ ret = true;
+ goto cleanup;
+ }
+ EvtClose(log->hQuery);
+
+ log->hQuery = EvtQuery(NULL, channel, query, EvtQueryChannelPath | EvtQueryReverseDirection | EvtQueryTolerateQueryErrors);
+ if (!log->hQuery) {
+ if (GetLastError() == ERROR_EVT_CHANNEL_NOT_FOUND)
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtQuery() for retention failed, channel '%s' not found, extended info: %s",
+ channel2utf8(channel), EvtGetExtendedStatus_utf8());
+ else
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtQuery() for retention on channel '%s' failed, extended info: %s",
+ channel2utf8(channel), EvtGetExtendedStatus_utf8());
+
+ goto cleanup;
+ }
+
+ if (!wevt_get_next_event(log, &retention->last_event) || retention->last_event.id == 0) {
+ // no data in eventlog
+ retention->last_event = retention->first_event;
+ }
+ retention->last_event.id += 1; // we should read the last record
+ ret = true;
+
+cleanup:
+ wevt_query_done(log);
+
+ if(ret) {
+ retention->entries = (channel && !query) ? retention->last_event.id - retention->first_event.id : 0;
+
+ if(retention->last_event.created_ns >= retention->first_event.created_ns)
+ retention->duration_ns = retention->last_event.created_ns - retention->first_event.created_ns;
+ else
+ retention->duration_ns = retention->first_event.created_ns - retention->last_event.created_ns;
+
+ retention->size_bytes = wevt_log_file_size(channel);
+ }
+ else
+ memset(retention, 0, sizeof(*retention));
+
+ return ret;
+}
+
+static uint64_t wevt_log_file_size(const wchar_t *channel) {
+ EVT_HANDLE hLog = NULL;
+ EVT_VARIANT evtVariant;
+ DWORD bufferUsed = 0;
+ uint64_t file_size = 0;
+
+ // Open the event log channel
+ hLog = EvtOpenLog(NULL, channel, EvtOpenChannelPath);
+ if (!hLog) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtOpenLog() on channel '%s' failed, extended info: %s",
+ channel2utf8(channel), EvtGetExtendedStatus_utf8());
+ goto cleanup;
+ }
+
+ // Get the file size of the log
+ if (!EvtGetLogInfo(hLog, EvtLogFileSize, sizeof(evtVariant), &evtVariant, &bufferUsed)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtGetLogInfo() on channel '%s' failed, extended info: %s",
+ channel2utf8(channel), EvtGetExtendedStatus_utf8());
+ goto cleanup;
+ }
+
+ // Extract the file size from the EVT_VARIANT structure
+ file_size = evtVariant.UInt64Val;
+
+cleanup:
+ if (hLog)
+ EvtClose(hLog);
+
+ return file_size;
+}
diff --git a/src/collectors/windows-events.plugin/windows-events-query.h b/src/collectors/windows-events.plugin/windows-events-query.h
new file mode 100644
index 000000000..3136b23df
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-query.h
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_WINDOWS_EVENTS_QUERY_H
+#define NETDATA_WINDOWS_EVENTS_QUERY_H
+
+#include "libnetdata/libnetdata.h"
+#include "windows-events.h"
+
+#define BATCH_NEXT_EVENT 500
+
+typedef struct wevt_event {
+ uint64_t id; // EventRecordId (unique and sequential per channel)
+ uint8_t version;
+ uint8_t level; // The severity of event
+ uint8_t opcode; // we receive this as 8bit, but providers use 32bit
+ uint16_t event_id; // This is the template that defines the message to be shown
+ uint16_t task;
+ uint16_t qualifiers;
+ uint32_t process_id;
+ uint32_t thread_id;
+ uint64_t keywords; // Categorization of the event
+ ND_UUID provider;
+ ND_UUID activity_id;
+ ND_UUID related_activity_id;
+ nsec_t created_ns;
+ WEVT_PROVIDER_PLATFORM platform;
+} WEVT_EVENT;
+
+#define WEVT_EVENT_EMPTY (WEVT_EVENT){ .id = 0, .created_ns = 0, }
+
+typedef struct {
+ EVT_VARIANT *data;
+ DWORD size;
+ DWORD used;
+ DWORD count;
+} WEVT_VARIANT;
+
+typedef struct {
+ WEVT_EVENT first_event;
+ WEVT_EVENT last_event;
+
+ uint64_t entries;
+ nsec_t duration_ns;
+ uint64_t size_bytes;
+} EVT_RETENTION;
+
+struct provider_meta_handle;
+
+typedef enum __attribute__((packed)) {
+ WEVT_QUERY_BASIC = (1 << 0),
+ WEVT_QUERY_EXTENDED = (1 << 1),
+ WEVT_QUERY_EVENT_DATA = (1 << 2),
+} WEVT_QUERY_TYPE;
+
+#define WEVT_QUERY_RETENTION WEVT_QUERY_BASIC
+#define WEVT_QUERY_NORMAL (WEVT_QUERY_BASIC | WEVT_QUERY_EXTENDED)
+#define WEVT_QUERY_FTS (WEVT_QUERY_BASIC | WEVT_QUERY_EXTENDED | WEVT_QUERY_EVENT_DATA)
+
+typedef struct wevt_log {
+ struct {
+ DWORD size;
+ DWORD used;
+ EVT_HANDLE hEvents[BATCH_NEXT_EVENT];
+ } batch;
+
+ EVT_HANDLE hEvent;
+ EVT_HANDLE hQuery;
+ EVT_HANDLE hRenderSystemContext;
+ EVT_HANDLE hRenderUserContext;
+ struct provider_meta_handle *provider;
+
+ WEVT_QUERY_TYPE type;
+
+ struct {
+ struct {
+ // temp buffer used for rendering event log messages
+ // never use directly
+ WEVT_VARIANT system;
+ WEVT_VARIANT user;
+ } raw;
+
+ // temp buffer used for fetching and converting UNICODE and UTF-8
+ // every string operation overwrites it, multiple times per event log entry
+ // it can be used within any function, for its own purposes,
+ // but never share between functions
+ TXT_UTF16 unicode;
+
+ // string attributes of the current event log entry
+ // valid until another event if fetched
+
+ // IMPORTANT:
+ // EVERY FIELD NEEDS ITS OWN BUFFER!
+ // the way facets work, all the field value pointers need to be valid
+ // until the entire row closes, so reusing a buffer for the same field
+ // actually copies the same value to all fields using the same buffer.
+
+ TXT_UTF8 channel;
+ TXT_UTF8 provider;
+ TXT_UTF8 computer;
+ TXT_UTF8 account;
+ TXT_UTF8 domain;
+ TXT_UTF8 sid;
+
+ TXT_UTF8 event; // the message to be shown to the user
+ TXT_UTF8 level;
+ TXT_UTF8 keywords;
+ TXT_UTF8 opcode;
+ TXT_UTF8 task;
+ TXT_UTF8 xml;
+
+ BUFFER *event_data;
+ } ops;
+
+ struct {
+ size_t event_count;
+ size_t failed_count;
+ } query_stats;
+
+ struct {
+ size_t queries_count;
+ size_t queries_failed;
+
+ size_t event_count;
+ size_t failed_count;
+ } log_stats;
+
+} WEVT_LOG;
+
+WEVT_LOG *wevt_openlog6(WEVT_QUERY_TYPE type);
+void wevt_closelog6(WEVT_LOG *log);
+
+bool wevt_channel_retention(WEVT_LOG *log, const wchar_t *channel, const wchar_t *query, EVT_RETENTION *retention);
+
+bool wevt_query(WEVT_LOG *log, LPCWSTR channel, LPCWSTR query, EVT_QUERY_FLAGS direction);
+void wevt_query_done(WEVT_LOG *log);
+
+bool wevt_get_next_event(WEVT_LOG *log, WEVT_EVENT *ev);
+
+bool EvtFormatMessage_utf16(
+ TXT_UTF16 *dst, EVT_HANDLE hMetadata, EVT_HANDLE hEvent, DWORD dwMessageId, EVT_FORMAT_MESSAGE_FLAGS flags);
+
+bool EvtFormatMessage_Event_utf8(TXT_UTF16 *tmp, struct provider_meta_handle *p, EVT_HANDLE hEvent, TXT_UTF8 *dst);
+bool EvtFormatMessage_Xml_utf8(TXT_UTF16 *tmp, struct provider_meta_handle *p, EVT_HANDLE hEvent, TXT_UTF8 *dst);
+
+void evt_variant_to_buffer(BUFFER *b, EVT_VARIANT *ev, const char *separator);
+
+static inline void wevt_variant_cleanup(WEVT_VARIANT *v) {
+ freez(v->data);
+}
+
+static inline void wevt_variant_resize(WEVT_VARIANT *v, size_t required_size) {
+ if(required_size < v->size)
+ return;
+
+ wevt_variant_cleanup(v);
+ v->size = txt_compute_new_size(v->size, required_size);
+ v->data = mallocz(v->size);
+}
+
+static inline void wevt_variant_count_from_used(WEVT_VARIANT *v) {
+ v->count = v->used / sizeof(*v->data);
+}
+
+static inline uint8_t wevt_field_get_uint8(EVT_VARIANT *ev) {
+ if((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeNull)
+ return 0;
+
+ fatal_assert((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeByte);
+ return ev->ByteVal;
+}
+
+static inline uint16_t wevt_field_get_uint16(EVT_VARIANT *ev) {
+ if((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeNull)
+ return 0;
+
+ fatal_assert((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeUInt16);
+ return ev->UInt16Val;
+}
+
+static inline uint32_t wevt_field_get_uint32(EVT_VARIANT *ev) {
+ if((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeNull)
+ return 0;
+
+ fatal_assert((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeUInt32);
+ return ev->UInt32Val;
+}
+
+static inline uint64_t wevt_field_get_uint64(EVT_VARIANT *ev) {
+ if((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeNull)
+ return 0;
+
+ fatal_assert((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeUInt64);
+ return ev->UInt64Val;
+}
+
+static inline uint64_t wevt_field_get_uint64_hex(EVT_VARIANT *ev) {
+ if((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeNull)
+ return 0;
+
+ fatal_assert((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeHexInt64);
+ return ev->UInt64Val;
+}
+
+static inline bool wevt_field_get_string_utf8(EVT_VARIANT *ev, TXT_UTF8 *dst) {
+ if((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeNull) {
+ txt_utf8_empty(dst);
+ return false;
+ }
+
+ fatal_assert((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeString);
+ return wchar_to_txt_utf8(dst, ev->StringVal, -1);
+}
+
+bool cached_sid_to_account_domain_sidstr(PSID sid, TXT_UTF8 *dst_account, TXT_UTF8 *dst_domain, TXT_UTF8 *dst_sid_str);
+static inline bool wevt_field_get_sid(EVT_VARIANT *ev, TXT_UTF8 *dst_account, TXT_UTF8 *dst_domain, TXT_UTF8 *dst_sid_str) {
+ if((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeNull) {
+ txt_utf8_empty(dst_account);
+ txt_utf8_empty(dst_domain);
+ txt_utf8_empty(dst_sid_str);
+ return false;
+ }
+
+ fatal_assert((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeSid);
+ return cached_sid_to_account_domain_sidstr(ev->SidVal, dst_account, dst_domain, dst_sid_str);
+}
+
+static inline uint64_t wevt_field_get_filetime_to_ns(EVT_VARIANT *ev) {
+ if((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeNull)
+ return 0;
+
+ fatal_assert((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeFileTime);
+ return os_windows_ulonglong_to_unix_epoch_ns(ev->FileTimeVal);
+}
+
+static inline bool wevt_GUID_to_ND_UUID(ND_UUID *nd_uuid, const GUID *guid) {
+ if(guid && sizeof(GUID) == sizeof(ND_UUID)) {
+ memcpy(nd_uuid->uuid, guid, sizeof(ND_UUID));
+ return true;
+ }
+ else {
+ *nd_uuid = UUID_ZERO;
+ return false;
+ }
+}
+
+static inline bool wevt_get_uuid_by_type(EVT_VARIANT *ev, ND_UUID *dst) {
+ if((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeNull) {
+ wevt_GUID_to_ND_UUID(dst, NULL);
+ return false;
+ }
+
+ fatal_assert((ev->Type & EVT_VARIANT_TYPE_MASK) == EvtVarTypeGuid);
+ return wevt_GUID_to_ND_UUID(dst, ev->GuidVal);
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/wes/defining-severity-levels
+static inline bool is_valid_provider_level(uint64_t level, bool strict) {
+ if(strict)
+ // when checking if the name is provider independent
+ return level >= 16 && level <= 255;
+ else
+ // when checking acceptable values in provider manifests
+ return level <= 255;
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/wes/defining-tasks-and-opcodes
+static inline bool is_valid_provider_opcode(uint64_t opcode, bool strict) {
+ if(strict)
+ // when checking if the name is provider independent
+ return opcode >= 10 && opcode <= 239;
+ else
+ // when checking acceptable values in provider manifests
+ return opcode <= 255;
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/wes/defining-tasks-and-opcodes
+static inline bool is_valid_provider_task(uint64_t task, bool strict) {
+ if(strict)
+ // when checking if the name is provider independent
+ return task > 0 && task <= 0xFFFF;
+ else
+ // when checking acceptable values in provider manifests
+ return task <= 0xFFFF;
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/wes/defining-keywords-used-to-classify-types-of-events
+static inline bool is_valid_provider_keyword(uint64_t keyword, bool strict) {
+ if(strict)
+ // when checking if the name is provider independent
+ return keyword > 0 && keyword <= 0x0000FFFFFFFFFFFF;
+ else
+ // when checking acceptable values in provider manifests
+ return true;
+}
+
+#endif //NETDATA_WINDOWS_EVENTS_QUERY_H
diff --git a/src/collectors/windows-events.plugin/windows-events-sources.c b/src/collectors/windows-events.plugin/windows-events-sources.c
new file mode 100644
index 000000000..b931ed059
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-sources.c
@@ -0,0 +1,644 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "windows-events.h"
+
+//struct {
+// const char *name;
+// const wchar_t *query;
+//} custom_queries[] = {
+// {
+// .name = "All-Administrative-Events",
+// .query = L"<QueryList>\n"
+// " <Query Id=\"0\" Path=\"Application\">\n"
+// " <Select Path=\"Application\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Security\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"System\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"HardwareEvents\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Internet Explorer\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Key Management Service\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-AppV-Client/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-AppV-Client/Virtual Applications\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-All-User-Install-Agent/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-AppHost/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Application Server-Applications/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-AppModel-Runtime/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-AppReadiness/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-AssignedAccess/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-AssignedAccessBroker/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Storage-ATAPort/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-BitLocker-DrivePreparationTool/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Client-Licensing-Platform/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-DataIntegrityScan/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-DataIntegrityScan/CrashRecovery\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-DSC/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Autopilot\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-DeviceSetupManager/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Dhcp-Client/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Dhcpv6-Client/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Diagnosis-Scripted/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Storage-Disk/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-DxgKrnl-Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-EDP-Application-Learning/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-EDP-Audit-Regular/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-EDP-Audit-TCB/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Client-License-Flexible-Platform/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-GenericRoaming/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Hyper-V-Guest-Drivers/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Hyper-V-Hypervisor-Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Hyper-V-VID-Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Kernel-EventTracing/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-KeyboardFilter/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-ModernDeployment-Diagnostics-Provider/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-ModernDeployment-Diagnostics-Provider/Autopilot\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-ModernDeployment-Diagnostics-Provider/Diagnostics\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-ModernDeployment-Diagnostics-Provider/ManagementService\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-MUI/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-PowerShell/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-PrintBRM/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-PrintService/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Provisioning-Diagnostics-Provider/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Provisioning-Diagnostics-Provider/AutoPilot\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Provisioning-Diagnostics-Provider/ManagementService\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-PushNotification-Platform/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-RemoteApp and Desktop Connections/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-RemoteAssistance/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-RemoteDesktopServices-RdpCoreTS/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-RetailDemo/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-SecurityMitigationsBroker/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-SmartCard-TPM-VCard-Module/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-SMBDirect/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-SMBWitnessClient/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Storage-Tiering/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Storage-ClassPnP/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Storage-Storport/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-ClientUSBDevices/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-LocalSessionManager/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-PnPDevices/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-Printers/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-RemoteConnectionManager/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-ServerUSBDevices/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Troubleshooting-Recommended/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-User Device Registration/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-VerifyHardwareSecurity/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-WindowsBackup/ActionCenter\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Microsoft-Windows-Workplace Join/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"OAlerts\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"OneApp_IGCC\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"OpenSSH/Admin\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"USER_ESRV_SVC_QUEENCREEK\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Visual Studio\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " <Select Path=\"Windows PowerShell\">*[System[(Level=1 or Level=2 or Level=3)]]</Select>\n"
+// " </Query>\n"
+// "</QueryList>",
+// },
+// {
+// .name = "All-Remote-Desktop-Services",
+// .query = L"<QueryList>\n"
+// " <Query Id=\"0\" Path=\"Microsoft-Rdms-UI/Admin\">\n"
+// " <Select Path=\"Microsoft-Rdms-UI/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Rdms-UI/Operational\">*</Select>\n"
+// " <Select Path=\"Remote-Desktop-Management-Service/Admin\">*</Select>\n"
+// " <Select Path=\"Remote-Desktop-Management-Service/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-SessionBroker-Client/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-SessionBroker-Client/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-RemoteConnectionManager/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-PnPDevices/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-PnPDevices/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-RemoteApp and Desktop Connections/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-RemoteApp and Desktop Connection Management/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-RemoteApp and Desktop Connection Management/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-SessionBroker/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-SessionBroker/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-TSV-VmHostAgent/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-TSV-VmHostAgent/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-ServerUSBDevices/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-ServerUSBDevices/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-LocalSessionManager/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-LocalSessionManager/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-ClientUSBDevices/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-ClientUSBDevices/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-RDPClient/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-Licensing/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-Licensing/Operational\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-Gateway/Admin\">*</Select>\n"
+// " <Select Path=\"Microsoft-Windows-TerminalServices-Gateway/Operational\">*</Select>\n"
+// " </Query>\n"
+// "</QueryList>",
+// },
+// {
+// .name = "All-Security-SPP",
+// .query = L"<QueryList>\n"
+// " <Query Id=\"0\" Path=\"Microsoft-Windows-HelloForBusiness/Operational\">\n"
+// " <Select Path=\"Microsoft-Windows-HelloForBusiness/Operational\">*[System[(Level&gt;5 )]]</Select>\n"
+// " </Query>\n"
+// "</QueryList>",
+// }
+//};
+
+ENUM_STR_MAP_DEFINE(WEVT_SOURCE_TYPE) = {
+ { .id = WEVTS_ALL, .name = WEVT_SOURCE_ALL_NAME },
+ { .id = WEVTS_ADMIN, .name = WEVT_SOURCE_ALL_ADMIN_NAME },
+ { .id = WEVTS_OPERATIONAL, .name = WEVT_SOURCE_ALL_OPERATIONAL_NAME },
+ { .id = WEVTS_ANALYTIC, .name = WEVT_SOURCE_ALL_ANALYTIC_NAME },
+ { .id = WEVTS_DEBUG, .name = WEVT_SOURCE_ALL_DEBUG_NAME },
+ { .id = WEVTS_WINDOWS, .name = WEVT_SOURCE_ALL_WINDOWS_NAME },
+ { .id = WEVTS_ENABLED, .name = WEVT_SOURCE_ALL_ENABLED_NAME },
+ { .id = WEVTS_DISABLED, .name = WEVT_SOURCE_ALL_DISABLED_NAME },
+ { .id = WEVTS_FORWARDED, .name = WEVT_SOURCE_ALL_FORWARDED_NAME },
+ { .id = WEVTS_CLASSIC, .name = WEVT_SOURCE_ALL_CLASSIC_NAME },
+ { .id = WEVTS_BACKUP_MODE, .name = WEVT_SOURCE_ALL_BACKUP_MODE_NAME },
+ { .id = WEVTS_OVERWRITE_MODE, .name = WEVT_SOURCE_ALL_OVERWRITE_MODE_NAME },
+ { .id = WEVTS_STOP_WHEN_FULL_MODE, .name = WEVT_SOURCE_ALL_STOP_WHEN_FULL_MODE_NAME },
+ { .id = WEVTS_RETAIN_AND_BACKUP_MODE, .name = WEVT_SOURCE_ALL_RETAIN_AND_BACKUP_MODE_NAME },
+
+ // terminator
+ { . id = 0, .name = NULL }
+};
+
+BITMAP_STR_DEFINE_FUNCTIONS(WEVT_SOURCE_TYPE, WEVTS_NONE, "");
+
+DICTIONARY *wevt_sources = NULL;
+DICTIONARY *used_hashes_registry = NULL;
+static usec_t wevt_session = 0;
+
+void wevt_sources_del_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
+ LOGS_QUERY_SOURCE *src = value;
+ freez((void *)src->fullname);
+ string_freez(src->source);
+
+ src->fullname = NULL;
+ src->source = NULL;
+}
+
+static bool wevt_sources_conflict_cb(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) {
+ LOGS_QUERY_SOURCE *src_old = old_value;
+ LOGS_QUERY_SOURCE *src_new = new_value;
+
+ bool ret = false;
+ if(src_new->last_scan_monotonic_ut > src_old->last_scan_monotonic_ut) {
+ src_old->last_scan_monotonic_ut = src_new->last_scan_monotonic_ut;
+
+ if (src_old->source != src_new->source) {
+ string_freez(src_old->source);
+ src_old->source = src_new->source;
+ src_new->source = NULL;
+ }
+ src_old->source_type = src_new->source_type;
+
+ src_old->msg_first_ut = src_new->msg_first_ut;
+ src_old->msg_last_ut = src_new->msg_last_ut;
+ src_old->msg_first_id = src_new->msg_first_id;
+ src_old->msg_last_id = src_new->msg_last_id;
+ src_old->entries = src_new->entries;
+ src_old->size = src_new->size;
+
+ ret = true;
+ }
+
+ freez((void *)src_new->fullname);
+ string_freez(src_new->source);
+ src_new->fullname = NULL;
+ src_new->source = NULL;
+
+ return ret;
+}
+
+void wevt_sources_init(void) {
+ wevt_session = now_realtime_usec();
+
+ used_hashes_registry = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
+
+ wevt_sources = dictionary_create_advanced(DICT_OPTION_FIXED_SIZE | DICT_OPTION_DONT_OVERWRITE_VALUE,
+ NULL, sizeof(LOGS_QUERY_SOURCE));
+
+ dictionary_register_delete_callback(wevt_sources, wevt_sources_del_cb, NULL);
+ dictionary_register_conflict_callback(wevt_sources, wevt_sources_conflict_cb, NULL);
+}
+
+void buffer_json_wevt_versions(BUFFER *wb __maybe_unused) {
+ buffer_json_member_add_object(wb, "versions");
+ {
+ buffer_json_member_add_uint64(wb, "sources",
+ wevt_session + dictionary_version(wevt_sources));
+ }
+ buffer_json_object_close(wb);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+int wevt_sources_dict_items_backward_compar(const void *a, const void *b) {
+ const DICTIONARY_ITEM **da = (const DICTIONARY_ITEM **)a, **db = (const DICTIONARY_ITEM **)b;
+ LOGS_QUERY_SOURCE *sa = dictionary_acquired_item_value(*da);
+ LOGS_QUERY_SOURCE *sb = dictionary_acquired_item_value(*db);
+
+ // compare the last message timestamps
+ if(sa->msg_last_ut < sb->msg_last_ut)
+ return 1;
+
+ if(sa->msg_last_ut > sb->msg_last_ut)
+ return -1;
+
+ // compare the first message timestamps
+ if(sa->msg_first_ut < sb->msg_first_ut)
+ return 1;
+
+ if(sa->msg_first_ut > sb->msg_first_ut)
+ return -1;
+
+ return 0;
+}
+
+int wevt_sources_dict_items_forward_compar(const void *a, const void *b) {
+ return -wevt_sources_dict_items_backward_compar(a, b);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+typedef enum {
+ wevt_source_type_internal,
+ wevt_source_type_provider,
+ wevt_source_type_channel,
+} wevt_source_type;
+
+struct wevt_source {
+ wevt_source_type type;
+ usec_t first_ut;
+ usec_t last_ut;
+ size_t count;
+ size_t entries;
+ uint64_t size;
+};
+
+static int wevt_source_to_json_array_cb(const DICTIONARY_ITEM *item, void *entry, void *data) {
+ const struct wevt_source *s = entry;
+ BUFFER *wb = data;
+
+ const char *name = dictionary_acquired_item_name(item);
+
+ if(s->count == 1 && strncmp(name, WEVT_SOURCE_ALL_OF_PROVIDER_PREFIX, sizeof(WEVT_SOURCE_ALL_OF_PROVIDER_PREFIX) - 1) == 0)
+ // do not include "All-Of-X" when there is only 1 channel
+ return 0;
+
+ bool default_selected = (s->type == wevt_source_type_channel);
+ if(default_selected && (strcmp(name, "NetdataWEL") == 0 || strcmp(name, "Netdata/Access") == 0))
+ // do not select Netdata Access logs by default
+ default_selected = false;
+
+ buffer_json_add_array_item_object(wb);
+ {
+ char size_for_humans[128];
+ size_snprintf(size_for_humans, sizeof(size_for_humans), s->size, "B", false);
+
+ char duration_for_humans[128];
+ duration_snprintf(duration_for_humans, sizeof(duration_for_humans),
+ (time_t)((s->last_ut - s->first_ut) / USEC_PER_SEC), "s", true);
+
+ char entries_for_humans[128];
+ entries_snprintf(entries_for_humans, sizeof(entries_for_humans), s->entries, "", false);
+
+ char info[1024];
+ snprintfz(info, sizeof(info), "%zu channel%s, with a total size of %s, covering %s%s%s%s",
+ s->count, s->count > 1 ? "s":"", size_for_humans, duration_for_humans,
+ s->entries ? ", having " : "", s->entries ? entries_for_humans : "", s->entries ? " entries" : "");
+
+ buffer_json_member_add_string(wb, "id", name);
+ buffer_json_member_add_string(wb, "name", name);
+ buffer_json_member_add_string(wb, "pill", size_for_humans);
+ buffer_json_member_add_string(wb, "info", info);
+ buffer_json_member_add_boolean(wb, "default_selected", default_selected);
+ }
+ buffer_json_object_close(wb); // options object
+
+ return 1;
+}
+
+static bool wevt_source_merge_sizes(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value , void *data __maybe_unused) {
+ struct wevt_source *old_v = old_value;
+ const struct wevt_source *new_v = new_value;
+
+ old_v->count += new_v->count;
+ old_v->size += new_v->size;
+ old_v->entries += new_v->entries;
+
+ if(new_v->first_ut && new_v->first_ut < old_v->first_ut)
+ old_v->first_ut = new_v->first_ut;
+
+ if(new_v->last_ut && new_v->last_ut > old_v->last_ut)
+ old_v->last_ut = new_v->last_ut;
+
+ return false;
+}
+
+void wevt_sources_to_json_array(BUFFER *wb) {
+ DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_NAME_LINK_DONT_CLONE|DICT_OPTION_DONT_OVERWRITE_VALUE);
+ dictionary_register_conflict_callback(dict, wevt_source_merge_sizes, NULL);
+
+ struct wevt_source t = { 0 };
+
+ LOGS_QUERY_SOURCE *src;
+ dfe_start_read(wevt_sources, src) {
+ t.first_ut = src->msg_first_ut;
+ t.last_ut = src->msg_last_ut;
+ t.count = 1;
+ t.size = src->size;
+ t.entries = src->entries;
+
+ src->source_type |= WEVTS_ALL;
+ t.type = wevt_source_type_internal;
+ for(size_t i = 0; WEVT_SOURCE_TYPE_names[i].name ;i++) {
+ if(src->source_type & WEVT_SOURCE_TYPE_names[i].id)
+ dictionary_set(dict, WEVT_SOURCE_TYPE_names[i].name, &t, sizeof(t));
+ }
+
+ if(src->provider) {
+ t.type = wevt_source_type_provider;
+ dictionary_set(dict, string2str(src->provider), &t, sizeof(t));
+ }
+
+ if(src->source) {
+ t.type = wevt_source_type_channel;
+ dictionary_set(dict, string2str(src->source), &t, sizeof(t));
+ }
+ }
+ dfe_done(jf);
+
+ dictionary_sorted_walkthrough_read(dict, wevt_source_to_json_array_cb, wb);
+}
+
+static bool ndEvtGetChannelConfigProperty(EVT_HANDLE hChannelConfig, WEVT_VARIANT *pr, EVT_CHANNEL_CONFIG_PROPERTY_ID id) {
+ if (!EvtGetChannelConfigProperty(hChannelConfig, id, 0, pr->size, pr->data, &pr->used)) {
+ DWORD status = GetLastError();
+ if (ERROR_INSUFFICIENT_BUFFER == status) {
+ wevt_variant_resize(pr, pr->used);
+ if(!EvtGetChannelConfigProperty(hChannelConfig, id, 0, pr->size, pr->data, &pr->used)) {
+ pr->used = 0;
+ pr->count = 0;
+ return false;
+ }
+ }
+ }
+
+ wevt_variant_count_from_used(pr);
+ return true;
+}
+
+WEVT_SOURCE_TYPE categorize_channel(const wchar_t *channel_path, const char **provider, WEVT_VARIANT *property) {
+ EVT_HANDLE hChannelConfig = NULL;
+ WEVT_SOURCE_TYPE result = WEVTS_ALL;
+
+ // Open the channel configuration
+ hChannelConfig = EvtOpenChannelConfig(NULL, channel_path, 0);
+ if (!hChannelConfig)
+ goto cleanup;
+
+ if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelConfigType) &
+ property->count &&
+ property->data[0].Type == EvtVarTypeUInt32) {
+ switch (property->data[0].UInt32Val) {
+ case EvtChannelTypeAdmin:
+ result |= WEVTS_ADMIN;
+ break;
+
+ case EvtChannelTypeOperational:
+ result |= WEVTS_OPERATIONAL;
+ break;
+
+ case EvtChannelTypeAnalytic:
+ result |= WEVTS_ANALYTIC;
+ break;
+
+ case EvtChannelTypeDebug:
+ result |= WEVTS_DEBUG;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelConfigClassicEventlog) &&
+ property->count &&
+ property->data[0].Type == EvtVarTypeBoolean &&
+ property->data[0].BooleanVal)
+ result |= WEVTS_CLASSIC;
+
+ if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelConfigOwningPublisher) &&
+ property->count &&
+ property->data[0].Type == EvtVarTypeString) {
+ *provider = provider2utf8(property->data[0].StringVal);
+ if(wcscasecmp(property->data[0].StringVal, L"Microsoft-Windows-EventCollector") == 0)
+ result |= WEVTS_FORWARDED;
+ }
+ else
+ *provider = NULL;
+
+ if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelConfigEnabled) &&
+ property->count &&
+ property->data[0].Type == EvtVarTypeBoolean) {
+ if(property->data[0].BooleanVal)
+ result |= WEVTS_ENABLED;
+ else
+ result |= WEVTS_DISABLED;
+ }
+
+ bool got_retention = false;
+ bool retained = false;
+ if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelLoggingConfigRetention) &&
+ property->count &&
+ property->data[0].Type == EvtVarTypeBoolean) {
+ got_retention = true;
+ retained = property->data[0].BooleanVal;
+ }
+
+ bool got_auto_backup = false;
+ bool auto_backup = false;
+ if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelLoggingConfigAutoBackup) &&
+ property->count &&
+ property->data[0].Type == EvtVarTypeBoolean) {
+ got_auto_backup = true;
+ auto_backup = property->data[0].BooleanVal;
+ }
+
+ if(got_retention && got_auto_backup) {
+ if(!retained) {
+ if(auto_backup)
+ result |= WEVTS_BACKUP_MODE;
+ else
+ result |= WEVTS_OVERWRITE_MODE;
+ }
+ else {
+ if(auto_backup)
+ result |= WEVTS_STOP_WHEN_FULL_MODE;
+ else
+ result |= WEVTS_RETAIN_AND_BACKUP_MODE;
+ }
+ }
+
+cleanup:
+ if (hChannelConfig)
+ EvtClose(hChannelConfig);
+
+ return result;
+}
+
+void wevt_sources_scan(void) {
+ static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
+ LPWSTR channel = NULL;
+ EVT_HANDLE hChannelEnum = NULL;
+
+ if(spinlock_trylock(&spinlock)) {
+ const usec_t started_ut = now_monotonic_usec();
+
+ WEVT_VARIANT property = { 0 };
+ DWORD dwChannelBufferSize = 0;
+ DWORD dwChannelBufferUsed = 0;
+ DWORD status = ERROR_SUCCESS;
+
+ // Open a handle to enumerate the event channels
+ hChannelEnum = EvtOpenChannelEnum(NULL, 0);
+ if (!hChannelEnum) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "WINDOWS EVENTS: EvtOpenChannelEnum() failed with %" PRIu64 "\n",
+ (uint64_t)GetLastError());
+ goto cleanup;
+ }
+
+ WEVT_LOG *log = wevt_openlog6(WEVT_QUERY_RETENTION);
+ if(!log) goto cleanup;
+
+ while (true) {
+ if (!EvtNextChannelPath(hChannelEnum, dwChannelBufferSize, channel, &dwChannelBufferUsed)) {
+ status = GetLastError();
+ if (status == ERROR_NO_MORE_ITEMS)
+ break; // No more channels
+ else if (status == ERROR_INSUFFICIENT_BUFFER) {
+ dwChannelBufferSize = dwChannelBufferUsed;
+ freez(channel);
+ channel = mallocz(dwChannelBufferSize * sizeof(WCHAR));
+ continue;
+ } else {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "WINDOWS EVENTS: EvtNextChannelPath() failed\n");
+ break;
+ }
+ }
+
+ EVT_RETENTION retention;
+ if(!wevt_channel_retention(log, channel, NULL, &retention))
+ continue;
+
+ LOGS_QUERY_SOURCE *found = dictionary_get(wevt_sources, channel2utf8(channel));
+ if(found) {
+ // we just need to update its retention
+
+ found->last_scan_monotonic_ut = now_monotonic_usec();
+ found->msg_first_id = retention.first_event.id;
+ found->msg_last_id = retention.last_event.id;
+ found->msg_first_ut = retention.first_event.created_ns / NSEC_PER_USEC;
+ found->msg_last_ut = retention.last_event.created_ns / NSEC_PER_USEC;
+ found->size = retention.size_bytes;
+ continue;
+ }
+
+ const char *name = channel2utf8(channel);
+ const char *fullname = strdupz(name);
+ const char *provider;
+
+ WEVT_SOURCE_TYPE sources = categorize_channel(channel, &provider, &property);
+ char *slash = strchr(name, '/');
+ if(slash) *slash = '\0';
+
+ if(strcasecmp(name, "Application") == 0)
+ sources |= WEVTS_WINDOWS;
+ if(strcasecmp(name, "Security") == 0)
+ sources |= WEVTS_WINDOWS;
+ if(strcasecmp(name, "Setup") == 0)
+ sources |= WEVTS_WINDOWS;
+ if(strcasecmp(name, "System") == 0)
+ sources |= WEVTS_WINDOWS;
+
+ LOGS_QUERY_SOURCE src = {
+ .entries = retention.entries,
+ .fullname = fullname,
+ .fullname_len = strlen(fullname),
+ .last_scan_monotonic_ut = now_monotonic_usec(),
+ .msg_first_id = retention.first_event.id,
+ .msg_last_id = retention.last_event.id,
+ .msg_first_ut = retention.first_event.created_ns / NSEC_PER_USEC,
+ .msg_last_ut = retention.last_event.created_ns / NSEC_PER_USEC,
+ .size = retention.size_bytes,
+ .source_type = sources,
+ .source = string_strdupz(fullname),
+ };
+
+ if(strncmp(fullname, "Netdata", 7) == 0)
+ // WEL based providers of Netdata are named NetdataX
+ provider = "Netdata";
+
+ if(provider && *provider) {
+ char buf[sizeof(WEVT_SOURCE_ALL_OF_PROVIDER_PREFIX) + strlen(provider)]; // sizeof() includes terminator
+ snprintf(buf, sizeof(buf), WEVT_SOURCE_ALL_OF_PROVIDER_PREFIX "%s", provider);
+
+ if(trim_all(buf) != NULL) {
+ for (size_t i = 0; i < sizeof(buf) - 1; i++) {
+ // remove character that may interfere with our parsing
+ if (isspace((uint8_t) buf[i]) || buf[i] == '%' || buf[i] == '+' || buf[i] == '|' || buf[i] == ':')
+ buf[i] = '_';
+ }
+ src.provider = string_strdupz(buf);
+ }
+ }
+
+ dictionary_set(wevt_sources, src.fullname, &src, sizeof(src));
+ }
+
+// // add custom queries
+// for(size_t i = 0; i < sizeof(custom_queries) / sizeof(custom_queries[0]) ;i++) {
+// EVT_RETENTION retention;
+// if(!wevt_channel_retention(log, NULL, custom_queries[i].query, &retention))
+// continue;
+//
+// LOGS_QUERY_SOURCE src = {
+// .entries = 0,
+// .fullname = strdupz(custom_queries[i].name),
+// .fullname_len = strlen(custom_queries[i].name),
+// .last_scan_monotonic_ut = now_monotonic_usec(),
+// .msg_first_id = retention.first_event.id,
+// .msg_last_id = retention.last_event.id,
+// .msg_first_ut = retention.first_event.created_ns / NSEC_PER_USEC,
+// .msg_last_ut = retention.last_event.created_ns / NSEC_PER_USEC,
+// .size = retention.size_bytes,
+// .source_type = WEVTS_ALL,
+// .source = string_strdupz(custom_queries[i].name),
+// };
+//
+// dictionary_set(wevt_sources, src.fullname, &src, sizeof(src));
+// }
+//
+ wevt_closelog6(log);
+
+ LOGS_QUERY_SOURCE *src;
+ dfe_start_write(wevt_sources, src)
+ {
+ if(src->last_scan_monotonic_ut < started_ut) {
+ src->msg_first_id = 0;
+ src->msg_last_id = 0;
+ src->msg_first_ut = 0;
+ src->msg_last_ut = 0;
+ src->size = 0;
+ dictionary_del(wevt_sources, src->fullname);
+ }
+ }
+ dfe_done(src);
+ dictionary_garbage_collect(wevt_sources);
+
+ spinlock_unlock(&spinlock);
+
+ wevt_variant_cleanup(&property);
+ }
+
+cleanup:
+ freez(channel);
+ EvtClose(hChannelEnum);
+}
diff --git a/src/collectors/windows-events.plugin/windows-events-sources.h b/src/collectors/windows-events.plugin/windows-events-sources.h
new file mode 100644
index 000000000..4ad4880d7
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-sources.h
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_WINDOWS_EVENTS_SOURCES_H
+#define NETDATA_WINDOWS_EVENTS_SOURCES_H
+
+#include "libnetdata/libnetdata.h"
+
+typedef enum {
+ WEVTS_NONE = 0,
+ WEVTS_ALL = (1 << 0),
+ WEVTS_ADMIN = (1 << 1),
+ WEVTS_OPERATIONAL = (1 << 2),
+ WEVTS_ANALYTIC = (1 << 3),
+ WEVTS_DEBUG = (1 << 4),
+ WEVTS_WINDOWS = (1 << 5),
+ WEVTS_ENABLED = (1 << 6),
+ WEVTS_DISABLED = (1 << 7),
+ WEVTS_FORWARDED = (1 << 8),
+ WEVTS_CLASSIC = (1 << 9),
+ WEVTS_BACKUP_MODE = (1 << 10),
+ WEVTS_OVERWRITE_MODE = (1 << 11),
+ WEVTS_STOP_WHEN_FULL_MODE = (1 << 12),
+ WEVTS_RETAIN_AND_BACKUP_MODE = (1 << 13),
+} WEVT_SOURCE_TYPE;
+
+BITMAP_STR_DEFINE_FUNCTIONS_EXTERN(WEVT_SOURCE_TYPE)
+
+#define WEVT_SOURCE_ALL_NAME "All"
+#define WEVT_SOURCE_ALL_ADMIN_NAME "All-Admin"
+#define WEVT_SOURCE_ALL_OPERATIONAL_NAME "All-Operational"
+#define WEVT_SOURCE_ALL_ANALYTIC_NAME "All-Analytic"
+#define WEVT_SOURCE_ALL_DEBUG_NAME "All-Debug"
+#define WEVT_SOURCE_ALL_WINDOWS_NAME "All-Windows"
+#define WEVT_SOURCE_ALL_ENABLED_NAME "All-Enabled"
+#define WEVT_SOURCE_ALL_DISABLED_NAME "All-Disabled"
+#define WEVT_SOURCE_ALL_FORWARDED_NAME "All-Forwarded"
+#define WEVT_SOURCE_ALL_CLASSIC_NAME "All-Classic"
+#define WEVT_SOURCE_ALL_BACKUP_MODE_NAME "All-In-Backup-Mode"
+#define WEVT_SOURCE_ALL_OVERWRITE_MODE_NAME "All-In-Overwrite-Mode"
+#define WEVT_SOURCE_ALL_STOP_WHEN_FULL_MODE_NAME "All-In-StopWhenFull-Mode"
+#define WEVT_SOURCE_ALL_RETAIN_AND_BACKUP_MODE_NAME "All-In-RetainAndBackup-Mode"
+
+#define WEVT_SOURCE_ALL_OF_PROVIDER_PREFIX "All-Of-"
+
+typedef struct {
+ const char *fullname;
+ size_t fullname_len;
+
+ const wchar_t *custom_query;
+
+ STRING *source;
+ STRING *provider;
+ WEVT_SOURCE_TYPE source_type;
+ usec_t msg_first_ut;
+ usec_t msg_last_ut;
+ size_t size;
+
+ usec_t last_scan_monotonic_ut;
+
+ uint64_t msg_first_id;
+ uint64_t msg_last_id;
+ uint64_t entries;
+} LOGS_QUERY_SOURCE;
+
+extern DICTIONARY *wevt_sources;
+extern DICTIONARY *used_hashes_registry;
+
+void wevt_sources_init(void);
+void wevt_sources_scan(void);
+void buffer_json_wevt_versions(BUFFER *wb);
+
+void wevt_sources_to_json_array(BUFFER *wb);
+WEVT_SOURCE_TYPE wevt_internal_source_type(const char *value);
+
+int wevt_sources_dict_items_backward_compar(const void *a, const void *b);
+int wevt_sources_dict_items_forward_compar(const void *a, const void *b);
+
+#endif //NETDATA_WINDOWS_EVENTS_SOURCES_H
diff --git a/src/collectors/windows-events.plugin/windows-events-unicode.c b/src/collectors/windows-events.plugin/windows-events-unicode.c
new file mode 100644
index 000000000..81da31107
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-unicode.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "windows-events-unicode.h"
+
+inline void utf82unicode(wchar_t *dst, size_t dst_size, const char *src) {
+ if (src) {
+ // Convert from UTF-8 to wide char (UTF-16)
+ if (utf8_to_utf16(dst, dst_size, src, -1) == 0)
+ wcsncpy(dst, L"[failed conv.]", dst_size - 1);
+ }
+ else
+ wcsncpy(dst, L"[null]", dst_size - 1);
+}
+
+inline void unicode2utf8(char *dst, size_t dst_size, const wchar_t *src) {
+ if (src) {
+ if(WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, (int)dst_size, NULL, NULL) == 0)
+ strncpyz(dst, "[failed conv.]", dst_size - 1);
+ }
+ else
+ strncpyz(dst, "[null]", dst_size - 1);
+}
+
+wchar_t *channel2unicode(const char *utf8str) {
+ static __thread wchar_t buffer[1024];
+ utf82unicode(buffer, _countof(buffer), utf8str);
+ return buffer;
+}
+
+char *channel2utf8(const wchar_t *channel) {
+ static __thread char buffer[1024];
+ unicode2utf8(buffer, sizeof(buffer), channel);
+ return buffer;
+}
+
+char *query2utf8(const wchar_t *query) {
+ static __thread char buffer[16384];
+ unicode2utf8(buffer, sizeof(buffer), query);
+ return buffer;
+}
+
+char *provider2utf8(const wchar_t *provider) {
+ static __thread char buffer[256];
+ unicode2utf8(buffer, sizeof(buffer), provider);
+ return buffer;
+}
diff --git a/src/collectors/windows-events.plugin/windows-events-unicode.h b/src/collectors/windows-events.plugin/windows-events-unicode.h
new file mode 100644
index 000000000..e932bb5df
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-unicode.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_WINDOWS_EVENTS_UNICODE_H
+#define NETDATA_WINDOWS_EVENTS_UNICODE_H
+
+#include "libnetdata/libnetdata.h"
+
+#define WINEVENT_NAME_KEYWORDS_SEPARATOR ", "
+static inline void txt_utf8_add_keywords_separator_if_needed(TXT_UTF8 *dst) {
+ if(dst->used > 1)
+ txt_utf8_append(dst, WINEVENT_NAME_KEYWORDS_SEPARATOR, sizeof(WINEVENT_NAME_KEYWORDS_SEPARATOR) - 1);
+}
+
+static inline void txt_utf8_set_numeric_if_empty(TXT_UTF8 *dst, const char *prefix, size_t len, uint64_t value) {
+ if(dst->used <= 1) {
+ txt_utf8_resize(dst, len + UINT64_MAX_LENGTH + 1, false);
+ memcpy(dst->data, prefix, len);
+ dst->used = len + print_uint64(&dst->data[len], value) + 1;
+ }
+}
+
+static inline void txt_utf8_set_hex_if_empty(TXT_UTF8 *dst, const char *prefix, size_t len, uint64_t value) {
+ if(dst->used <= 1) {
+ txt_utf8_resize(dst, len + UINT64_HEX_MAX_LENGTH + 1, false);
+ memcpy(dst->data, prefix, len);
+ dst->used = len + print_uint64_hex_full(&dst->data[len], value) + 1;
+ }
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// conversions
+
+void unicode2utf8(char *dst, size_t dst_size, const wchar_t *src);
+void utf82unicode(wchar_t *dst, size_t dst_size, const char *src);
+
+char *channel2utf8(const wchar_t *channel);
+wchar_t *channel2unicode(const char *utf8str);
+
+char *query2utf8(const wchar_t *query);
+char *provider2utf8(const wchar_t *provider);
+
+#endif //NETDATA_WINDOWS_EVENTS_UNICODE_H
diff --git a/src/collectors/windows-events.plugin/windows-events-xml.c b/src/collectors/windows-events.plugin/windows-events-xml.c
new file mode 100644
index 000000000..931ea6c54
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-xml.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "windows-events-xml.h"
+
+#include <string.h>
+#include <stdio.h>
+
+#define INDENT_STEP 2
+#define A_LOT_OF_SPACES " "
+
+// Helper: Add indentation
+static inline void buffer_add_xml_indent(BUFFER *buffer, const int level) {
+ size_t total_spaces = (size_t)level * INDENT_STEP;
+ const size_t step = sizeof(A_LOT_OF_SPACES) - 1;
+ while (total_spaces > 0) {
+ const size_t spaces_to_add = (total_spaces > step) ? step : total_spaces;
+ buffer_fast_strcat(buffer, A_LOT_OF_SPACES, spaces_to_add);
+ total_spaces -= spaces_to_add;
+ }
+}
+
+const char *append_the_rest(BUFFER *buffer, const char *xml, const char *end) {
+ if(xml >= end) return end;
+ buffer_fast_strcat(buffer, xml, end - xml);
+ return end;
+}
+
+static const char *parse_node(BUFFER *buffer, const char *xml, const char *end, int level);
+
+// Helper: Parse the value (between > and <) and return the next position to parse
+const char *parse_value_and_closing_tag(BUFFER *buffer, const char *xml, const char *end, int level) {
+ const char *start = xml;
+ bool has_subnodes = false;
+
+ // const char *tag_start = NULL, *tag_end = NULL;
+ while (xml < end) {
+ if(*xml == '<') {
+ if(xml + 1 < end && *(xml + 1) == '/') {
+ // a closing tag
+ xml += 2;
+
+// tag_start = xml;
+
+ while(xml < end && *xml != '>')
+ xml++;
+
+// tag_end = xml;
+
+ if(xml < end && *xml == '>')
+ xml++;
+
+ if(has_subnodes) {
+ buffer_putc(buffer, '\n');
+ buffer_add_xml_indent(buffer, level);
+ }
+
+ buffer_fast_strcat(buffer, start, xml - start);
+ return xml;
+ }
+ else {
+ // an opening tag
+ buffer_fast_strcat(buffer, start, xml - start);
+ xml = start = parse_node(buffer, xml, end, level + 1);
+ while(xml < end && isspace((uint8_t)*xml))
+ xml++;
+ has_subnodes = true;
+ }
+ }
+ else
+ xml++;
+ }
+
+ return append_the_rest(buffer, start, end);
+}
+
+// Parse a field value and return the next position to parse
+const char *parse_field_value(BUFFER *buffer, const char *xml, const char *end) {
+ const char quote = *xml;
+
+ if(quote != '"' && quote != '\'')
+ return append_the_rest(buffer, xml, end);
+
+ const char *start = xml++;
+
+ while (xml < end && *xml != quote) {
+ if (*xml == '\\') {
+ xml++; // Skip escape character
+
+ if(xml < end)
+ xml++;
+
+ continue;
+ }
+
+ xml++;
+ }
+
+ if(xml < end && *xml == quote) {
+ xml++; // Move past the closing quote
+ buffer_fast_strcat(buffer, start, xml - start);
+ return xml;
+ }
+
+ return append_the_rest(buffer, start, end);
+}
+
+// Parse a field name and return the next position to parse
+const char *parse_field(BUFFER *buffer, const char *xml, const char *end) {
+ while(isspace((uint8_t)*xml) && xml < end) xml++;
+
+ const char *start = xml;
+
+ while (*xml != '=' && xml < end)
+ xml++;
+
+ // Append the field name
+ buffer_fast_strcat(buffer, start, xml - start);
+
+ if(xml < end && *xml == '=') {
+ xml++;
+
+ buffer_putc(buffer, '=');
+
+ if(xml < end && (*xml == '"' || *xml == '\''))
+ xml = parse_field_value(buffer, xml, end);
+
+ return xml; // Return the next character to parse
+ }
+
+ return append_the_rest(buffer, start, end);
+}
+
+// Parse a node (handles fields and subnodes) and return the next position to parse
+static inline const char *parse_node(BUFFER *buffer, const char *xml, const char *end, int level) {
+ if(*xml != '<')
+ return append_the_rest(buffer, xml, end);
+
+ const char *start = xml++; // skip the <
+
+ buffer_putc(buffer, '\n');
+ buffer_add_xml_indent(buffer, level);
+
+ // skip spaces before the tag name
+ while(xml < end && isspace((uint8_t)*xml)) xml++;
+
+ // Parse the tag name
+// const char *tag_start = xml, *tag_end = NULL;
+ while (xml < end && *xml != '>' && *xml != '/') {
+ xml++;
+
+ if(xml < end && isspace((uint8_t)*xml)) {
+ xml++;
+// tag_end = xml;
+
+ while(xml < end && isspace((uint8_t)*xml))
+ xml++;
+
+ if(xml < end && *xml == '/') {
+ // an opening tag that is self-closing
+ xml++;
+ if(xml < end && *xml == '>') {
+ xml++;
+ buffer_fast_strcat(buffer, start, xml - start);
+ return xml;
+ }
+ else
+ return append_the_rest(buffer, start, end);
+ }
+ else if(xml < end && *xml == '>') {
+ // the end of an opening tag
+ xml++;
+ buffer_fast_strcat(buffer, start, xml - start);
+ return parse_value_and_closing_tag(buffer, xml, end, level);
+ }
+ else {
+ buffer_fast_strcat(buffer, start, xml - start);
+ xml = start = parse_field(buffer, xml, end);
+ while(xml < end && isspace((uint8_t)*xml))
+ xml++;
+ }
+ }
+ }
+
+ bool self_closing_tag = false;
+ if(xml < end && *xml == '/') {
+ self_closing_tag = true;
+ xml++;
+ }
+
+ if(xml < end && *xml == '>') {
+ xml++;
+ buffer_fast_strcat(buffer, start, xml - start);
+
+ if(self_closing_tag)
+ return xml;
+
+ return parse_value_and_closing_tag(buffer, xml, end, level);
+ }
+
+ return append_the_rest(buffer, start, end);
+}
+
+static inline void buffer_pretty_print_xml_object(BUFFER *buffer, const char *xml, const char *end) {
+ while(xml < end) {
+ while(xml < end && isspace((uint8_t)*xml))
+ xml++;
+
+ if(xml < end && *xml == '<')
+ xml = parse_node(buffer, xml, end, 1);
+ else {
+ append_the_rest(buffer, xml, end);
+ return;
+ }
+ }
+}
+
+void buffer_pretty_print_xml(BUFFER *buffer, const char *xml, size_t xml_len) {
+ const char *end = xml + xml_len;
+ buffer_pretty_print_xml_object(buffer, xml, end);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+bool buffer_extract_and_print_xml_with_cb(BUFFER *buffer, const char *xml, size_t xml_len, const char *prefix, const char *keys[],
+ void (*cb)(BUFFER *, const char *, const char *, const char *)) {
+ if(!keys || !*keys[0]) {
+ buffer_pretty_print_xml(buffer, xml, xml_len);
+ return true;
+ }
+
+ const char *start = xml, *end = NULL;
+ for(size_t k = 0; keys[k] ; k++) {
+ if(!*keys[k]) continue;
+
+ size_t klen = strlen(keys[k]);
+ char tag_open[klen + 2];
+ tag_open[0] = '<';
+ strcpy(&tag_open[1], keys[k]);
+ tag_open[klen + 1] = '\0';
+
+ const char *new_start = strstr(start, tag_open);
+ if(!new_start)
+ return false;
+
+ start = new_start + klen + 1;
+
+ if(*start != '>' && !isspace((uint8_t)*start))
+ return false;
+
+ if(*start != '>') {
+ start = strchr(start, '>');
+ if(!start) return false;
+ }
+ start++; // skip the >
+
+ char tag_close[klen + 4];
+ tag_close[0] = '<';
+ tag_close[1] = '/';
+ strcpy(&tag_close[2], keys[k]);
+ tag_close[klen + 2] = '>';
+ tag_close[klen + 3] = '\0';
+
+ const char *new_end = strstr(start, tag_close);
+ if(!new_end || (end && new_end > end))
+ return false;
+
+ end = new_end;
+ }
+
+ if(!start || !end || start == end)
+ return false;
+
+ cb(buffer, prefix, start, end);
+ return true;
+}
+
+static void print_xml_cb(BUFFER *buffer, const char *prefix, const char *start, const char *end) {
+ if(prefix)
+ buffer_strcat(buffer, prefix);
+
+ buffer_pretty_print_xml_object(buffer, start, end);
+}
+
+bool buffer_extract_and_print_xml(BUFFER *buffer, const char *xml, size_t xml_len, const char *prefix, const char *keys[]) {
+ return buffer_extract_and_print_xml_with_cb(
+ buffer, xml, xml_len,
+ prefix, keys,
+ print_xml_cb);
+}
+
+static void print_value_cb(BUFFER *buffer, const char *prefix, const char *start, const char *end) {
+ if(prefix)
+ buffer_strcat(buffer, prefix);
+
+ buffer_need_bytes(buffer, end - start + 1);
+
+ char *started = &buffer->buffer[buffer->len];
+ char *d = started;
+ const char *s = start;
+
+ while(s < end && s) {
+ if(*s == '&' && s + 3 < end) {
+ if(*(s + 1) == '#') {
+ if(s + 4 < end && *(s + 2) == '1' && *(s + 4) == ';') {
+ if (*(s + 3) == '0') {
+ s += 5;
+ *d++ = '\n';
+ continue;
+ } else if (*(s + 3) == '3') {
+ s += 5;
+ // *d++ = '\r';
+ continue;
+ }
+ } else if (*(s + 2) == '9' && *(s + 3) == ';') {
+ s += 4;
+ *d++ = '\t';
+ continue;
+ }
+ }
+ else if(s + 3 < end && *(s + 2) == 't' && *(s + 3) == ';') {
+ if(*(s + 1) == 'l') {
+ s += 4;
+ *d++ = '<';
+ continue;
+ }
+ else if(*(s + 1) == 'g') {
+ s += 4;
+ *d++ = '>';
+ continue;
+ }
+ }
+ }
+ *d++ = *s++;
+ }
+ *d = '\0';
+ buffer->len += d - started;
+}
+
+bool buffer_xml_extract_and_print_value(BUFFER *buffer, const char *xml, size_t xml_len, const char *prefix, const char *keys[]) {
+ return buffer_extract_and_print_xml_with_cb(
+ buffer, xml, xml_len,
+ prefix, keys,
+ print_value_cb);
+}
diff --git a/src/collectors/windows-events.plugin/windows-events-xml.h b/src/collectors/windows-events.plugin/windows-events-xml.h
new file mode 100644
index 000000000..78d2f686e
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events-xml.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef WINDOWS_EVENTS_XML_H
+#define WINDOWS_EVENTS_XML_H
+
+#include "libnetdata/libnetdata.h"
+
+void buffer_pretty_print_xml(BUFFER *buffer, const char *xml, size_t xml_len);
+bool buffer_extract_and_print_xml(BUFFER *buffer, const char *xml, size_t xml_len, const char *prefix, const char *keys[]);
+bool buffer_xml_extract_and_print_value(BUFFER *buffer, const char *xml, size_t xml_len, const char *prefix, const char *keys[]);
+
+#endif //WINDOWS_EVENTS_XML_H
diff --git a/src/collectors/windows-events.plugin/windows-events.c b/src/collectors/windows-events.plugin/windows-events.c
new file mode 100644
index 000000000..09ce558ae
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events.c
@@ -0,0 +1,1402 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "libnetdata/libnetdata.h"
+#include "libnetdata/required_dummies.h"
+
+#include "windows-events.h"
+
+netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
+static bool plugin_should_exit = false;
+
+#define WEVT_ALWAYS_VISIBLE_KEYS NULL
+
+#define WEVT_KEYS_EXCLUDED_FROM_FACETS \
+ "|" WEVT_FIELD_MESSAGE \
+ "|" WEVT_FIELD_XML \
+ ""
+
+#define WEVT_KEYS_INCLUDED_IN_FACETS \
+ "|" WEVT_FIELD_COMPUTER \
+ "|" WEVT_FIELD_PROVIDER \
+ "|" WEVT_FIELD_LEVEL \
+ "|" WEVT_FIELD_KEYWORDS \
+ "|" WEVT_FIELD_OPCODE \
+ "|" WEVT_FIELD_TASK \
+ "|" WEVT_FIELD_ACCOUNT \
+ "|" WEVT_FIELD_DOMAIN \
+ "|" WEVT_FIELD_SID \
+ ""
+
+#define query_has_fts(lqs) ((lqs)->rq.query != NULL)
+
+static inline WEVT_QUERY_STATUS check_stop(const bool *cancelled, const usec_t *stop_monotonic_ut) {
+ if(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)) {
+ nd_log(NDLS_COLLECTORS, NDLP_INFO, "Function has been cancelled");
+ return WEVT_CANCELLED;
+ }
+
+ if(now_monotonic_usec() > __atomic_load_n(stop_monotonic_ut, __ATOMIC_RELAXED)) {
+ internal_error(true, "Function timed out");
+ return WEVT_TIMED_OUT;
+ }
+
+ return WEVT_OK;
+}
+
+FACET_ROW_SEVERITY wevt_levelid_to_facet_severity(FACETS *facets __maybe_unused, FACET_ROW *row, void *data __maybe_unused) {
+ FACET_ROW_KEY_VALUE *levelid_rkv = dictionary_get(row->dict, WEVT_FIELD_LEVEL "ID");
+ if(!levelid_rkv || levelid_rkv->empty)
+ return FACET_ROW_SEVERITY_NORMAL;
+
+ int windows_event_level = str2i(buffer_tostring(levelid_rkv->wb));
+
+ switch (windows_event_level) {
+ case WEVT_LEVEL_VERBOSE:
+ return FACET_ROW_SEVERITY_DEBUG;
+
+ default:
+ case WEVT_LEVEL_INFORMATION:
+ return FACET_ROW_SEVERITY_NORMAL;
+
+ case WEVT_LEVEL_WARNING:
+ return FACET_ROW_SEVERITY_WARNING;
+
+ case WEVT_LEVEL_ERROR:
+ case WEVT_LEVEL_CRITICAL:
+ return FACET_ROW_SEVERITY_CRITICAL;
+ }
+}
+
+struct wevt_bin_data {
+ bool rendered;
+ WEVT_EVENT ev;
+ WEVT_LOG *log;
+ EVT_HANDLE hEvent;
+ PROVIDER_META_HANDLE *provider;
+};
+
+static void wevt_cleanup_bin_data(void *data) {
+ struct wevt_bin_data *d = data;
+
+ if(d->hEvent)
+ EvtClose(d->hEvent);
+
+ provider_release(d->provider);
+ freez(d);
+}
+
+static inline void wevt_facets_register_bin_data(WEVT_LOG *log, FACETS *facets, WEVT_EVENT *ev) {
+ struct wevt_bin_data *d = mallocz(sizeof(struct wevt_bin_data));
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ internal_fatal(strcmp(log->ops.provider.data, provider_get_name(log->provider)) != 0,
+ "Provider name mismatch in data!");
+
+ internal_fatal(!UUIDeq(ev->provider, provider_get_uuid(log->provider)),
+ "Provider UUID mismatch in data!");
+#endif
+
+ d->ev = *ev;
+ d->log = log;
+ d->rendered = false;
+
+ // take the bookmark
+ d->hEvent = log->hEvent; log->hEvent = NULL;
+
+ // dup the provider
+ d->provider = provider_dup(log->provider);
+
+ facets_row_bin_data_set(facets, wevt_cleanup_bin_data, d);
+}
+
+static void wevt_lazy_loading_event_and_xml(struct wevt_bin_data *d, FACET_ROW *row __maybe_unused) {
+ if(d->rendered) return;
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ const FACET_ROW_KEY_VALUE *provider_rkv = dictionary_get(row->dict, WEVT_FIELD_PROVIDER);
+ internal_fatal(!provider_rkv || strcmp(buffer_tostring(provider_rkv->wb), provider_get_name(d->provider)) != 0,
+ "Provider of row does not match the bin data associated with it");
+
+ uint64_t event_record_id = UINT64_MAX;
+ const FACET_ROW_KEY_VALUE *event_record_id_rkv = dictionary_get(row->dict, WEVT_FIELD_EVENTRECORDID);
+ if(event_record_id_rkv)
+ event_record_id = str2uint64_t(buffer_tostring(event_record_id_rkv->wb), NULL);
+ internal_fatal(event_record_id != d->ev.id,
+ "Event Record ID of row does not match the bin data associated with it");
+#endif
+
+ // the message needs the xml
+ EvtFormatMessage_Xml_utf8(&d->log->ops.unicode, d->provider, d->hEvent, &d->log->ops.xml);
+ EvtFormatMessage_Event_utf8(&d->log->ops.unicode, d->provider, d->hEvent, &d->log->ops.event);
+ d->rendered = true;
+}
+
+static void wevt_lazy_load_xml(
+ FACETS *facets,
+ BUFFER *json_array,
+ FACET_ROW_KEY_VALUE *rkv __maybe_unused,
+ FACET_ROW *row,
+ void *data __maybe_unused) {
+
+ struct wevt_bin_data *d = facets_row_bin_data_get(facets, row);
+ if(!d) {
+ buffer_json_add_array_item_string(json_array, "Failed to get row BIN DATA from facets");
+ return;
+ }
+
+ wevt_lazy_loading_event_and_xml(d, row);
+ buffer_json_add_array_item_string(json_array, d->log->ops.xml.data);
+}
+
+static void wevt_lazy_load_message(
+ FACETS *facets,
+ BUFFER *json_array,
+ FACET_ROW_KEY_VALUE *rkv __maybe_unused,
+ FACET_ROW *row,
+ void *data __maybe_unused) {
+
+ struct wevt_bin_data *d = facets_row_bin_data_get(facets, row);
+ if(!d) {
+ buffer_json_add_array_item_string(json_array, "Failed to get row BIN DATA from facets");
+ return;
+ }
+
+ wevt_lazy_loading_event_and_xml(d, row);
+
+ if(d->log->ops.event.used <= 1) {
+ TXT_UTF8 *xml = &d->log->ops.xml;
+
+ buffer_flush(rkv->wb);
+
+ bool added_message = false;
+ if(xml->used > 1) {
+ const char *message_path[] = {
+ "RenderingInfo",
+ "Message",
+ NULL};
+
+ added_message = buffer_xml_extract_and_print_value(
+ rkv->wb,
+ xml->data, xml->used - 1,
+ NULL,
+ message_path);
+ }
+
+ if(!added_message) {
+ const FACET_ROW_KEY_VALUE *event_id_rkv = dictionary_get(row->dict, WEVT_FIELD_EVENTID);
+ if (event_id_rkv && buffer_strlen(event_id_rkv->wb)) {
+ buffer_fast_strcat(rkv->wb, "Event ", 6);
+ buffer_fast_strcat(rkv->wb, buffer_tostring(event_id_rkv->wb), buffer_strlen(event_id_rkv->wb));
+ } else
+ buffer_strcat(rkv->wb, "Unknown Event ");
+
+ const FACET_ROW_KEY_VALUE *provider_rkv = dictionary_get(row->dict, WEVT_FIELD_PROVIDER);
+ if (provider_rkv && buffer_strlen(provider_rkv->wb)) {
+ buffer_fast_strcat(rkv->wb, " of ", 4);
+ buffer_fast_strcat(rkv->wb, buffer_tostring(provider_rkv->wb), buffer_strlen(provider_rkv->wb));
+ buffer_putc(rkv->wb, '.');
+ } else
+ buffer_strcat(rkv->wb, "of unknown Provider.");
+ }
+
+ if(xml->used > 1) {
+ const char *event_path[] = {
+ "EventData",
+ NULL
+ };
+ bool added_event_data = buffer_extract_and_print_xml(
+ rkv->wb,
+ xml->data, xml->used - 1,
+ "\n\nRelated event data:\n",
+ event_path);
+
+ const char *user_path[] = {
+ "UserData",
+ NULL
+ };
+ bool added_user_data = buffer_extract_and_print_xml(
+ rkv->wb,
+ xml->data, xml->used - 1,
+ "\n\nRelated user data:\n",
+ user_path);
+
+ if(!added_event_data && !added_user_data)
+ buffer_strcat(rkv->wb, " Without any related data.");
+ }
+
+ buffer_json_add_array_item_string(json_array, buffer_tostring(rkv->wb));
+ }
+ else
+ buffer_json_add_array_item_string(json_array, d->log->ops.event.data);
+}
+
+static void wevt_register_fields(LOGS_QUERY_STATUS *lqs) {
+ // the order of the fields here, controls the order of the fields at the table presented
+
+ FACETS *facets = lqs->facets;
+ LOGS_QUERY_REQUEST *rq = &lqs->rq;
+
+ facets_register_row_severity(facets, wevt_levelid_to_facet_severity, NULL);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_COMPUTER,
+ rq->default_facet | FACET_KEY_OPTION_VISIBLE);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_CHANNEL,
+ rq->default_facet | FACET_KEY_OPTION_FTS);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_PROVIDER,
+ rq->default_facet | FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_ACCOUNT,
+ rq->default_facet | FACET_KEY_OPTION_FTS);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_DOMAIN,
+ rq->default_facet | FACET_KEY_OPTION_FTS);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_SID,
+ rq->default_facet | FACET_KEY_OPTION_FTS);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_EVENTID,
+ rq->default_facet |
+ FACET_KEY_OPTION_VISIBLE | FACET_KEY_OPTION_FTS);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_EVENTS_API,
+ rq->default_facet |
+ FACET_KEY_OPTION_FTS);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_LEVEL,
+ rq->default_facet | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_EXPANDED_FILTER);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_LEVEL "ID",
+ FACET_KEY_OPTION_NONE);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_PROCESSID,
+ FACET_KEY_OPTION_FTS);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_THREADID,
+ FACET_KEY_OPTION_FTS);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_TASK,
+ rq->default_facet | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_VISIBLE);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_TASK "ID",
+ FACET_KEY_OPTION_NONE);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_OPCODE,
+ rq->default_facet | FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_VISIBLE);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_OPCODE "ID",
+ FACET_KEY_OPTION_NONE);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_KEYWORDS,
+ rq->default_facet | FACET_KEY_OPTION_FTS);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_KEYWORDS "ID",
+ FACET_KEY_OPTION_NONE);
+
+ facets_register_dynamic_key_name(
+ facets,
+ WEVT_FIELD_MESSAGE,
+ FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_MAIN_TEXT | FACET_KEY_OPTION_VISIBLE,
+ wevt_lazy_load_message,
+ NULL);
+
+ facets_register_dynamic_key_name(
+ facets,
+ WEVT_FIELD_XML,
+ FACET_KEY_OPTION_NEVER_FACET | FACET_KEY_OPTION_PRETTY_XML,
+ wevt_lazy_load_xml,
+ NULL);
+
+ if(query_has_fts(lqs)) {
+ facets_register_key_name(
+ facets, WEVT_FIELD_EVENT_MESSAGE_HIDDEN,
+ FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_HIDDEN | FACET_KEY_OPTION_NEVER_FACET);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_EVENT_XML_HIDDEN,
+ FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_HIDDEN | FACET_KEY_OPTION_NEVER_FACET);
+
+ facets_register_key_name(
+ facets, WEVT_FIELD_EVENT_DATA_HIDDEN,
+ FACET_KEY_OPTION_FTS | FACET_KEY_OPTION_HIDDEN | FACET_KEY_OPTION_NEVER_FACET);
+ }
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ facets_register_key_name(
+ facets, "z_level_source",
+ rq->default_facet);
+
+ facets_register_key_name(
+ facets, "z_keywords_source",
+ rq->default_facet);
+
+ facets_register_key_name(
+ facets, "z_opcode_source",
+ rq->default_facet);
+
+ facets_register_key_name(
+ facets, "z_task_source",
+ rq->default_facet);
+#endif
+}
+
+#ifdef NETDATA_INTERNAL_CHECKS
+static const char *source_to_str(TXT_UTF8 *txt) {
+ switch(txt->src) {
+ default:
+ case TXT_SOURCE_UNKNOWN:
+ return "unknown";
+
+ case TXT_SOURCE_EVENT_LOG:
+ return "event-log";
+
+ case TXT_SOURCE_PROVIDER:
+ return "provider";
+
+ case TXT_SOURCE_FIELD_CACHE:
+ return "fields-cache";
+
+ case TXT_SOURCE_HARDCODED:
+ return "hardcoded";
+ }
+}
+#endif
+
+static const char *events_api_to_str(WEVT_PROVIDER_PLATFORM platform) {
+ switch(platform) {
+ case WEVT_PLATFORM_WEL:
+ return "Windows Event Log";
+
+ case WEVT_PLATFORM_ETW:
+ return "Event Tracing for Windows";
+
+ case WEVT_PLATFORM_TL:
+ return "TraceLogging";
+
+ default:
+ return "Unknown";
+ }
+}
+
+static inline size_t wevt_process_event(WEVT_LOG *log, FACETS *facets, LOGS_QUERY_SOURCE *src, usec_t *msg_ut __maybe_unused, WEVT_EVENT *ev) {
+ static __thread char uuid_str[UUID_STR_LEN];
+
+ size_t len, bytes = log->ops.raw.system.used + log->ops.raw.user.used;
+
+ if(!UUIDiszero(ev->provider)) {
+ uuid_unparse_lower(ev->provider.uuid, uuid_str);
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_PROVIDER_GUID, sizeof(WEVT_FIELD_PROVIDER_GUID) - 1,
+ uuid_str, sizeof(uuid_str) - 1);
+ }
+
+ if(!UUIDiszero(ev->activity_id)) {
+ uuid_unparse_lower(ev->activity_id.uuid, uuid_str);
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_ACTIVITY_ID, sizeof(WEVT_FIELD_ACTIVITY_ID) - 1,
+ uuid_str, sizeof(uuid_str) - 1);
+ }
+
+ if(!UUIDiszero(ev->related_activity_id)) {
+ uuid_unparse_lower(ev->related_activity_id.uuid, uuid_str);
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_RELATED_ACTIVITY_ID, sizeof(WEVT_FIELD_RELATED_ACTIVITY_ID) - 1,
+ uuid_str, sizeof(uuid_str) - 1);
+ }
+
+ if(ev->qualifiers) {
+ static __thread char qualifiers[UINT64_HEX_MAX_LENGTH];
+ len = print_uint64_hex(qualifiers, ev->qualifiers);
+ bytes += len;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_QUALIFIERS, sizeof(WEVT_FIELD_QUALIFIERS) - 1,
+ qualifiers, len);
+ }
+
+ {
+ static __thread char event_record_id_str[UINT64_MAX_LENGTH];
+ len = print_uint64(event_record_id_str, ev->id);
+ bytes += len;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_EVENTRECORDID, sizeof(WEVT_FIELD_EVENTRECORDID) - 1,
+ event_record_id_str, len);
+ }
+
+ if(ev->version) {
+ static __thread char version[UINT64_MAX_LENGTH];
+ len = print_uint64(version, ev->version);
+ bytes += len;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_VERSION, sizeof(WEVT_FIELD_VERSION) - 1,
+ version, len);
+ }
+
+ if(log->ops.provider.used > 1) {
+ bytes += log->ops.provider.used * 2; // unicode is double
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_PROVIDER, sizeof(WEVT_FIELD_PROVIDER) - 1,
+ log->ops.provider.data, log->ops.provider.used - 1);
+ }
+
+ if(log->ops.channel.used > 1) {
+ bytes += log->ops.channel.used * 2;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_CHANNEL, sizeof(WEVT_FIELD_CHANNEL) - 1,
+ log->ops.channel.data, log->ops.channel.used - 1);
+ }
+ else {
+ bytes += src->fullname_len * 2;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_CHANNEL, sizeof(WEVT_FIELD_CHANNEL) - 1,
+ src->fullname, src->fullname_len);
+ }
+
+ if(log->ops.level.used > 1) {
+ bytes += log->ops.level.used * 2;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_LEVEL, sizeof(WEVT_FIELD_LEVEL) - 1,
+ log->ops.level.data, log->ops.level.used - 1);
+ }
+
+ if(log->ops.computer.used > 1) {
+ bytes += log->ops.computer.used * 2;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_COMPUTER, sizeof(WEVT_FIELD_COMPUTER) - 1,
+ log->ops.computer.data, log->ops.computer.used - 1);
+ }
+
+ if(log->ops.opcode.used > 1) {
+ bytes += log->ops.opcode.used * 2;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_OPCODE, sizeof(WEVT_FIELD_OPCODE) - 1,
+ log->ops.opcode.data, log->ops.opcode.used - 1);
+ }
+
+ if(log->ops.keywords.used > 1) {
+ bytes += log->ops.keywords.used * 2;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_KEYWORDS, sizeof(WEVT_FIELD_KEYWORDS) - 1,
+ log->ops.keywords.data, log->ops.keywords.used - 1);
+ }
+
+ if(log->ops.task.used > 1) {
+ bytes += log->ops.task.used * 2;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_TASK, sizeof(WEVT_FIELD_TASK) - 1,
+ log->ops.task.data, log->ops.task.used - 1);
+ }
+
+ if(log->ops.account.used > 1) {
+ bytes += log->ops.account.used * 2;
+ facets_add_key_value_length(
+ facets,
+ WEVT_FIELD_ACCOUNT, sizeof(WEVT_FIELD_ACCOUNT) - 1,
+ log->ops.account.data, log->ops.account.used - 1);
+ }
+
+ if(log->ops.domain.used > 1) {
+ bytes += log->ops.domain.used * 2;
+ facets_add_key_value_length(
+ facets,
+ WEVT_FIELD_DOMAIN, sizeof(WEVT_FIELD_DOMAIN) - 1,
+ log->ops.domain.data, log->ops.domain.used - 1);
+ }
+
+ if(log->ops.sid.used > 1) {
+ bytes += log->ops.sid.used * 2;
+ facets_add_key_value_length(
+ facets,
+ WEVT_FIELD_SID, sizeof(WEVT_FIELD_SID) - 1,
+ log->ops.sid.data, log->ops.sid.used - 1);
+ }
+
+ {
+ static __thread char event_id_str[UINT64_MAX_LENGTH];
+ len = print_uint64(event_id_str, ev->event_id);
+ bytes += len;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_EVENTID, sizeof(WEVT_FIELD_EVENTID) - 1,
+ event_id_str, len);
+ }
+
+ {
+ const char *s = events_api_to_str(ev->platform);
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_EVENTS_API, sizeof(WEVT_FIELD_EVENTS_API) - 1, s, strlen(s));
+ }
+
+ if(ev->process_id) {
+ static __thread char process_id_str[UINT64_MAX_LENGTH];
+ len = print_uint64(process_id_str, ev->process_id);
+ bytes += len;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_PROCESSID, sizeof(WEVT_FIELD_PROCESSID) - 1,
+ process_id_str, len);
+ }
+
+ if(ev->thread_id) {
+ static __thread char thread_id_str[UINT64_MAX_LENGTH];
+ len = print_uint64(thread_id_str, ev->thread_id);
+ bytes += len;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_THREADID, sizeof(WEVT_FIELD_THREADID) - 1,
+ thread_id_str, len);
+ }
+
+ {
+ static __thread char str[UINT64_MAX_LENGTH];
+ len = print_uint64(str, ev->level);
+ bytes += len;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_LEVEL "ID", sizeof(WEVT_FIELD_LEVEL) + 2 - 1, str, len);
+ }
+
+ {
+ static __thread char str[UINT64_HEX_MAX_LENGTH];
+ len = print_uint64_hex_full(str, ev->keywords);
+ bytes += len;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_KEYWORDS "ID", sizeof(WEVT_FIELD_KEYWORDS) + 2 - 1, str, len);
+ }
+
+ {
+ static __thread char str[UINT64_MAX_LENGTH];
+ len = print_uint64(str, ev->opcode);
+ bytes += len;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_OPCODE "ID", sizeof(WEVT_FIELD_OPCODE) + 2 - 1, str, len);
+ }
+
+ {
+ static __thread char str[UINT64_MAX_LENGTH];
+ len = print_uint64(str, ev->task);
+ bytes += len;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_TASK "ID", sizeof(WEVT_FIELD_TASK) + 2 - 1, str, len);
+ }
+
+ if(log->type & WEVT_QUERY_EVENT_DATA) {
+ // the query has full text-search
+ if(log->ops.event.used > 1) {
+ bytes += log->ops.event.used;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_EVENT_MESSAGE_HIDDEN, sizeof(WEVT_FIELD_EVENT_MESSAGE_HIDDEN) - 1,
+ log->ops.event.data, log->ops.event.used - 1);
+ }
+
+ if(log->ops.xml.used > 1) {
+ bytes += log->ops.xml.used;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_EVENT_XML_HIDDEN, sizeof(WEVT_FIELD_EVENT_XML_HIDDEN) - 1,
+ log->ops.xml.data, log->ops.xml.used - 1);
+ }
+
+ if(log->ops.event_data->len) {
+ bytes += log->ops.event_data->len;
+ facets_add_key_value_length(
+ facets, WEVT_FIELD_EVENT_DATA_HIDDEN, sizeof(WEVT_FIELD_EVENT_DATA_HIDDEN) - 1,
+ buffer_tostring(log->ops.event_data), buffer_strlen(log->ops.event_data));
+ }
+ }
+
+ wevt_facets_register_bin_data(log, facets, ev);
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ facets_add_key_value(facets, "z_level_source", source_to_str(&log->ops.level));
+ facets_add_key_value(facets, "z_keywords_source", source_to_str(&log->ops.keywords));
+ facets_add_key_value(facets, "z_opcode_source", source_to_str(&log->ops.opcode));
+ facets_add_key_value(facets, "z_task_source", source_to_str(&log->ops.task));
+#endif
+
+ return bytes;
+}
+
+static void send_progress_update(LOGS_QUERY_STATUS *lqs, size_t current_row_counter, bool flush_current_file) {
+ usec_t now_ut = now_monotonic_usec();
+
+ if(current_row_counter > lqs->c.progress.entries.current_query_total) {
+ lqs->c.progress.entries.total += current_row_counter - lqs->c.progress.entries.current_query_total;
+ lqs->c.progress.entries.current_query_total = current_row_counter;
+ }
+
+ if(flush_current_file) {
+ lqs->c.progress.entries.total += current_row_counter;
+ lqs->c.progress.entries.total -= lqs->c.progress.entries.current_query_total;
+ lqs->c.progress.entries.completed += current_row_counter;
+ lqs->c.progress.entries.current_query_total = 0;
+ }
+
+ size_t completed = lqs->c.progress.entries.completed + current_row_counter;
+ if(completed > lqs->c.progress.entries.total)
+ lqs->c.progress.entries.total = completed;
+
+ usec_t progress_duration_ut = now_ut - lqs->c.progress.last_ut;
+ if(progress_duration_ut >= WINDOWS_EVENTS_PROGRESS_EVERY_UT) {
+ lqs->c.progress.last_ut = now_ut;
+
+ netdata_mutex_lock(&stdout_mutex);
+ pluginsd_function_progress_to_stdout(lqs->rq.transaction, completed, lqs->c.progress.entries.total);
+ netdata_mutex_unlock(&stdout_mutex);
+ }
+}
+
+static WEVT_QUERY_STATUS wevt_query_backward(
+ WEVT_LOG *log, BUFFER *wb __maybe_unused, FACETS *facets,
+ LOGS_QUERY_SOURCE *src,
+ LOGS_QUERY_STATUS *lqs)
+{
+ usec_t start_ut = lqs->query.start_ut;
+ usec_t stop_ut = lqs->query.stop_ut;
+ bool stop_when_full = lqs->query.stop_when_full;
+
+// lqs->c.query_file.start_ut = start_ut;
+// lqs->c.query_file.stop_ut = stop_ut;
+
+ if(!wevt_query(log, channel2unicode(src->fullname), lqs->c.query, EvtQueryReverseDirection))
+ return WEVT_FAILED_TO_SEEK;
+
+ size_t errors_no_timestamp = 0;
+ usec_t latest_msg_ut = 0; // the biggest timestamp we have seen so far
+ usec_t first_msg_ut = 0; // the first message we got from the db
+ size_t row_counter = 0, last_row_counter = 0, rows_useful = 0;
+ size_t bytes = 0, last_bytes = 0;
+
+ usec_t last_usec_from = 0;
+ usec_t last_usec_to = 0;
+
+ WEVT_QUERY_STATUS status = WEVT_OK;
+
+ facets_rows_begin(facets);
+ WEVT_EVENT e;
+ while (status == WEVT_OK && wevt_get_next_event(log, &e)) {
+ usec_t msg_ut = e.created_ns / NSEC_PER_USEC;
+
+ if(unlikely(!msg_ut)) {
+ errors_no_timestamp++;
+ continue;
+ }
+
+ if (unlikely(msg_ut > start_ut))
+ continue;
+
+ if (unlikely(msg_ut < stop_ut))
+ break;
+
+ if(unlikely(msg_ut > latest_msg_ut))
+ latest_msg_ut = msg_ut;
+
+ if(unlikely(!first_msg_ut)) {
+ first_msg_ut = msg_ut;
+ // lqs->c.query_file.first_msg_ut = msg_ut;
+ }
+
+// sampling_t sample = is_row_in_sample(log, lqs, src, msg_ut,
+// FACETS_ANCHOR_DIRECTION_BACKWARD,
+// facets_row_candidate_to_keep(facets, msg_ut));
+//
+// if(sample == SAMPLING_FULL) {
+ bytes += wevt_process_event(log, facets, src, &msg_ut, &e);
+
+ // make sure each line gets a unique timestamp
+ if(unlikely(msg_ut >= last_usec_from && msg_ut <= last_usec_to))
+ msg_ut = --last_usec_from;
+ else
+ last_usec_from = last_usec_to = msg_ut;
+
+ if(facets_row_finished(facets, msg_ut))
+ rows_useful++;
+
+ row_counter++;
+ if(unlikely((row_counter % FUNCTION_DATA_ONLY_CHECK_EVERY_ROWS) == 0 &&
+ stop_when_full &&
+ facets_rows(facets) >= lqs->rq.entries)) {
+ // stop the data only query
+ usec_t oldest = facets_row_oldest_ut(facets);
+ if(oldest && msg_ut < (oldest - lqs->anchor.delta_ut))
+ break;
+ }
+
+ if(unlikely(row_counter % FUNCTION_PROGRESS_EVERY_ROWS == 0)) {
+ status = check_stop(lqs->cancelled, lqs->stop_monotonic_ut);
+
+ if(status == WEVT_OK) {
+ lqs->c.rows_read += row_counter - last_row_counter;
+ last_row_counter = row_counter;
+
+ lqs->c.bytes_read += bytes - last_bytes;
+ last_bytes = bytes;
+
+ send_progress_update(lqs, row_counter, false);
+ }
+ }
+// }
+// else if(sample == SAMPLING_SKIP_FIELDS)
+// facets_row_finished_unsampled(facets, msg_ut);
+// else {
+// sampling_update_running_query_file_estimates(facets, log, lqs, src, msg_ut, FACETS_ANCHOR_DIRECTION_BACKWARD);
+// break;
+// }
+ }
+
+ send_progress_update(lqs, row_counter, true);
+ lqs->c.rows_read += row_counter - last_row_counter;
+ lqs->c.bytes_read += bytes - last_bytes;
+ lqs->c.rows_useful += rows_useful;
+
+ if(errors_no_timestamp)
+ netdata_log_error("WINDOWS-EVENTS: %zu events did not have timestamps", errors_no_timestamp);
+
+ if(latest_msg_ut > lqs->last_modified)
+ lqs->last_modified = latest_msg_ut;
+
+ wevt_query_done(log);
+
+ return status;
+}
+
+static WEVT_QUERY_STATUS wevt_query_forward(
+ WEVT_LOG *log, BUFFER *wb __maybe_unused, FACETS *facets,
+ LOGS_QUERY_SOURCE *src,
+ LOGS_QUERY_STATUS *lqs)
+{
+ usec_t start_ut = lqs->query.start_ut;
+ usec_t stop_ut = lqs->query.stop_ut;
+ bool stop_when_full = lqs->query.stop_when_full;
+
+// lqs->c.query_file.start_ut = start_ut;
+// lqs->c.query_file.stop_ut = stop_ut;
+
+ if(!wevt_query(log, channel2unicode(src->fullname), lqs->c.query, EvtQueryForwardDirection))
+ return WEVT_FAILED_TO_SEEK;
+
+ size_t errors_no_timestamp = 0;
+ usec_t latest_msg_ut = 0; // the biggest timestamp we have seen so far
+ usec_t first_msg_ut = 0; // the first message we got from the db
+ size_t row_counter = 0, last_row_counter = 0, rows_useful = 0;
+ size_t bytes = 0, last_bytes = 0;
+
+ usec_t last_usec_from = 0;
+ usec_t last_usec_to = 0;
+
+ WEVT_QUERY_STATUS status = WEVT_OK;
+
+ facets_rows_begin(facets);
+ WEVT_EVENT e;
+ while (status == WEVT_OK && wevt_get_next_event(log, &e)) {
+ usec_t msg_ut = e.created_ns / NSEC_PER_USEC;
+
+ if(unlikely(!msg_ut)) {
+ errors_no_timestamp++;
+ continue;
+ }
+
+ if (unlikely(msg_ut < start_ut))
+ continue;
+
+ if (unlikely(msg_ut > stop_ut))
+ break;
+
+ if(likely(msg_ut > latest_msg_ut))
+ latest_msg_ut = msg_ut;
+
+ if(unlikely(!first_msg_ut)) {
+ first_msg_ut = msg_ut;
+ // lqs->c.query_file.first_msg_ut = msg_ut;
+ }
+
+// sampling_t sample = is_row_in_sample(log, lqs, src, msg_ut,
+// FACETS_ANCHOR_DIRECTION_FORWARD,
+// facets_row_candidate_to_keep(facets, msg_ut));
+//
+// if(sample == SAMPLING_FULL) {
+ bytes += wevt_process_event(log, facets, src, &msg_ut, &e);
+
+ // make sure each line gets a unique timestamp
+ if(unlikely(msg_ut >= last_usec_from && msg_ut <= last_usec_to))
+ msg_ut = ++last_usec_to;
+ else
+ last_usec_from = last_usec_to = msg_ut;
+
+ if(facets_row_finished(facets, msg_ut))
+ rows_useful++;
+
+ row_counter++;
+ if(unlikely((row_counter % FUNCTION_DATA_ONLY_CHECK_EVERY_ROWS) == 0 &&
+ stop_when_full &&
+ facets_rows(facets) >= lqs->rq.entries)) {
+ // stop the data only query
+ usec_t newest = facets_row_newest_ut(facets);
+ if(newest && msg_ut > (newest + lqs->anchor.delta_ut))
+ break;
+ }
+
+ if(unlikely(row_counter % FUNCTION_PROGRESS_EVERY_ROWS == 0)) {
+ status = check_stop(lqs->cancelled, lqs->stop_monotonic_ut);
+
+ if(status == WEVT_OK) {
+ lqs->c.rows_read += row_counter - last_row_counter;
+ last_row_counter = row_counter;
+
+ lqs->c.bytes_read += bytes - last_bytes;
+ last_bytes = bytes;
+
+ send_progress_update(lqs, row_counter, false);
+ }
+ }
+// }
+// else if(sample == SAMPLING_SKIP_FIELDS)
+// facets_row_finished_unsampled(facets, msg_ut);
+// else {
+// sampling_update_running_query_file_estimates(facets, log, lqs, src, msg_ut, FACETS_ANCHOR_DIRECTION_FORWARD);
+// break;
+// }
+ }
+
+ send_progress_update(lqs, row_counter, true);
+ lqs->c.rows_read += row_counter - last_row_counter;
+ lqs->c.bytes_read += bytes - last_bytes;
+ lqs->c.rows_useful += rows_useful;
+
+ if(errors_no_timestamp)
+ netdata_log_error("WINDOWS-EVENTS: %zu events did not have timestamps", errors_no_timestamp);
+
+ if(latest_msg_ut > lqs->last_modified)
+ lqs->last_modified = latest_msg_ut;
+
+ wevt_query_done(log);
+
+ return status;
+}
+
+static WEVT_QUERY_STATUS wevt_query_one_channel(
+ WEVT_LOG *log,
+ BUFFER *wb, FACETS *facets,
+ LOGS_QUERY_SOURCE *src,
+ LOGS_QUERY_STATUS *lqs) {
+
+ errno_clear();
+
+ WEVT_QUERY_STATUS status;
+ if(lqs->rq.direction == FACETS_ANCHOR_DIRECTION_FORWARD)
+ status = wevt_query_forward(log, wb, facets, src, lqs);
+ else
+ status = wevt_query_backward(log, wb, facets, src, lqs);
+
+ return status;
+}
+
+static bool source_is_mine(LOGS_QUERY_SOURCE *src, LOGS_QUERY_STATUS *lqs) {
+ if(
+ // no source is requested
+ (lqs->rq.source_type == WEVTS_NONE && !lqs->rq.sources) ||
+
+ // matches our internal source types
+ (src->source_type & lqs->rq.source_type) ||
+
+ // matches the source name
+ (lqs->rq.sources && src->source && simple_pattern_matches(lqs->rq.sources, string2str(src->source))) ||
+
+ // matches the provider (providers start with a special prefix to avoid mix and match)
+ (lqs->rq.sources && src->provider && simple_pattern_matches(lqs->rq.sources, string2str(src->provider)))
+
+ ) {
+
+ if(!src->msg_last_ut)
+ // the file is not scanned yet, or the timestamps have not been updated,
+ // so we don't know if it can contribute or not - let's add it.
+ return true;
+
+ usec_t anchor_delta = ANCHOR_DELTA_UT;
+ usec_t first_ut = src->msg_first_ut - anchor_delta;
+ usec_t last_ut = src->msg_last_ut + anchor_delta;
+
+ if(last_ut >= lqs->rq.after_ut && first_ut <= lqs->rq.before_ut)
+ return true;
+ }
+
+ return false;
+}
+
+static int wevt_master_query(BUFFER *wb __maybe_unused, LOGS_QUERY_STATUS *lqs __maybe_unused) {
+ // make sure the sources list is updated
+ wevt_sources_scan();
+
+ lqs->c.query = wevt_generate_query_no_xpath(lqs, wb);
+ if(!lqs->c.query)
+ return rrd_call_function_error(wb, "failed to generate query", HTTP_RESP_INTERNAL_SERVER_ERROR);
+
+ FACETS *facets = lqs->facets;
+
+ WEVT_QUERY_STATUS status = WEVT_NO_CHANNEL_MATCHED;
+
+ lqs->c.files_matched = 0;
+ lqs->c.file_working = 0;
+ lqs->c.rows_useful = 0;
+ lqs->c.rows_read = 0;
+ lqs->c.bytes_read = 0;
+
+ size_t files_used = 0;
+ size_t files_max = dictionary_entries(wevt_sources);
+ const DICTIONARY_ITEM *file_items[files_max];
+
+ // count the files
+ bool files_are_newer = false;
+ LOGS_QUERY_SOURCE *src;
+ dfe_start_read(wevt_sources, src) {
+ if(!source_is_mine(src, lqs))
+ continue;
+
+ file_items[files_used++] = dictionary_acquired_item_dup(wevt_sources, src_dfe.item);
+
+ if(src->msg_last_ut > lqs->rq.if_modified_since)
+ files_are_newer = true;
+
+ lqs->c.progress.entries.total += src->entries;
+ }
+ dfe_done(jf);
+
+ lqs->c.files_matched = files_used;
+
+ if(lqs->rq.if_modified_since && !files_are_newer) {
+ // release the files
+ for(size_t f = 0; f < files_used ;f++)
+ dictionary_acquired_item_release(wevt_sources, file_items[f]);
+
+ return rrd_call_function_error(wb, "not modified", HTTP_RESP_NOT_MODIFIED);
+ }
+
+ // sort the files, so that they are optimal for facets
+ if(files_used >= 2) {
+ if (lqs->rq.direction == FACETS_ANCHOR_DIRECTION_BACKWARD)
+ qsort(file_items, files_used, sizeof(const DICTIONARY_ITEM *),
+ wevt_sources_dict_items_backward_compar);
+ else
+ qsort(file_items, files_used, sizeof(const DICTIONARY_ITEM *),
+ wevt_sources_dict_items_forward_compar);
+ }
+
+ bool partial = false;
+ usec_t query_started_ut = now_monotonic_usec();
+ usec_t started_ut = query_started_ut;
+ usec_t ended_ut = started_ut;
+ usec_t duration_ut, max_duration_ut = 0;
+
+ WEVT_LOG *log = wevt_openlog6(query_has_fts(lqs) ? WEVT_QUERY_FTS : WEVT_QUERY_NORMAL);
+ if(!log) {
+ // release the files
+ for(size_t f = 0; f < files_used ;f++)
+ dictionary_acquired_item_release(wevt_sources, file_items[f]);
+
+ netdata_log_error("WINDOWS EVENTS: cannot open windows event log");
+ return rrd_call_function_error(wb, "cannot open windows events log", HTTP_RESP_INTERNAL_SERVER_ERROR);
+ }
+
+ // sampling_query_init(lqs, facets);
+
+ buffer_json_member_add_array(wb, "_channels");
+ for(size_t f = 0; f < files_used ;f++) {
+ const char *fullname = dictionary_acquired_item_name(file_items[f]);
+ src = dictionary_acquired_item_value(file_items[f]);
+
+ if(!source_is_mine(src, lqs))
+ continue;
+
+ started_ut = ended_ut;
+
+ // do not even try to do the query if we expect it to pass the timeout
+ if(ended_ut + max_duration_ut * 3 >= *lqs->stop_monotonic_ut) {
+ partial = true;
+ status = WEVT_TIMED_OUT;
+ break;
+ }
+
+ lqs->c.file_working++;
+
+ size_t rows_useful = lqs->c.rows_useful;
+ size_t rows_read = lqs->c.rows_read;
+ size_t bytes_read = lqs->c.bytes_read;
+ size_t matches_setup_ut = lqs->c.matches_setup_ut;
+
+ // sampling_file_init(lqs, src);
+
+ lqs->c.progress.entries.current_query_total = src->entries;
+ WEVT_QUERY_STATUS tmp_status = wevt_query_one_channel(log, wb, facets, src, lqs);
+
+ rows_useful = lqs->c.rows_useful - rows_useful;
+ rows_read = lqs->c.rows_read - rows_read;
+ bytes_read = lqs->c.bytes_read - bytes_read;
+ matches_setup_ut = lqs->c.matches_setup_ut - matches_setup_ut;
+
+ ended_ut = now_monotonic_usec();
+ duration_ut = ended_ut - started_ut;
+
+ if(duration_ut > max_duration_ut)
+ max_duration_ut = duration_ut;
+
+ buffer_json_add_array_item_object(wb); // channel source
+ {
+ // information about the file
+ buffer_json_member_add_string(wb, "_name", fullname);
+ buffer_json_member_add_uint64(wb, "_source_type", src->source_type);
+ buffer_json_member_add_string(wb, "_source", string2str(src->source));
+ buffer_json_member_add_uint64(wb, "_msg_first_ut", src->msg_first_ut);
+ buffer_json_member_add_uint64(wb, "_msg_last_ut", src->msg_last_ut);
+
+ // information about the current use of the file
+ buffer_json_member_add_uint64(wb, "duration_ut", ended_ut - started_ut);
+ buffer_json_member_add_uint64(wb, "rows_read", rows_read);
+ buffer_json_member_add_uint64(wb, "rows_useful", rows_useful);
+ buffer_json_member_add_double(wb, "rows_per_second", (double) rows_read / (double) duration_ut * (double) USEC_PER_SEC);
+ buffer_json_member_add_uint64(wb, "bytes_read", bytes_read);
+ buffer_json_member_add_double(wb, "bytes_per_second", (double) bytes_read / (double) duration_ut * (double) USEC_PER_SEC);
+ buffer_json_member_add_uint64(wb, "duration_matches_ut", matches_setup_ut);
+
+ // if(lqs->rq.sampling) {
+ // buffer_json_member_add_object(wb, "_sampling");
+ // {
+ // buffer_json_member_add_uint64(wb, "sampled", lqs->c.samples_per_file.sampled);
+ // buffer_json_member_add_uint64(wb, "unsampled", lqs->c.samples_per_file.unsampled);
+ // buffer_json_member_add_uint64(wb, "estimated", lqs->c.samples_per_file.estimated);
+ // }
+ // buffer_json_object_close(wb); // _sampling
+ // }
+ }
+ buffer_json_object_close(wb); // channel source
+
+ bool stop = false;
+ switch(tmp_status) {
+ case WEVT_OK:
+ case WEVT_NO_CHANNEL_MATCHED:
+ status = (status == WEVT_OK) ? WEVT_OK : tmp_status;
+ break;
+
+ case WEVT_FAILED_TO_OPEN:
+ case WEVT_FAILED_TO_SEEK:
+ partial = true;
+ if(status == WEVT_NO_CHANNEL_MATCHED)
+ status = tmp_status;
+ break;
+
+ case WEVT_CANCELLED:
+ case WEVT_TIMED_OUT:
+ partial = true;
+ stop = true;
+ status = tmp_status;
+ break;
+
+ case WEVT_NOT_MODIFIED:
+ internal_fatal(true, "this should never be returned here");
+ break;
+ }
+
+ if(stop)
+ break;
+ }
+ buffer_json_array_close(wb); // _channels
+
+ // release the files
+ for(size_t f = 0; f < files_used ;f++)
+ dictionary_acquired_item_release(wevt_sources, file_items[f]);
+
+ switch (status) {
+ case WEVT_OK:
+ if(lqs->rq.if_modified_since && !lqs->c.rows_useful)
+ return rrd_call_function_error(wb, "no useful logs, not modified", HTTP_RESP_NOT_MODIFIED);
+ break;
+
+ case WEVT_TIMED_OUT:
+ case WEVT_NO_CHANNEL_MATCHED:
+ break;
+
+ case WEVT_CANCELLED:
+ return rrd_call_function_error(wb, "client closed connection", HTTP_RESP_CLIENT_CLOSED_REQUEST);
+
+ case WEVT_NOT_MODIFIED:
+ return rrd_call_function_error(wb, "not modified", HTTP_RESP_NOT_MODIFIED);
+
+ case WEVT_FAILED_TO_OPEN:
+ return rrd_call_function_error(wb, "failed to open event log", HTTP_RESP_INTERNAL_SERVER_ERROR);
+
+ case WEVT_FAILED_TO_SEEK:
+ return rrd_call_function_error(wb, "failed to execute event log query", HTTP_RESP_INTERNAL_SERVER_ERROR);
+
+ default:
+ return rrd_call_function_error(wb, "unknown status", HTTP_RESP_INTERNAL_SERVER_ERROR);
+ }
+
+ buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
+ buffer_json_member_add_boolean(wb, "partial", partial);
+ buffer_json_member_add_string(wb, "type", "table");
+
+ // build a message for the query
+ if(!lqs->rq.data_only) {
+ CLEAN_BUFFER *msg = buffer_create(0, NULL);
+ CLEAN_BUFFER *msg_description = buffer_create(0, NULL);
+ ND_LOG_FIELD_PRIORITY msg_priority = NDLP_INFO;
+
+ // if(!journal_files_completed_once()) {
+ // buffer_strcat(msg, "Journals are still being scanned. ");
+ // buffer_strcat(msg_description
+ // , "LIBRARY SCAN: The journal files are still being scanned, you are probably viewing incomplete data. ");
+ // msg_priority = NDLP_WARNING;
+ // }
+
+ if(partial) {
+ buffer_strcat(msg, "Query timed-out, incomplete data. ");
+ buffer_strcat(msg_description
+ , "QUERY TIMEOUT: The query timed out and may not include all the data of the selected window. ");
+ msg_priority = NDLP_WARNING;
+ }
+
+ // if(lqs->c.samples.estimated || lqs->c.samples.unsampled) {
+ // double percent = (double) (lqs->c.samples.sampled * 100.0 /
+ // (lqs->c.samples.estimated + lqs->c.samples.unsampled + lqs->c.samples.sampled));
+ // buffer_sprintf(msg, "%.2f%% real data", percent);
+ // buffer_sprintf(msg_description, "ACTUAL DATA: The filters counters reflect %0.2f%% of the data. ", percent);
+ // msg_priority = MIN(msg_priority, NDLP_NOTICE);
+ // }
+ //
+ // if(lqs->c.samples.unsampled) {
+ // double percent = (double) (lqs->c.samples.unsampled * 100.0 /
+ // (lqs->c.samples.estimated + lqs->c.samples.unsampled + lqs->c.samples.sampled));
+ // buffer_sprintf(msg, ", %.2f%% unsampled", percent);
+ // buffer_sprintf(msg_description
+ // , "UNSAMPLED DATA: %0.2f%% of the events exist and have been counted, but their values have not been evaluated, so they are not included in the filters counters. "
+ // , percent);
+ // msg_priority = MIN(msg_priority, NDLP_NOTICE);
+ // }
+ //
+ // if(lqs->c.samples.estimated) {
+ // double percent = (double) (lqs->c.samples.estimated * 100.0 /
+ // (lqs->c.samples.estimated + lqs->c.samples.unsampled + lqs->c.samples.sampled));
+ // buffer_sprintf(msg, ", %.2f%% estimated", percent);
+ // buffer_sprintf(msg_description
+ // , "ESTIMATED DATA: The query selected a large amount of data, so to avoid delaying too much, the presented data are estimated by %0.2f%%. "
+ // , percent);
+ // msg_priority = MIN(msg_priority, NDLP_NOTICE);
+ // }
+
+ buffer_json_member_add_object(wb, "message");
+ if(buffer_tostring(msg)) {
+ buffer_json_member_add_string(wb, "title", buffer_tostring(msg));
+ buffer_json_member_add_string(wb, "description", buffer_tostring(msg_description));
+ buffer_json_member_add_string(wb, "status", nd_log_id2priority(msg_priority));
+ }
+ // else send an empty object if there is nothing to tell
+ buffer_json_object_close(wb); // message
+ }
+
+ if(!lqs->rq.data_only) {
+ buffer_json_member_add_time_t(wb, "update_every", 1);
+ buffer_json_member_add_string(wb, "help", WEVT_FUNCTION_DESCRIPTION);
+ }
+
+ if(!lqs->rq.data_only || lqs->rq.tail)
+ buffer_json_member_add_uint64(wb, "last_modified", lqs->last_modified);
+
+ facets_sort_and_reorder_keys(facets);
+ facets_report(facets, wb, used_hashes_registry);
+
+ wb->expires = now_realtime_sec() + (lqs->rq.data_only ? 3600 : 0);
+ buffer_json_member_add_time_t(wb, "expires", wb->expires);
+
+ // if(lqs->rq.sampling) {
+ // buffer_json_member_add_object(wb, "_sampling");
+ // {
+ // buffer_json_member_add_uint64(wb, "sampled", lqs->c.samples.sampled);
+ // buffer_json_member_add_uint64(wb, "unsampled", lqs->c.samples.unsampled);
+ // buffer_json_member_add_uint64(wb, "estimated", lqs->c.samples.estimated);
+ // }
+ // buffer_json_object_close(wb); // _sampling
+ // }
+
+ wevt_closelog6(log);
+
+ wb->content_type = CT_APPLICATION_JSON;
+ wb->response_code = HTTP_RESP_OK;
+ return wb->response_code;
+}
+
+void function_windows_events(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled,
+ BUFFER *payload, HTTP_ACCESS access __maybe_unused,
+ const char *source __maybe_unused, void *data __maybe_unused) {
+ bool have_slice = LQS_DEFAULT_SLICE_MODE;
+
+ LOGS_QUERY_STATUS tmp_fqs = {
+ .facets = lqs_facets_create(
+ LQS_DEFAULT_ITEMS_PER_QUERY,
+ FACETS_OPTION_ALL_KEYS_FTS | FACETS_OPTION_HASH_IDS,
+ WEVT_ALWAYS_VISIBLE_KEYS,
+ WEVT_KEYS_INCLUDED_IN_FACETS,
+ WEVT_KEYS_EXCLUDED_FROM_FACETS,
+ have_slice),
+
+ .rq = LOGS_QUERY_REQUEST_DEFAULTS(transaction, have_slice, FACETS_ANCHOR_DIRECTION_BACKWARD),
+
+ .cancelled = cancelled,
+ .stop_monotonic_ut = stop_monotonic_ut,
+ };
+ LOGS_QUERY_STATUS *lqs = &tmp_fqs;
+
+ CLEAN_BUFFER *wb = lqs_create_output_buffer();
+
+ // ------------------------------------------------------------------------
+ // parse the parameters
+
+ if(lqs_request_parse_and_validate(lqs, wb, function, payload, have_slice, WEVT_FIELD_LEVEL)) {
+ wevt_register_fields(lqs);
+
+ // ------------------------------------------------------------------------
+ // add versions to the response
+
+ buffer_json_wevt_versions(wb);
+
+ // ------------------------------------------------------------------------
+ // run the request
+
+ if (lqs->rq.info)
+ lqs_info_response(wb, lqs->facets);
+ else {
+ wevt_master_query(wb, lqs);
+ if (wb->response_code == HTTP_RESP_OK)
+ buffer_json_finalize(wb);
+ }
+ }
+
+ netdata_mutex_lock(&stdout_mutex);
+ pluginsd_function_result_to_stdout(transaction, wb);
+ netdata_mutex_unlock(&stdout_mutex);
+
+ lqs_cleanup(lqs);
+}
+
+int main(int argc __maybe_unused, char **argv __maybe_unused) {
+ nd_thread_tag_set("wevt.plugin");
+ nd_log_initialize_for_external_plugins("windows-events.plugin");
+
+ // ------------------------------------------------------------------------
+ // initialization
+
+ wevt_sources_init();
+ provider_cache_init();
+ cached_sid_username_init();
+ field_cache_init();
+
+ if(!EnableWindowsPrivilege(SE_SECURITY_NAME))
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_SECURITY_NAME);
+
+ if(!EnableWindowsPrivilege(SE_BACKUP_NAME))
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_BACKUP_NAME);
+
+ if(!EnableWindowsPrivilege(SE_AUDIT_NAME))
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Failed to enable %s privilege", SE_AUDIT_NAME);
+
+ // ------------------------------------------------------------------------
+ // debug
+
+ if(argc >= 2 && strcmp(argv[argc - 1], "debug") == 0) {
+ wevt_sources_scan();
+
+ struct {
+ const char *func;
+ } array[] = {
+ { "windows-events after:-8640000 before:0 last:200 source:All" },
+ //{ "windows-events after:-86400 before:0 direction:backward last:200 facets:HdUoSYab5wV,Cq2r7mRUv4a,LAnVlsIQfeD,BnPLNbA5VWT,KeCITtVD5AD,HytMJ9kj82B,JM3OPW3kHn6,H106l8MXSSr,HREiMN.4Ahu,ClaDGnYSQE7,ApYltST_icg,PtkRm91M0En data_only:false slice:true source:All" },
+ //{ "windows-events after:1726055370 before:1726056270 direction:backward last:200 facets:HdUoSYab5wV,Cq2r7mRUv4a,LAnVlsIQfeD,BnPLNbA5VWT,KeCITtVD5AD,HytMJ9kj82B,LT.Xp9I9tiP,No4kPTQbS.g,LQ2LQzfE8EG,PtkRm91M0En,JM3OPW3kHn6,ClaDGnYSQE7,H106l8MXSSr,HREiMN.4Ahu data_only:false source:All HytMJ9kj82B:BlC24d5JBBV,PtVoyIuX.MU,HMj1B38kHTv KeCITtVD5AD:PY1JtCeWwSe,O9kz5J37nNl,JZoJURadhDb" },
+ // { "windows-events after:1725636012 before:1726240812 direction:backward last:200 facets:HdUoSYab5wV,Cq2r7mRUv4a,LAnVlsIQfeD,BnPLNbA5VWT,KeCITtVD5AD,HytMJ9kj82B,JM3OPW3kHn6,H106l8MXSSr,HREiMN.4Ahu,ClaDGnYSQE7,ApYltST_icg,PtkRm91M0En data_only:false source:All PtkRm91M0En:LDzHbP5libb" },
+ //{ "windows-events after:1725650386 before:1725736786 anchor:1725652420809461 direction:forward last:200 facets:HWNGeY7tg6c,LAnVlsIQfeD,BnPLNbA5VWT,Cq2r7mRUv4a,KeCITtVD5AD,I_Amz_APBm3,HytMJ9kj82B,LT.Xp9I9tiP,No4kPTQbS.g,LQ2LQzfE8EG,PtkRm91M0En,JM3OPW3kHn6 if_modified_since:1725736649011085 data_only:true delta:true tail:true source:all Cq2r7mRUv4a:PPc9fUy.q6o No4kPTQbS.g:Dwo9PhK27v3 HytMJ9kj82B:KbbznGjt_9r LAnVlsIQfeD:OfU1t5cpjgG JM3OPW3kHn6:CS_0g5AEpy2" },
+ //{ "windows-events info after:1725650420 before:1725736820" },
+ //{ "windows-events after:1725650420 before:1725736820 last:200 facets:HWNGeY7tg6c,LAnVlsIQfeD,BnPLNbA5VWT,Cq2r7mRUv4a,KeCITtVD5AD,I_Amz_APBm3,HytMJ9kj82B,LT.Xp9I9tiP,No4kPTQbS.g,LQ2LQzfE8EG,PtkRm91M0En,JM3OPW3kHn6 source:all Cq2r7mRUv4a:PPc9fUy.q6o No4kPTQbS.g:Dwo9PhK27v3 HytMJ9kj82B:KbbznGjt_9r LAnVlsIQfeD:OfU1t5cpjgG JM3OPW3kHn6:CS_0g5AEpy2" },
+ //{ "windows-events after:1725650430 before:1725736830 last:200 facets:HWNGeY7tg6c,LAnVlsIQfeD,BnPLNbA5VWT,Cq2r7mRUv4a,KeCITtVD5AD,I_Amz_APBm3,HytMJ9kj82B,LT.Xp9I9tiP,No4kPTQbS.g,LQ2LQzfE8EG,PtkRm91M0En,JM3OPW3kHn6 source:all Cq2r7mRUv4a:PPc9fUy.q6o No4kPTQbS.g:Dwo9PhK27v3 HytMJ9kj82B:KbbznGjt_9r LAnVlsIQfeD:OfU1t5cpjgG JM3OPW3kHn6:CS_0g5AEpy2" },
+ { NULL },
+ };
+
+ for(int i = 0; array[i].func ;i++) {
+ bool cancelled = false;
+ usec_t stop_monotonic_ut = now_monotonic_usec() + 600 * USEC_PER_SEC;
+ //char buf[] = "windows-events after:-86400 before:0 direction:backward last:200 data_only:false slice:true source:all";
+ function_windows_events("123", (char *)array[i].func, &stop_monotonic_ut, &cancelled, NULL, HTTP_ACCESS_ALL, NULL, NULL);
+ }
+ printf("\n\nAll done!\n\n");
+ fflush(stdout);
+ exit(1);
+ }
+
+ // ------------------------------------------------------------------------
+ // the event loop for functions
+
+ struct functions_evloop_globals *wg =
+ functions_evloop_init(WINDOWS_EVENTS_WORKER_THREADS, "WEVT", &stdout_mutex, &plugin_should_exit);
+
+ functions_evloop_add_function(wg,
+ WEVT_FUNCTION_NAME,
+ function_windows_events,
+ WINDOWS_EVENTS_DEFAULT_TIMEOUT,
+ NULL);
+
+ // ------------------------------------------------------------------------
+ // register functions to netdata
+
+ netdata_mutex_lock(&stdout_mutex);
+
+ fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"logs\" "HTTP_ACCESS_FORMAT" %d\n",
+ WEVT_FUNCTION_NAME, WINDOWS_EVENTS_DEFAULT_TIMEOUT, WEVT_FUNCTION_DESCRIPTION,
+ (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA),
+ RRDFUNCTIONS_PRIORITY_DEFAULT);
+
+ fflush(stdout);
+ netdata_mutex_unlock(&stdout_mutex);
+
+ // ------------------------------------------------------------------------
+
+ usec_t send_newline_ut = 0;
+ usec_t since_last_scan_ut = WINDOWS_EVENTS_SCAN_EVERY_USEC * 2; // something big to trigger scanning at start
+ usec_t since_last_providers_release_ut = 0;
+ const bool tty = isatty(fileno(stdout)) == 1;
+
+ heartbeat_t hb;
+ heartbeat_init(&hb, USEC_PER_SEC);
+ while(!plugin_should_exit) {
+
+ if(since_last_scan_ut > WINDOWS_EVENTS_SCAN_EVERY_USEC) {
+ wevt_sources_scan();
+ since_last_scan_ut = 0;
+ }
+
+ if(since_last_providers_release_ut > WINDOWS_EVENTS_RELEASE_PROVIDERS_HANDLES_EVERY_UT) {
+ providers_release_unused_handles();
+ since_last_providers_release_ut = 0;
+ }
+
+ usec_t dt_ut = heartbeat_next(&hb);
+ since_last_providers_release_ut += dt_ut;
+ since_last_scan_ut += dt_ut;
+ send_newline_ut += dt_ut;
+
+ if(!tty && send_newline_ut > USEC_PER_SEC) {
+ send_newline_and_flush(&stdout_mutex);
+ send_newline_ut = 0;
+ }
+ }
+
+ exit(0);
+}
diff --git a/src/collectors/windows-events.plugin/windows-events.h b/src/collectors/windows-events.plugin/windows-events.h
new file mode 100644
index 000000000..34d600a98
--- /dev/null
+++ b/src/collectors/windows-events.plugin/windows-events.h
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_WINDOWS_EVENTS_H
+#define NETDATA_WINDOWS_EVENTS_H
+
+#include "libnetdata/libnetdata.h"
+#include "collectors/all.h"
+
+typedef enum {
+ WEVT_NO_CHANNEL_MATCHED,
+ WEVT_FAILED_TO_OPEN,
+ WEVT_FAILED_TO_SEEK,
+ WEVT_TIMED_OUT,
+ WEVT_OK,
+ WEVT_NOT_MODIFIED,
+ WEVT_CANCELLED,
+} WEVT_QUERY_STATUS;
+
+#define WEVT_CHANNEL_CLASSIC_TRACE 0x0
+#define WEVT_CHANNEL_GLOBAL_SYSTEM 0x8
+#define WEVT_CHANNEL_GLOBAL_APPLICATION 0x9
+#define WEVT_CHANNEL_GLOBAL_SECURITY 0xa
+
+#define WEVT_LEVEL_NONE 0x0
+#define WEVT_LEVEL_CRITICAL 0x1
+#define WEVT_LEVEL_ERROR 0x2
+#define WEVT_LEVEL_WARNING 0x3
+#define WEVT_LEVEL_INFORMATION 0x4
+#define WEVT_LEVEL_VERBOSE 0x5
+#define WEVT_LEVEL_RESERVED_6 0x6
+#define WEVT_LEVEL_RESERVED_7 0x7
+#define WEVT_LEVEL_RESERVED_8 0x8
+#define WEVT_LEVEL_RESERVED_9 0x9
+#define WEVT_LEVEL_RESERVED_10 0xa
+#define WEVT_LEVEL_RESERVED_11 0xb
+#define WEVT_LEVEL_RESERVED_12 0xc
+#define WEVT_LEVEL_RESERVED_13 0xd
+#define WEVT_LEVEL_RESERVED_14 0xe
+#define WEVT_LEVEL_RESERVED_15 0xf
+
+#define WEVT_OPCODE_INFO 0x0
+#define WEVT_OPCODE_START 0x1
+#define WEVT_OPCODE_STOP 0x2
+#define WEVT_OPCODE_DC_START 0x3
+#define WEVT_OPCODE_DC_STOP 0x4
+#define WEVT_OPCODE_EXTENSION 0x5
+#define WEVT_OPCODE_REPLY 0x6
+#define WEVT_OPCODE_RESUME 0x7
+#define WEVT_OPCODE_SUSPEND 0x8
+#define WEVT_OPCODE_SEND 0x9
+#define WEVT_OPCODE_RECEIVE 0xf0
+#define WEVT_OPCODE_RESERVED_241 0xf1
+#define WEVT_OPCODE_RESERVED_242 0xf2
+#define WEVT_OPCODE_RESERVED_243 0xf3
+#define WEVT_OPCODE_RESERVED_244 0xf4
+#define WEVT_OPCODE_RESERVED_245 0xf5
+#define WEVT_OPCODE_RESERVED_246 0xf6
+#define WEVT_OPCODE_RESERVED_247 0xf7
+#define WEVT_OPCODE_RESERVED_248 0xf8
+#define WEVT_OPCODE_RESERVED_249 0xf9
+#define WEVT_OPCODE_RESERVED_250 0xfa
+#define WEVT_OPCODE_RESERVED_251 0xfb
+#define WEVT_OPCODE_RESERVED_252 0xfc
+#define WEVT_OPCODE_RESERVED_253 0xfd
+#define WEVT_OPCODE_RESERVED_254 0xfe
+#define WEVT_OPCODE_RESERVED_255 0xff
+
+#define WEVT_TASK_NONE 0x0
+
+#define WEVT_KEYWORD_NONE 0x0
+#define WEVT_KEYWORD_RESPONSE_TIME 0x0001000000000000
+#define WEVT_KEYWORD_WDI_CONTEXT 0x0002000000000000
+#define WEVT_KEYWORD_WDI_DIAG 0x0004000000000000
+#define WEVT_KEYWORD_SQM 0x0008000000000000
+#define WEVT_KEYWORD_AUDIT_FAILURE 0x0010000000000000
+#define WEVT_KEYWORD_AUDIT_SUCCESS 0x0020000000000000
+#define WEVT_KEYWORD_CORRELATION_HINT 0x0040000000000000
+#define WEVT_KEYWORD_EVENTLOG_CLASSIC 0x0080000000000000
+#define WEVT_KEYWORD_RESERVED_56 0x0100000000000000
+#define WEVT_KEYWORD_RESERVED_57 0x0200000000000000
+#define WEVT_KEYWORD_RESERVED_58 0x0400000000000000
+#define WEVT_KEYWORD_RESERVED_59 0x0800000000000000
+#define WEVT_KEYWORDE_RESERVED_60 0x1000000000000000
+#define WEVT_KEYWORD_RESERVED_61 0x2000000000000000
+#define WEVT_KEYWORD_RESERVED_62 0x4000000000000000
+#define WEVT_KEYWORD_RESERVED_63 0x8000000000000000
+
+#define WEVT_LEVEL_NAME_NONE "None"
+#define WEVT_LEVEL_NAME_CRITICAL "Critical"
+#define WEVT_LEVEL_NAME_ERROR "Error"
+#define WEVT_LEVEL_NAME_WARNING "Warning"
+#define WEVT_LEVEL_NAME_INFORMATION "Information"
+#define WEVT_LEVEL_NAME_VERBOSE "Verbose"
+
+#define WEVT_OPCODE_NAME_INFO "Info"
+#define WEVT_OPCODE_NAME_START "Start"
+#define WEVT_OPCODE_NAME_STOP "Stop"
+#define WEVT_OPCODE_NAME_DC_START "DC Start"
+#define WEVT_OPCODE_NAME_DC_STOP "DC Stop"
+#define WEVT_OPCODE_NAME_EXTENSION "Extension"
+#define WEVT_OPCODE_NAME_REPLY "Reply"
+#define WEVT_OPCODE_NAME_RESUME "Resume"
+#define WEVT_OPCODE_NAME_SUSPEND "Suspend"
+#define WEVT_OPCODE_NAME_SEND "Send"
+#define WEVT_OPCODE_NAME_RECEIVE "Receive"
+
+#define WEVT_TASK_NAME_NONE "None"
+
+#define WEVT_KEYWORD_NAME_NONE "None"
+#define WEVT_KEYWORD_NAME_RESPONSE_TIME "Response Time"
+#define WEVT_KEYWORD_NAME_WDI_CONTEXT "WDI Context"
+#define WEVT_KEYWORD_NAME_WDI_DIAG "WDI Diagnostics"
+#define WEVT_KEYWORD_NAME_SQM "SQM (Software Quality Metrics)"
+#define WEVT_KEYWORD_NAME_AUDIT_FAILURE "Audit Failure"
+#define WEVT_KEYWORD_NAME_AUDIT_SUCCESS "Audit Success"
+#define WEVT_KEYWORD_NAME_CORRELATION_HINT "Correlation Hint"
+#define WEVT_KEYWORD_NAME_EVENTLOG_CLASSIC "Event Log Classic"
+
+#define WEVT_PREFIX_LEVEL "Level " // the space at the end is needed
+#define WEVT_PREFIX_KEYWORDS "Keywords " // the space at the end is needed
+#define WEVT_PREFIX_OPCODE "Opcode " // the space at the end is needed
+#define WEVT_PREFIX_TASK "Task " // the space at the end is needed
+
+#include "windows-events-sources.h"
+#include "windows-events-unicode.h"
+#include "windows-events-xml.h"
+#include "windows-events-providers.h"
+#include "windows-events-fields-cache.h"
+#include "windows-events-query.h"
+
+// enable or disable preloading on full-text-search
+#define ON_FTS_PRELOAD_MESSAGE 1
+#define ON_FTS_PRELOAD_XML 0
+#define ON_FTS_PRELOAD_EVENT_DATA 1
+
+#define WEVT_FUNCTION_DESCRIPTION "View, search and analyze the Microsoft Windows Events log."
+#define WEVT_FUNCTION_NAME "windows-events"
+
+#define WINDOWS_EVENTS_WORKER_THREADS 5
+#define WINDOWS_EVENTS_DEFAULT_TIMEOUT 600
+#define WINDOWS_EVENTS_SCAN_EVERY_USEC (5 * 60 * USEC_PER_SEC)
+#define WINDOWS_EVENTS_PROGRESS_EVERY_UT (250 * USEC_PER_MS)
+#define FUNCTION_PROGRESS_EVERY_ROWS (2000)
+#define FUNCTION_DATA_ONLY_CHECK_EVERY_ROWS (1000)
+#define ANCHOR_DELTA_UT (10 * USEC_PER_SEC)
+
+// run providers release every 5 mins
+#define WINDOWS_EVENTS_RELEASE_PROVIDERS_HANDLES_EVERY_UT (5 * 60 * USEC_PER_SEC)
+// release idle handles that are older than 5 mins
+#define WINDOWS_EVENTS_RELEASE_IDLE_PROVIDER_HANDLES_TIME_UT (5 * 60 * USEC_PER_SEC)
+
+#define WEVT_FIELD_COMPUTER "Computer"
+#define WEVT_FIELD_CHANNEL "Channel"
+#define WEVT_FIELD_PROVIDER "Provider"
+#define WEVT_FIELD_PROVIDER_GUID "ProviderGUID"
+#define WEVT_FIELD_EVENTRECORDID "EventRecordID"
+#define WEVT_FIELD_VERSION "Version"
+#define WEVT_FIELD_QUALIFIERS "Qualifiers"
+#define WEVT_FIELD_EVENTID "EventID"
+#define WEVT_FIELD_LEVEL "Level"
+#define WEVT_FIELD_KEYWORDS "Keywords"
+#define WEVT_FIELD_OPCODE "Opcode"
+#define WEVT_FIELD_ACCOUNT "UserAccount"
+#define WEVT_FIELD_DOMAIN "UserDomain"
+#define WEVT_FIELD_SID "UserSID"
+#define WEVT_FIELD_TASK "Task"
+#define WEVT_FIELD_PROCESSID "ProcessID"
+#define WEVT_FIELD_THREADID "ThreadID"
+#define WEVT_FIELD_ACTIVITY_ID "ActivityID"
+#define WEVT_FIELD_RELATED_ACTIVITY_ID "RelatedActivityID"
+#define WEVT_FIELD_XML "XML"
+#define WEVT_FIELD_MESSAGE "Message"
+#define WEVT_FIELD_EVENTS_API "EventsAPI"
+#define WEVT_FIELD_EVENT_DATA_HIDDEN "__HIDDEN__EVENT__DATA__"
+#define WEVT_FIELD_EVENT_MESSAGE_HIDDEN "__HIDDEN__MESSAGE__DATA__"
+#define WEVT_FIELD_EVENT_XML_HIDDEN "__HIDDEN__XML__DATA__"
+
+// functions needed by LQS
+
+// structures needed by LQS
+struct lqs_extension {
+ wchar_t *query;
+
+ struct {
+ struct {
+ size_t completed;
+ size_t total;
+ } queries;
+
+ struct {
+ size_t current_query_total;
+ size_t completed;
+ size_t total;
+ } entries;
+
+ usec_t last_ut;
+ } progress;
+
+ // struct {
+ // usec_t start_ut;
+ // usec_t stop_ut;
+ // usec_t first_msg_ut;
+ //
+ // uint64_t first_msg_seqnum;
+ // } query_file;
+
+ // struct {
+ // uint32_t enable_after_samples;
+ // uint32_t slots;
+ // uint32_t sampled;
+ // uint32_t unsampled;
+ // uint32_t estimated;
+ // } samples;
+
+ // struct {
+ // uint32_t enable_after_samples;
+ // uint32_t every;
+ // uint32_t skipped;
+ // uint32_t recalibrate;
+ // uint32_t sampled;
+ // uint32_t unsampled;
+ // uint32_t estimated;
+ // } samples_per_file;
+
+ // struct {
+ // usec_t start_ut;
+ // usec_t end_ut;
+ // usec_t step_ut;
+ // uint32_t enable_after_samples;
+ // uint32_t sampled[SYSTEMD_JOURNAL_SAMPLING_SLOTS];
+ // uint32_t unsampled[SYSTEMD_JOURNAL_SAMPLING_SLOTS];
+ // } samples_per_time_slot;
+
+ // per file progress info
+ // size_t cached_count;
+
+ // progress statistics
+ usec_t matches_setup_ut;
+ size_t rows_useful;
+ size_t rows_read;
+ size_t bytes_read;
+ size_t files_matched;
+ size_t file_working;
+};
+
+// prepare LQS
+#define LQS_DEFAULT_SLICE_MODE 0
+#define LQS_FUNCTION_NAME WEVT_FUNCTION_NAME
+#define LQS_FUNCTION_DESCRIPTION WEVT_FUNCTION_DESCRIPTION
+#define LQS_DEFAULT_ITEMS_PER_QUERY 200
+#define LQS_DEFAULT_ITEMS_SAMPLING 1000000
+#define LQS_SOURCE_TYPE WEVT_SOURCE_TYPE
+#define LQS_SOURCE_TYPE_ALL WEVTS_ALL
+#define LQS_SOURCE_TYPE_NONE WEVTS_NONE
+#define LQS_PARAMETER_SOURCE_NAME "Event Channels" // this is how it is shown to users
+#define LQS_FUNCTION_GET_INTERNAL_SOURCE_TYPE(value) WEVT_SOURCE_TYPE_2id_one(value)
+#define LQS_FUNCTION_SOURCE_TO_JSON_ARRAY(wb) wevt_sources_to_json_array(wb)
+#include "libnetdata/facets/logs_query_status.h"
+
+#include "windows-events-query-builder.h" // needs the LQS definition, so it has to be last
+
+#endif //NETDATA_WINDOWS_EVENTS_H