1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,300 @@
# Windows Eventlog Input Plugin
Telegraf's win_eventlog input plugin gathers metrics from the windows event log.
## Collect Windows Event Log messages
Supports Windows Vista and higher.
Telegraf should have Administrator permissions to subscribe for some of the
Windows Events Channels, like System Log.
Telegraf minimum version: Telegraf 1.16.0
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Input plugin to collect Windows Event Log messages
# This plugin ONLY supports Windows
[[inputs.win_eventlog]]
## Telegraf should have Administrator permissions to subscribe for some
## Windows Events channels (e.g. System log)
## LCID (Locale ID) for event rendering
## 1033 to force English language
## 0 to use default Windows locale
# locale = 0
## Name of eventlog, used only if xpath_query is empty
## Example: "Application"
# eventlog_name = ""
## xpath_query can be in defined short form like "Event/System[EventID=999]"
## or you can form a XML Query. Refer to the Consuming Events article:
## https://docs.microsoft.com/en-us/windows/win32/wes/consuming-events
## XML query is the recommended form, because it is most flexible
## You can create or debug XML Query by creating Custom View in Windows Event Viewer
## and then copying resulting XML here
xpath_query = '''
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">*</Select>
<Suppress Path="Security">*[System[( (EventID &gt;= 5152 and EventID &lt;= 5158) or EventID=5379 or EventID=4672)]]</Suppress>
</Query>
<Query Id="1" Path="Application">
<Select Path="Application">*[System[(Level &lt; 4)]]</Select>
</Query>
<Query Id="2" Path="Windows PowerShell">
<Select Path="Windows PowerShell">*[System[(Level &lt; 4)]]</Select>
</Query>
<Query Id="3" Path="System">
<Select Path="System">*</Select>
</Query>
<Query Id="4" Path="Setup">
<Select Path="Setup">*</Select>
</Query>
</QueryList>
'''
## When true, event logs are read from the beginning; otherwise only future
## events will be logged.
# from_beginning = false
## Number of events to fetch in one batch
# event_batch_size = 5
# Process UserData XML to fields, if this node exists in Event XML
# process_userdata = true
# Process EventData XML to fields, if this node exists in Event XML
# process_eventdata = true
## Separator character to use for unrolled XML Data field names
# separator = "_"
## Get only first line of Message field. For most events first line is
## usually more than enough
# only_first_line_of_message = true
## Parse timestamp from TimeCreated.SystemTime event field.
## Will default to current time of telegraf processing on parsing error or if
## set to false
# timestamp_from_event = true
## System field names:
## "Source", "EventID", "Version", "Level", "Task", "Opcode", "Keywords",
## "TimeCreated", "EventRecordID", "ActivityID", "RelatedActivityID",
## "ProcessID", "ThreadID", "ProcessName", "Channel", "Computer", "UserID",
## "UserName", "Message", "LevelText", "TaskText", "OpcodeText"
##
## In addition to System, Data fields can be unrolled from additional XML
## nodes in event. Human-readable representation of those nodes is formatted
## into event Message field, but XML is more machine-parsable
## Event fields to include as tags
## The values below are included by default.
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
# event_tags = ["Source", "EventID", "Level", "LevelText", "Task", "TaskText", "Opcode", "OpcodeText", "Keywords", "Channel", "Computer"]
## Event fields to include
## All fields are sent by default.
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
# event_fields = ["*"]
## Event fields to exclude
## Note that if you exclude all fields then no metrics are produced. A valid
## metric includes at least one field.
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
# exclude_fields = []
## Event fields to exclude if their value is empty or equals to zero
## The values below are included by default.
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
# exclude_empty = ["Task", "Opcode", "*ActivityID", "UserID"]
## Maximum memory size available for an event to render
## Events larger that that are not processed and will not create a metric.
## NOTE: As events are encoded in UTF-16 we need two bytes per character.
# event_size_limit = "64KB"
```
### Filtering
There are three types of filtering: **Event Log** name, **XPath Query** and
**XML Query**.
**Event Log** name filtering is simple:
```toml
eventlog_name = "Application"
xpath_query = '''
```
For **XPath Query** filtering set the `xpath_query` value, and `eventlog_name`
will be ignored:
```toml
eventlog_name = ""
xpath_query = "Event/System[EventID=999]"
```
**XML Query** is the most flexible: you can Select or Suppress any values, and
give ranges for other values. XML query is the recommended form, because it is
most flexible. You can create or debug XML Query by creating Custom View in
Windows Event Viewer and then copying resulting XML in config file.
XML Query documentation:
<https://docs.microsoft.com/en-us/windows/win32/wes/consuming-events>
## Troubleshooting
In case you see a `Collection took longer than expected` warning, there might
be a burst of events logged and the API is not able to deliver them fast enough
to complete processing within the specified interval. Tweaking the
`event_batch_size` setting might help to mitigate the issue.
The said warning does not indicate data-loss, but you should investigate the
amount of events you log.
## Metrics
You can send any field, *System*, *Computed* or *XML* as tag field. List of
those fields is in the `event_tags` config array. Globbing is supported in this
array, i.e. `Level*` for all fields beginning with `Level`, or `L?vel` for all
fields where the name is `Level`, `L3vel`, `L@vel` and so on. Tag fields are
converted to strings automatically.
By default, all other fields are sent, but you can limit that either by listing
it in `event_fields` config array with globbing, or by adding some field name
masks in the `exclude_fields` config array.
You can limit sending fields with empty values by adding masks of names of such
fields in the `exclude_empty` config array. Value considered empty, if the
System field of type `int` or `uint32` is equal to zero, or if any field of type
`string` is an empty string.
List of System fields:
- Source (string)
- EventID (int)
- Version (int)
- Level (int)
- LevelText (string)
- Opcode (int)
- OpcodeText (string)
- Task (int)
- TaskText (string)
- Keywords (string): comma-separated in case of multiple values
- TimeCreated (string)
- EventRecordID (string)
- ActivityID (string)
- RelatedActivityID (string)
- ProcessID (int)
- ThreadID (int)
- ProcessName (string): derived from ProcessID
- Channel (string)
- Computer (string): useful if consumed from Forwarded Events
- UserID (string): SID
- UserName (string): derived from UserID, presented in form of DOMAIN\Username
- Message (string)
### Computed fields
Fields `Level`, `Opcode` and `Task` are converted to text and saved as computed
`*Text` fields.
`Keywords` field is converted from hex uint64 value by the `_EvtFormatMessage`
WINAPI function. There can be more than one value, in that case they will be
comma-separated. If keywords can't be converted (bad device driver or forwarded
from another computer with unknown Event Channel), hex uint64 is saved as is.
`ProcessName` field is found by looking up ProcessID. Can be empty if telegraf
doesn't have enough permissions.
`Username` field is found by looking up SID from UserID.
`Message` field is rendered from the event data, and can be several kilobytes of
text with line breaks. For most events the first line of this text is more then
enough, and additional info is more useful to be parsed as XML fields. So, for
brevity, plugin takes only the first line. You can set
`only_first_line_of_message` parameter to `false` to take full message text.
`TimeCreated` field is a string in RFC3339Nano format. By default Telegraf
parses it as an event timestamp. If there is a field parse error or
`timestamp_from_event` configuration parameter is set to `false`, then event
timestamp will be set to the exact time when Telegraf has parsed this event, so
it will be rounded to the nearest minute.
### Additional Fields
The content of **Event Data** and **User Data** XML Nodes can be added as
additional fields, and is added by default. You can disable that by setting
`process_userdata` or `process_eventdata` parameters to `false`.
For the fields from additional XML Nodes the `Name` attribute is taken as the
name, and inner text is the value. Type of those fields is always string.
Name of the field is formed from XML Path by adding _ inbetween levels. For
example, if UserData XML looks like this:
```xml
<UserData>
<CbsPackageChangeState xmlns="http://manifests.microsoft.com/win/2004/08/windows/setup_provider">
<PackageIdentifier>KB4566782</PackageIdentifier>
<IntendedPackageState>5112</IntendedPackageState>
<IntendedPackageStateTextized>Installed</IntendedPackageStateTextized>
<ErrorCode>0x0</ErrorCode>
<Client>UpdateAgentLCU</Client>
</CbsPackageChangeState>
</UserData>
```
It will be converted to following fields:
```text
CbsPackageChangeState_PackageIdentifier = "KB4566782"
CbsPackageChangeState_IntendedPackageState = "5112"
CbsPackageChangeState_IntendedPackageStateTextized = "Installed"
CbsPackageChangeState_ErrorCode = "0x0"
CbsPackageChangeState_Client = "UpdateAgentLCU"
```
If there are more than one field with the same name, all those fields are given
suffix with number: `_1`, `_2` and so on.
## Localization
Human readable Event Description is in the Message field. But it is better to be
skipped in favour of the Event XML values, because they are more
machine-readable.
Keywords, LevelText, TaskText, OpcodeText and Message are saved with the current
Windows locale by default. You can override this, for example, to English locale
by setting `locale` config parameter to `1033`. Unfortunately, **Event Data**
and **User Data** XML Nodes are in default Windows locale only.
Locale should be present on the computer. English locale is usually available on
all localized versions of modern Windows. A list of all locales is available
from Microsoft's [Open Specifications][1].
[1]: https://docs.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085406-a698-4e12-9d4d-c3b0ee3dbc4a
## Example Output
Some values are changed for anonymity.
```text
win_eventlog,Channel=System,Computer=PC,EventID=105,Keywords=0x8000000000000000,Level=4,LevelText=Information,Opcode=10,OpcodeText=General,Source=WudfUsbccidDriver,Task=1,TaskText=Driver,host=PC ProcessName="WUDFHost.exe",UserName="NT AUTHORITY\\LOCAL SERVICE",Data_dwMaxCCIDMessageLength="271",Data_bPINSupport="0x0",Data_bMaxCCIDBusySlots="1",EventRecordID=1914688i,UserID="S-1-5-19",Version=0i,Data_bClassGetEnvelope="0x0",Data_wLcdLayout="0x0",Data_bClassGetResponse="0x0",TimeCreated="2020-08-21T08:43:26.7481077Z",Message="The Smartcard reader reported the following class descriptor (part 2)." 1597999410000000000
win_eventlog,Channel=Security,Computer=PC,EventID=4798,Keywords=Audit\ Success,Level=0,LevelText=Information,Opcode=0,OpcodeText=Info,Source=Microsoft-Windows-Security-Auditing,Task=13824,TaskText=User\ Account\ Management,host=PC Data_TargetDomainName="PC",Data_SubjectUserName="User",Data_CallerProcessId="0x3d5c",Data_SubjectLogonId="0x46d14f8d",Version=0i,EventRecordID=223157i,Message="A user's local group membership was enumerated.",Data_TargetUserName="User",Data_TargetSid="S-1-5-21-.-.-.-1001",Data_SubjectUserSid="S-1-5-21-.-.-.-1001",Data_CallerProcessName="C:\\Windows\\explorer.exe",ActivityID="{0d4cc11d-7099-0002-4dc1-4c0d9970d601}",UserID="",Data_SubjectDomainName="PC",TimeCreated="2020-08-21T08:43:27.3036771Z",ProcessName="lsass.exe" 1597999410000000000
win_eventlog,Channel=Microsoft-Windows-Dhcp-Client/Admin,Computer=PC,EventID=1002,Keywords=0x4000000000000001,Level=2,LevelText=Error,Opcode=76,OpcodeText=IpLeaseDenied,Source=Microsoft-Windows-Dhcp-Client,Task=3,TaskText=Address\ Configuration\ State\ Event,host=PC Version=0i,Message="The IP address lease 10.20.30.40 for the Network Card with network address 0xaabbccddeeff has been denied by the DHCP server 10.20.30.1 (The DHCP Server sent a DHCPNACK message).",UserID="S-1-5-19",Data_HWLength="6",Data_HWAddress="545595B7EA01",TimeCreated="2020-08-21T08:43:42.8265853Z",EventRecordID=34i,ProcessName="svchost.exe",UserName="NT AUTHORITY\\LOCAL SERVICE" 1597999430000000000
win_eventlog,Channel=System,Computer=PC,EventID=10016,Keywords=Classic,Level=3,LevelText=Warning,Opcode=0,OpcodeText=Info,Source=Microsoft-Windows-DistributedCOM,Task=0,host=PC Data_param3="Активация",Data_param6="PC",Data_param8="S-1-5-21-2007059868-50816014-3139024325-1001",Version=0i,UserName="PC\\User",Data_param1="по умолчанию для компьютера",Data_param2="Локально",Data_param7="User",Data_param9="LocalHost (с использованием LRPC)",Data_param10="Microsoft.Windows.ShellExperienceHost_10.0.19041.423_neutral_neutral_cw5n1h2txyewy",ActivityID="{839cac9e-73a1-4559-a847-62f3a5e73e44}",ProcessName="svchost.exe",Message="The по умолчанию для компьютера permission settings do not grant Локально Активация permission for the COM Server application with CLSID ",Data_param5="{316CDED5-E4AE-4B15-9113-7055D84DCC97}",Data_param11="S-1-15-2-.-.-.-.-.-.-2861478708",TimeCreated="2020-08-21T08:43:45.5233759Z",EventRecordID=1914689i,UserID="S-1-5-21-.-.-.-1001",Data_param4="{C2F03A33-21F5-47FA-B4BB-156362A2F239}" 1597999430000000000
```

View file

@ -0,0 +1,82 @@
//go:build windows
package win_eventlog
// event is the event entry representation
// Only the most common elements are processed, human-readable data is rendered in Message
// More info on schema, if there will be need to add more:
// https://docs.microsoft.com/en-us/windows/win32/wes/eventschema-elements
type event struct {
Source provider `xml:"System>Provider"`
EventID int `xml:"System>EventID"`
Version int `xml:"System>Version"`
Level int `xml:"System>Level"`
Task int `xml:"System>Task"`
Opcode int `xml:"System>Opcode"`
Keywords string `xml:"System>Keywords"`
TimeCreated timeCreated `xml:"System>TimeCreated"`
EventRecordID int `xml:"System>EventRecordID"`
Correlation correlation `xml:"System>Correlation"`
Execution execution `xml:"System>Execution"`
Channel string `xml:"System>Channel"`
Computer string `xml:"System>Computer"`
Security security `xml:"System>Security"`
UserData userData `xml:"UserData"`
EventData eventData `xml:"EventData"`
RenderingInfo *renderingInfo `xml:"RenderingInfo"`
Message string
LevelText string
TaskText string
OpcodeText string
}
// userData Application-provided XML data
type userData struct {
InnerXML []byte `xml:",innerxml"`
}
// eventData Application-provided XML data
type eventData struct {
InnerXML []byte `xml:",innerxml"`
}
// provider is the event provider information
type provider struct {
Name string `xml:"Name,attr"`
}
// correlation is used for the event grouping
type correlation struct {
ActivityID string `xml:"ActivityID,attr"`
RelatedActivityID string `xml:"RelatedActivityID,attr"`
}
// execution Info for event
type execution struct {
ProcessID uint32 `xml:"ProcessID,attr"`
ThreadID uint32 `xml:"ThreadID,attr"`
ProcessName string
}
// security Data for event
type security struct {
UserID string `xml:"UserID,attr"`
}
// timeCreated field for event
type timeCreated struct {
SystemTime string `xml:"SystemTime,attr"`
}
// renderingInfo is provided for events forwarded by Windows event Collector
// see https://learn.microsoft.com/en-us/windows/win32/api/winevt/nf-winevt-evtformatmessage#parameters
type renderingInfo struct {
Message string `xml:"Message"`
Level string `xml:"Level"`
Task string `xml:"Task"`
Opcode string `xml:"Opcode"`
Channel string `xml:"Channel"`
Provider string `xml:"Provider"`
Keywords []string `xml:"Keywords>Keyword"`
}

View file

@ -0,0 +1,102 @@
# Input plugin to collect Windows Event Log messages
# This plugin ONLY supports Windows
[[inputs.win_eventlog]]
## Telegraf should have Administrator permissions to subscribe for some
## Windows Events channels (e.g. System log)
## LCID (Locale ID) for event rendering
## 1033 to force English language
## 0 to use default Windows locale
# locale = 0
## Name of eventlog, used only if xpath_query is empty
## Example: "Application"
# eventlog_name = ""
## xpath_query can be in defined short form like "Event/System[EventID=999]"
## or you can form a XML Query. Refer to the Consuming Events article:
## https://docs.microsoft.com/en-us/windows/win32/wes/consuming-events
## XML query is the recommended form, because it is most flexible
## You can create or debug XML Query by creating Custom View in Windows Event Viewer
## and then copying resulting XML here
xpath_query = '''
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">*</Select>
<Suppress Path="Security">*[System[( (EventID &gt;= 5152 and EventID &lt;= 5158) or EventID=5379 or EventID=4672)]]</Suppress>
</Query>
<Query Id="1" Path="Application">
<Select Path="Application">*[System[(Level &lt; 4)]]</Select>
</Query>
<Query Id="2" Path="Windows PowerShell">
<Select Path="Windows PowerShell">*[System[(Level &lt; 4)]]</Select>
</Query>
<Query Id="3" Path="System">
<Select Path="System">*</Select>
</Query>
<Query Id="4" Path="Setup">
<Select Path="Setup">*</Select>
</Query>
</QueryList>
'''
## When true, event logs are read from the beginning; otherwise only future
## events will be logged.
# from_beginning = false
## Number of events to fetch in one batch
# event_batch_size = 5
# Process UserData XML to fields, if this node exists in Event XML
# process_userdata = true
# Process EventData XML to fields, if this node exists in Event XML
# process_eventdata = true
## Separator character to use for unrolled XML Data field names
# separator = "_"
## Get only first line of Message field. For most events first line is
## usually more than enough
# only_first_line_of_message = true
## Parse timestamp from TimeCreated.SystemTime event field.
## Will default to current time of telegraf processing on parsing error or if
## set to false
# timestamp_from_event = true
## System field names:
## "Source", "EventID", "Version", "Level", "Task", "Opcode", "Keywords",
## "TimeCreated", "EventRecordID", "ActivityID", "RelatedActivityID",
## "ProcessID", "ThreadID", "ProcessName", "Channel", "Computer", "UserID",
## "UserName", "Message", "LevelText", "TaskText", "OpcodeText"
##
## In addition to System, Data fields can be unrolled from additional XML
## nodes in event. Human-readable representation of those nodes is formatted
## into event Message field, but XML is more machine-parsable
## Event fields to include as tags
## The values below are included by default.
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
# event_tags = ["Source", "EventID", "Level", "LevelText", "Task", "TaskText", "Opcode", "OpcodeText", "Keywords", "Channel", "Computer"]
## Event fields to include
## All fields are sent by default.
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
# event_fields = ["*"]
## Event fields to exclude
## Note that if you exclude all fields then no metrics are produced. A valid
## metric includes at least one field.
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
# exclude_fields = []
## Event fields to exclude if their value is empty or equals to zero
## The values below are included by default.
## Globbing supported (e.g. "Level*" matches both "Level" and "LevelText")
# exclude_empty = ["Task", "Opcode", "*ActivityID", "UserID"]
## Maximum memory size available for an event to render
## Events larger that that are not processed and will not create a metric.
## NOTE: As events are encoded in UTF-16 we need two bytes per character.
# event_size_limit = "64KB"

View file

@ -0,0 +1,36 @@
//go:build windows
package win_eventlog
import "syscall"
// event log error codes.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
const (
errInsufficientBuffer syscall.Errno = 122
errNoMoreItems syscall.Errno = 259
errInvalidOperation syscall.Errno = 4317
)
// evtSubscribeFlag defines the possible values that specify when to start subscribing to events.
type evtSubscribeFlag uint32
// EVT_SUBSCRIBE_FLAGS enumeration
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa385588(v=vs.85).aspx
const (
evtSubscribeToFutureEvents evtSubscribeFlag = 1
evtSubscribeStartAtOldestRecord evtSubscribeFlag = 2
evtSubscribeStartAfterBookmark evtSubscribeFlag = 3
)
// evtRenderFlag uint32
type evtRenderFlag uint32
// EVT_RENDER_FLAGS enumeration
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa385563(v=vs.85).aspx
const (
// Render the event as an XML string. For details on the contents of the XML string, see the event schema.
evtRenderEventXML evtRenderFlag = 1
// Render bookmark
evtRenderBookmark evtRenderFlag = 2
)

View file

@ -0,0 +1,148 @@
//go:build windows
package win_eventlog
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"strings"
"unicode/utf16"
"unicode/utf8"
"unsafe"
"golang.org/x/sys/windows"
)
// decodeUTF16 to UTF8 bytes
func decodeUTF16(b []byte) ([]byte, error) {
if len(b)%2 != 0 {
return nil, errors.New("must have even length byte slice")
}
u16s := make([]uint16, 1)
ret := &bytes.Buffer{}
b8buf := make([]byte, 4)
lb := len(b)
for i := 0; i < lb; i += 2 {
u16s[0] = uint16(b[i]) + (uint16(b[i+1]) << 8)
r := utf16.Decode(u16s)
n := utf8.EncodeRune(b8buf, r[0])
ret.Write(b8buf[:n])
}
return ret.Bytes(), nil
}
// getFromSnapProcess finds information about process by the given pid
// Returns process name
func getFromSnapProcess(pid uint32) (string, error) {
snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, pid)
if err != nil {
return "", err
}
defer windows.CloseHandle(snap)
var pe32 windows.ProcessEntry32
pe32.Size = uint32(unsafe.Sizeof(pe32))
if err := windows.Process32First(snap, &pe32); err != nil {
return "", err
}
for {
if pe32.ProcessID == pid {
szexe := windows.UTF16ToString(pe32.ExeFile[:])
return szexe, nil
}
if err = windows.Process32Next(snap, &pe32); err != nil {
break
}
}
return "", fmt.Errorf("couldn't find pid: %d", pid)
}
type xmlnode struct {
XMLName xml.Name
Attrs []xml.Attr `xml:"-"`
Content []byte `xml:",innerxml"`
Text string `xml:",chardata"`
Nodes []xmlnode `xml:",any"`
}
// eventField for unique rendering
type eventField struct {
Name string
Value string
}
// UnmarshalXML redefined for xml elements walk
func (n *xmlnode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
n.Attrs = start.Attr
type node xmlnode
return d.DecodeElement((*node)(n), &start)
}
// unrollXMLFields extracts fields from xml data
func unrollXMLFields(data []byte, fieldsUsage map[string]int, separator string) ([]eventField, map[string]int) {
buf := bytes.NewBuffer(data)
dec := xml.NewDecoder(buf)
var fields []eventField
for {
var node xmlnode
err := dec.Decode(&node)
if err != nil {
break
}
var parents []string
walkXML([]xmlnode{node}, parents, separator, func(node xmlnode, parents []string, separator string) bool {
innerText := strings.TrimSpace(node.Text)
if len(innerText) > 0 {
valueName := strings.Join(parents, separator)
fieldsUsage[valueName]++
field := eventField{Name: valueName, Value: innerText}
fields = append(fields, field)
}
return true
})
}
return fields, fieldsUsage
}
func walkXML(nodes []xmlnode, parents []string, separator string, f func(xmlnode, []string, string) bool) {
for _, node := range nodes {
parentName := node.XMLName.Local
for _, attr := range node.Attrs {
attrName := strings.ToLower(attr.Name.Local)
if attrName == "name" {
// Add Name attribute to parent name
parentName = strings.Join([]string{parentName, attr.Value}, separator)
}
}
nodeParents := append(parents, parentName)
if f(node, nodeParents, separator) {
walkXML(node.Nodes, nodeParents, separator, f)
}
}
}
// uniqueFieldNames forms unique field names by adding _<num> if there are several of them
func uniqueFieldNames(fields []eventField, fieldsUsage map[string]int, separator string) []eventField {
var fieldsCounter = make(map[string]int, len(fields))
fieldsUnique := make([]eventField, 0, len(fields))
for _, field := range fields {
fieldName := field.Name
if fieldsUsage[field.Name] > 1 {
fieldsCounter[field.Name]++
fieldName = fmt.Sprint(field.Name, separator, fieldsCounter[field.Name])
}
fieldsUnique = append(fieldsUnique, eventField{
Name: fieldName,
Value: field.Value,
})
}
return fieldsUnique
}

View file

@ -0,0 +1,198 @@
//go:build windows
package win_eventlog
import (
"bytes"
"encoding/binary"
"encoding/xml"
"io"
"reflect"
"testing"
"unicode/utf16"
)
func TestDecodeUTF16(t *testing.T) {
testString := "Test String"
utf16s := utf16.Encode([]rune(testString))
var bytesUtf16 bytes.Buffer
writer := io.Writer(&bytesUtf16)
lb := len(utf16s)
for i := 0; i < lb; i++ {
word := make([]byte, 2)
binary.LittleEndian.PutUint16(word, utf16s[i])
_, err := writer.Write(word)
if err != nil {
t.Errorf("error preparing UTF-16 test string")
return
}
}
type args struct {
b []byte
}
tests := []struct {
name string
args args
want []byte
wantErr bool
}{
{
name: "Wrong UTF-16",
args: args{b: append(bytesUtf16.Bytes(), byte('\x00'))},
wantErr: true,
},
{
name: "UTF-16",
args: args{b: bytesUtf16.Bytes()},
want: []byte(testString),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := decodeUTF16(tt.args.b)
if (err != nil) != tt.wantErr {
t.Errorf("decodeUTF16() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("decodeUTF16() = %v, want %v", got, tt.want)
}
})
}
}
var xmlbroken = `
<BrokenXML>
<Data/>qq</Data>
</BrokenXML>
`
var xmldata = `
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<UserData>
<CbsPackageChangeState xmlns="http://manifests.microsoft.com/win/2004/08/windows/setup_provider">
<IntendedPackageState>5111</IntendedPackageState>
<ErrorCode><Code>0x0</Code></ErrorCode>
</CbsPackageChangeState>
</UserData>
<EventData>
<Data>2120-07-26T15:24:25Z</Data>
<Data>RulesEngine</Data>
<Data Name="Engine">RulesEngine</Data>
</EventData>
</Event>
`
type testEvent struct {
UserData struct {
InnerXML []byte `xml:",innerxml"`
} `xml:"UserData"`
EventData struct {
InnerXML []byte `xml:",innerxml"`
} `xml:"EventData"`
}
func TestUnrollXMLFields(t *testing.T) {
container := testEvent{}
err := xml.Unmarshal([]byte(xmldata), &container)
if err != nil {
t.Errorf("couldn't unmarshal precooked xml string xmldata")
return
}
type args struct {
data []byte
fieldsUsage map[string]int
}
tests := []struct {
name string
args args
want1 []eventField
want2 map[string]int
}{
{
name: "Broken XML",
args: args{
data: []byte(xmlbroken),
fieldsUsage: map[string]int{},
},
want1: nil,
want2: map[string]int{},
},
{
name: "EventData with non-unique names and one Name attr",
args: args{
data: container.EventData.InnerXML,
fieldsUsage: map[string]int{},
},
want1: []eventField{
{Name: "Data", Value: "2120-07-26T15:24:25Z"},
{Name: "Data", Value: "RulesEngine"},
{Name: "Data_Engine", Value: "RulesEngine"},
},
want2: map[string]int{"Data": 2, "Data_Engine": 1},
},
{
name: "UserData with non-unique names and three levels of depth",
args: args{
data: container.UserData.InnerXML,
fieldsUsage: map[string]int{},
},
want1: []eventField{
{Name: "CbsPackageChangeState_IntendedPackageState", Value: "5111"},
{Name: "CbsPackageChangeState_ErrorCode_Code", Value: "0x0"},
},
want2: map[string]int{
"CbsPackageChangeState_ErrorCode_Code": 1,
"CbsPackageChangeState_IntendedPackageState": 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1 := unrollXMLFields(tt.args.data, tt.args.fieldsUsage, "_")
if !reflect.DeepEqual(got, tt.want1) {
t.Errorf("ExtractFields() got = %v, want %v", got, tt.want1)
}
if !reflect.DeepEqual(got1, tt.want2) {
t.Errorf("ExtractFields() got1 = %v, want %v", got1, tt.want2)
}
})
}
}
func TestUniqueFieldNames(t *testing.T) {
type args struct {
fields []eventField
fieldsUsage map[string]int
}
tests := []struct {
name string
args args
want []eventField
}{
{
name: "Unique values",
args: args{
fields: []eventField{
{Name: "Data", Value: "2120-07-26T15:24:25Z"},
{Name: "Data", Value: "RulesEngine"},
{Name: "Engine", Value: "RulesEngine"},
},
fieldsUsage: map[string]int{"Data": 2, "Engine": 1},
},
want: []eventField{
{Name: "Data_1", Value: "2120-07-26T15:24:25Z"},
{Name: "Data_2", Value: "RulesEngine"},
{Name: "Engine", Value: "RulesEngine"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := uniqueFieldNames(tt.args.fields, tt.args.fieldsUsage, "_"); !reflect.DeepEqual(got, tt.want) {
t.Errorf("PrintFields() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -0,0 +1,604 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build windows
package win_eventlog
import (
"bufio"
"bytes"
_ "embed"
"encoding/xml"
"errors"
"fmt"
"math"
"reflect"
"strings"
"syscall"
"time"
"golang.org/x/sys/windows"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/filter"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
var errEventTooLarge = errors.New("event too large")
type WinEventLog struct {
Locale uint32 `toml:"locale"`
EventlogName string `toml:"eventlog_name"`
Query string `toml:"xpath_query"`
FromBeginning bool `toml:"from_beginning"`
BatchSize uint32 `toml:"event_batch_size"`
ProcessUserData bool `toml:"process_userdata"`
ProcessEventData bool `toml:"process_eventdata"`
Separator string `toml:"separator"`
OnlyFirstLineOfMessage bool `toml:"only_first_line_of_message"`
TimeStampFromEvent bool `toml:"timestamp_from_event"`
EventTags []string `toml:"event_tags"`
EventFields []string `toml:"event_fields"`
ExcludeFields []string `toml:"exclude_fields"`
ExcludeEmpty []string `toml:"exclude_empty"`
EventSizeLimit config.Size `toml:"event_size_limit"`
Log telegraf.Logger `toml:"-"`
subscription evtHandle
subscriptionFlag evtSubscribeFlag
bookmark evtHandle
tagFilter filter.Filter
fieldFilter filter.Filter
fieldEmptyFilter filter.Filter
}
func (*WinEventLog) SampleConfig() string {
return sampleConfig
}
func (w *WinEventLog) Init() error {
// Set defaults
if w.BatchSize < 1 {
w.BatchSize = 5
}
w.subscriptionFlag = evtSubscribeToFutureEvents
if w.FromBeginning {
w.subscriptionFlag = evtSubscribeStartAtOldestRecord
}
if w.Query == "" {
w.Query = "*"
}
if w.EventSizeLimit == 0 {
w.EventSizeLimit = config.Size(64 * 1024) // 64kb
} else if w.EventSizeLimit > math.MaxUint32 {
// Clip the size to not overflow
w.EventSizeLimit = config.Size(math.MaxUint32)
}
bookmark, err := evtCreateBookmark(nil)
if err != nil {
return err
}
w.bookmark = bookmark
if w.tagFilter, err = filter.Compile(w.EventTags); err != nil {
return fmt.Errorf("creating tag filter failed: %w", err)
}
if w.fieldFilter, err = filter.NewIncludeExcludeFilter(w.EventFields, w.ExcludeFields); err != nil {
return fmt.Errorf("creating field filter failed: %w", err)
}
if w.fieldEmptyFilter, err = filter.Compile(w.ExcludeEmpty); err != nil {
return fmt.Errorf("creating empty fields filter failed: %w", err)
}
return nil
}
func (w *WinEventLog) Start(telegraf.Accumulator) error {
subscription, err := w.evtSubscribe()
if err != nil {
return fmt.Errorf("subscription of Windows Event Log failed: %w", err)
}
w.subscription = subscription
w.Log.Debug("Subscription handle id:", w.subscription)
return nil
}
func (w *WinEventLog) GetState() interface{} {
bookmarkXML, err := w.renderBookmark()
if err != nil {
w.Log.Errorf("State-persistence failed, cannot render bookmark: %v", err)
return ""
}
return bookmarkXML
}
func (w *WinEventLog) SetState(state interface{}) error {
bookmarkXML, ok := state.(string)
if !ok {
return fmt.Errorf("invalid type %T for state", state)
}
ptr, err := syscall.UTF16PtrFromString(bookmarkXML)
if err != nil {
return fmt.Errorf("conversion to pointer failed: %w", err)
}
bookmark, err := evtCreateBookmark(ptr)
if err != nil {
return fmt.Errorf("creating bookmark failed: %w", err)
}
w.bookmark = bookmark
w.subscriptionFlag = evtSubscribeStartAfterBookmark
return nil
}
func (w *WinEventLog) Gather(acc telegraf.Accumulator) error {
for {
events, err := w.fetchEvents(w.subscription)
if err != nil {
if errors.Is(err, errNoMoreItems) {
break
}
w.Log.Errorf("Error getting events: %v", err)
return err
}
for i := range events {
// Prepare fields names usage counter
fieldsUsage := make(map[string]int)
tags := make(map[string]string)
fields := make(map[string]interface{})
event := events[i]
evt := reflect.ValueOf(&event).Elem()
timeStamp := time.Now()
// Walk through all fields of event struct to process System tags or fields
for i := 0; i < evt.NumField(); i++ {
fieldName := evt.Type().Field(i).Name
fieldType := evt.Field(i).Type().String()
fieldValue := evt.Field(i).Interface()
computedValues := make(map[string]interface{})
switch fieldName {
case "Source":
fieldValue = event.Source.Name
fieldType = reflect.TypeOf(fieldValue).String()
case "Execution":
fieldValue := event.Execution.ProcessID
fieldType = reflect.TypeOf(fieldValue).String()
fieldName = "ProcessID"
// Look up Process Name from pid
if should, _ := w.shouldProcessField("ProcessName"); should {
processName, err := getFromSnapProcess(fieldValue)
if err == nil {
computedValues["ProcessName"] = processName
}
}
case "TimeCreated":
fieldValue = event.TimeCreated.SystemTime
fieldType = reflect.TypeOf(fieldValue).String()
if w.TimeStampFromEvent {
timeStamp, err = time.Parse(time.RFC3339Nano, fmt.Sprintf("%v", fieldValue))
if err != nil {
w.Log.Warnf("Error parsing timestamp %q: %v", fieldValue, err)
}
}
case "Correlation":
if should, _ := w.shouldProcessField("ActivityID"); should {
activityID := event.Correlation.ActivityID
if len(activityID) > 0 {
computedValues["ActivityID"] = activityID
}
}
if should, _ := w.shouldProcessField("RelatedActivityID"); should {
relatedActivityID := event.Correlation.RelatedActivityID
if len(relatedActivityID) > 0 {
computedValues["RelatedActivityID"] = relatedActivityID
}
}
case "Security":
computedValues["UserID"] = event.Security.UserID
// Look up UserName and Domain from SID
if should, _ := w.shouldProcessField("UserName"); should {
sid := event.Security.UserID
usid, err := syscall.StringToSid(sid)
if err == nil {
username, domain, _, err := usid.LookupAccount("")
if err == nil {
computedValues["UserName"] = fmt.Sprint(domain, "\\", username)
}
}
}
}
if should, where := w.shouldProcessField(fieldName); should {
if where == "tags" {
strValue := fmt.Sprintf("%v", fieldValue)
if !w.shouldExcludeEmptyField(fieldName, "string", strValue) {
tags[fieldName] = strValue
fieldsUsage[fieldName]++
}
} else if where == "fields" {
if !w.shouldExcludeEmptyField(fieldName, fieldType, fieldValue) {
fields[fieldName] = fieldValue
fieldsUsage[fieldName]++
}
}
}
// Insert computed fields
for computedKey, computedValue := range computedValues {
if should, where := w.shouldProcessField(computedKey); should {
if where == "tags" {
tags[computedKey] = fmt.Sprintf("%v", computedValue)
fieldsUsage[computedKey]++
} else if where == "fields" {
fields[computedKey] = computedValue
fieldsUsage[computedKey]++
}
}
}
}
// Unroll additional XML
var xmlFields []eventField
if w.ProcessUserData {
fieldsUserData, xmlFieldsUsage := unrollXMLFields(event.UserData.InnerXML, fieldsUsage, w.Separator)
xmlFields = append(xmlFields, fieldsUserData...)
fieldsUsage = xmlFieldsUsage
}
if w.ProcessEventData {
fieldsEventData, xmlFieldsUsage := unrollXMLFields(event.EventData.InnerXML, fieldsUsage, w.Separator)
xmlFields = append(xmlFields, fieldsEventData...)
fieldsUsage = xmlFieldsUsage
}
uniqueXMLFields := uniqueFieldNames(xmlFields, fieldsUsage, w.Separator)
for _, xmlField := range uniqueXMLFields {
should, where := w.shouldProcessField(xmlField.Name)
if !should {
continue
}
if where == "tags" {
tags[xmlField.Name] = xmlField.Value
} else {
fields[xmlField.Name] = xmlField.Value
}
}
// Pass collected metrics
acc.AddFields("win_eventlog", fields, tags, timeStamp)
}
}
return nil
}
func (w *WinEventLog) Stop() {
//nolint:errcheck // ending the subscription, error can be ignored
_ = evtClose(w.subscription)
}
func (w *WinEventLog) shouldProcessField(field string) (should bool, list string) {
if w.tagFilter != nil && w.tagFilter.Match(field) {
return true, "tags"
}
if w.fieldFilter.Match(field) {
return true, "fields"
}
return false, "excluded"
}
func (w *WinEventLog) shouldExcludeEmptyField(field, fieldType string, fieldValue interface{}) (should bool) {
if w.fieldEmptyFilter == nil || !w.fieldEmptyFilter.Match(field) {
return false
}
switch fieldType {
case "string":
return len(fieldValue.(string)) < 1
case "int":
return fieldValue.(int) == 0
case "uint32":
return fieldValue.(uint32) == 0
}
return false
}
func (w *WinEventLog) evtSubscribe() (evtHandle, error) {
sigEvent, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
return 0, err
}
defer windows.CloseHandle(sigEvent)
logNamePtr, err := syscall.UTF16PtrFromString(w.EventlogName)
if err != nil {
return 0, err
}
xqueryPtr, err := syscall.UTF16PtrFromString(w.Query)
if err != nil {
return 0, err
}
var bookmark evtHandle
if w.subscriptionFlag == evtSubscribeStartAfterBookmark {
bookmark = w.bookmark
}
subsHandle, err := evtSubscribe(0, uintptr(sigEvent), logNamePtr, xqueryPtr, bookmark, 0, 0, w.subscriptionFlag)
if err != nil {
return 0, err
}
return subsHandle, nil
}
func (w *WinEventLog) fetchEventHandles(subsHandle evtHandle) ([]evtHandle, error) {
var evtReturned uint32
eventHandles := make([]evtHandle, w.BatchSize)
if err := evtNext(subsHandle, w.BatchSize, &eventHandles[0], 0, 0, &evtReturned); err != nil {
if errors.Is(err, errInvalidOperation) && evtReturned == 0 {
return nil, errNoMoreItems
}
return nil, err
}
return eventHandles[:evtReturned], nil
}
func (w *WinEventLog) fetchEvents(subsHandle evtHandle) ([]event, error) {
var events []event
eventHandles, err := w.fetchEventHandles(subsHandle)
if err != nil {
return nil, err
}
var evterr error
for _, eventHandle := range eventHandles {
if eventHandle == 0 {
continue
}
if event, err := w.renderEvent(eventHandle); err != nil {
w.Log.Errorf("Rendering event failed: %v", err)
} else {
events = append(events, event)
}
if err := evtUpdateBookmark(w.bookmark, eventHandle); err != nil {
w.Log.Errorf("Updateing bookmark failed: %v", err)
if evterr == nil {
evterr = err
}
}
if err := evtClose(eventHandle); err != nil {
w.Log.Errorf("Closing event failed: %v", err)
if evterr == nil {
evterr = err
}
}
}
return events, evterr
}
func (w *WinEventLog) renderBookmark() (string, error) {
// Determine the buffer size required
var used uint32
err := evtRender(w.bookmark, evtRenderBookmark, 0, nil, &used)
if err != nil && !errors.Is(err, errInsufficientBuffer) {
return "", err
}
// Actually retrieve the data
buf := make([]byte, used)
if err := evtRender(w.bookmark, evtRenderBookmark, uint32(len(buf)), &buf[0], &used); err != nil {
return "", err
}
// Decocde the charset
decoded, err := decodeUTF16(buf[:used])
if err != nil {
return "", err
}
// Strip the trailing null character if any
if decoded[len(decoded)-1] == 0 {
decoded = decoded[:len(decoded)-1]
}
return string(decoded), err
}
func (w *WinEventLog) renderEvent(eventHandle evtHandle) (event, error) {
// Determine the size of the buffer and grow the buffer if necessary
var used uint32
err := evtRender(eventHandle, evtRenderEventXML, 0, nil, &used)
if err != nil && !errors.Is(err, errInsufficientBuffer) {
return event{}, err
}
// If the event size exceeds the limit exit early as truncating the event
// data would destroy the XML structure.
if used > uint32(w.EventSizeLimit) {
return event{}, errEventTooLarge
}
// Actually retrieve the event
buf := make([]byte, used)
if err := evtRender(eventHandle, evtRenderEventXML, uint32(len(buf)), &buf[0], &used); err != nil {
return event{}, err
}
// Decode the charset
eventXML, err := decodeUTF16(buf[:used])
if err != nil {
return event{}, err
}
// Unmarshal the event XML. For forwarded events, this can fail but we can
// return the event without most text values, that way we will not lose
// information.
var evt event
if err := xml.Unmarshal(eventXML, &evt); err != nil {
//nolint:nilerr // This can happen when processing Forwarded Events
return evt, nil
}
// Do resolve local messages the usual way, while using built-in information for events forwarded by WEC.
// This is a safety measure as the underlying Windows-internal EvtFormatMessage might segfault in cases
// where the publisher (i.e. the remote machine which forwarded the event) is unavailable e.g. due to
// a reboot. See https://github.com/influxdata/telegraf/issues/12328 for the full story.
if evt.RenderingInfo == nil {
return w.renderLocalMessage(evt, eventHandle)
}
// We got 'RenderInfo' elements, so try to apply them in the following function
return w.renderRemoteMessage(evt)
}
func (w *WinEventLog) renderLocalMessage(event event, eventHandle evtHandle) (event, error) {
publisherHandle, err := openPublisherMetadata(0, event.Source.Name, w.Locale)
if err != nil {
return event, nil
}
defer evtClose(publisherHandle) //nolint:errcheck // Ignore error returned during Close
// Populating text values
keywords, err := formatEventString(evtFormatMessageKeyword, eventHandle, publisherHandle)
if err == nil {
event.Keywords = keywords
}
message, err := formatEventString(evtFormatMessageEvent, eventHandle, publisherHandle)
if err == nil {
if w.OnlyFirstLineOfMessage {
scanner := bufio.NewScanner(strings.NewReader(message))
scanner.Scan()
message = scanner.Text()
}
event.Message = message
}
level, err := formatEventString(evtFormatMessageLevel, eventHandle, publisherHandle)
if err == nil {
event.LevelText = level
}
task, err := formatEventString(evtFormatMessageTask, eventHandle, publisherHandle)
if err == nil {
event.TaskText = task
}
opcode, err := formatEventString(evtFormatMessageOpcode, eventHandle, publisherHandle)
if err == nil {
event.OpcodeText = opcode
}
return event, nil
}
func (w *WinEventLog) renderRemoteMessage(event event) (event, error) {
// Populating text values from RenderingInfo part of the XML
if len(event.RenderingInfo.Keywords) > 0 {
event.Keywords = strings.Join(event.RenderingInfo.Keywords, ",")
}
if event.RenderingInfo.Message != "" {
message := event.RenderingInfo.Message
if w.OnlyFirstLineOfMessage {
scanner := bufio.NewScanner(strings.NewReader(message))
scanner.Scan()
message = scanner.Text()
}
event.Message = message
}
if event.RenderingInfo.Level != "" {
event.LevelText = event.RenderingInfo.Level
}
if event.RenderingInfo.Task != "" {
event.TaskText = event.RenderingInfo.Task
}
if event.RenderingInfo.Opcode != "" {
event.OpcodeText = event.RenderingInfo.Opcode
}
return event, nil
}
func formatEventString(messageFlag evtFormatMessageFlag, eventHandle, publisherHandle evtHandle) (string, error) {
var bufferUsed uint32
err := evtFormatMessage(publisherHandle, eventHandle, 0, 0, 0, messageFlag, 0, nil, &bufferUsed)
if err != nil && !errors.Is(err, errInsufficientBuffer) {
return "", err
}
// Handle empty elements
if bufferUsed < 1 {
return "", nil
}
bufferUsed *= 2
buffer := make([]byte, bufferUsed)
bufferUsed = 0
err = evtFormatMessage(publisherHandle, eventHandle, 0, 0, 0, messageFlag,
uint32(len(buffer)/2), &buffer[0], &bufferUsed)
if err != nil {
return "", err
}
bufferUsed *= 2
result, err := decodeUTF16(buffer[:bufferUsed])
if err != nil {
return "", err
}
var out string
if messageFlag == evtFormatMessageKeyword {
// Keywords are returned as array of a zero-terminated strings
splitZero := func(c rune) bool { return c == '\x00' }
eventKeywords := strings.FieldsFunc(string(result), splitZero)
// So convert them to comma-separated string
out = strings.Join(eventKeywords, ",")
} else {
result := bytes.Trim(result, "\x00")
out = string(result)
}
return out, nil
}
// openPublisherMetadata opens a handle to the publisher's metadata. Close must
// be called on returned evtHandle when finished with the handle.
func openPublisherMetadata(session evtHandle, publisherName string, lang uint32) (evtHandle, error) {
p, err := syscall.UTF16PtrFromString(publisherName)
if err != nil {
return 0, err
}
h, err := evtOpenPublisherMetadata(session, p, nil, lang, 0)
if err != nil {
return 0, err
}
return h, nil
}
func init() {
inputs.Add("win_eventlog", func() telegraf.Input {
return &WinEventLog{
ProcessUserData: true,
ProcessEventData: true,
Separator: "_",
OnlyFirstLineOfMessage: true,
TimeStampFromEvent: true,
EventTags: []string{"Source", "EventID", "Level", "LevelText", "Keywords", "Channel", "Computer"},
ExcludeEmpty: []string{"Task", "Opcode", "*ActivityID", "UserID"},
}
})
}

View file

@ -0,0 +1,33 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build !windows
package win_eventlog
import (
_ "embed"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
type WinEventLog struct {
Log telegraf.Logger `toml:"-"`
}
func (*WinEventLog) SampleConfig() string { return sampleConfig }
func (w *WinEventLog) Init() error {
w.Log.Warn("Current platform is not supported")
return nil
}
func (*WinEventLog) Gather(telegraf.Accumulator) error { return nil }
func init() {
inputs.Add("win_eventlog", func() telegraf.Input {
return &WinEventLog{}
})
}

View file

@ -0,0 +1,133 @@
//go:build windows
package win_eventlog
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestWinEventLog_shouldExcludeEmptyField(t *testing.T) {
type args struct {
field string
fieldType string
fieldValue interface{}
}
tests := []struct {
name string
w *WinEventLog
args args
expected bool
}{
{
name: "Not in list",
args: args{field: "qq", fieldType: "string", fieldValue: ""},
expected: false,
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
},
{
name: "Empty string",
args: args{field: "test", fieldType: "string", fieldValue: ""},
expected: true,
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
},
{
name: "Non-empty string",
args: args{field: "test", fieldType: "string", fieldValue: "qq"},
expected: false,
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
},
{
name: "Zero int",
args: args{field: "test", fieldType: "int", fieldValue: int(0)},
expected: true,
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
},
{
name: "Non-zero int",
args: args{field: "test", fieldType: "int", fieldValue: int(-1)},
expected: false,
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
},
{
name: "Zero uint32",
args: args{field: "test", fieldType: "uint32", fieldValue: uint32(0)},
expected: true,
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
},
{
name: "Non-zero uint32",
args: args{field: "test", fieldType: "uint32", fieldValue: uint32(0xc0fefeed)},
expected: false,
w: &WinEventLog{ExcludeEmpty: []string{"te*"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.NoError(t, tt.w.Init())
actual := tt.w.shouldExcludeEmptyField(tt.args.field, tt.args.fieldType, tt.args.fieldValue)
require.Equal(t, tt.expected, actual)
})
}
}
func TestWinEventLog_shouldProcessField(t *testing.T) {
tags := []string{"Source", "Level*"}
fields := []string{"EventID", "Message*"}
excluded := []string{"Message*"}
type args struct {
field string
}
tests := []struct {
name string
w *WinEventLog
args args
wantShould bool
wantList string
}{
{
name: "Not in tags",
args: args{field: "test"},
wantShould: false,
wantList: "excluded",
w: &WinEventLog{EventTags: tags, EventFields: fields, ExcludeFields: excluded},
},
{
name: "In Tags",
args: args{field: "LevelText"},
wantShould: true,
wantList: "tags",
w: &WinEventLog{EventTags: tags, EventFields: fields, ExcludeFields: excluded},
},
{
name: "Not in Fields",
args: args{field: "EventId"},
wantShould: false,
wantList: "excluded",
w: &WinEventLog{EventTags: tags, EventFields: fields, ExcludeFields: excluded},
},
{
name: "In Fields",
args: args{field: "EventID"},
wantShould: true,
wantList: "fields",
w: &WinEventLog{EventTags: tags, EventFields: fields, ExcludeFields: excluded},
},
{
name: "In Fields and Excluded",
args: args{field: "Messages"},
wantShould: false,
wantList: "excluded",
w: &WinEventLog{EventTags: tags, EventFields: fields, ExcludeFields: excluded},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.NoError(t, tt.w.Init())
should, list := tt.w.shouldProcessField(tt.args.field)
require.Equal(t, tt.wantShould, should)
require.Equal(t, tt.wantList, list)
})
}
}

View file

@ -0,0 +1,250 @@
//go:build windows
package win_eventlog
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// evtHandle uintptr
type evtHandle uintptr
// Do the interface allocations only once for common errno values.
const (
errnoErrorIOPending = 997
)
var (
errErrorIOPending error = syscall.Errno(errnoErrorIOPending)
)
// evtFormatMessageFlag defines the values that specify the message string from the event to format.
type evtFormatMessageFlag uint32
// EVT_FORMAT_MESSAGE_FLAGS enumeration
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa385525(v=vs.85).aspx
const (
// evtFormatMessageEvent - Format the event's message string.
evtFormatMessageEvent evtFormatMessageFlag = iota + 1
// evtFormatMessageLevel - Format the message string of the level specified in the event.
evtFormatMessageLevel
// evtFormatMessageTask - Format the message string of the task specified in the event.
evtFormatMessageTask
// evtFormatMessageOpcode - Format the message string of the task specified in the event.
evtFormatMessageOpcode
// evtFormatMessageKeyword - Format the message string of the keywords specified in the event. If the
// event specifies multiple keywords, the formatted string is a list of null-terminated strings.
// Increment through the strings until your pointer points past the end of the used buffer.
evtFormatMessageKeyword
)
// errnoErr returns common boxed Errno values, to prevent allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case errnoErrorIOPending:
return errErrorIOPending
}
return e
}
var (
modwevtapi = windows.NewLazySystemDLL("wevtapi.dll")
procEvtSubscribe = modwevtapi.NewProc("EvtSubscribe")
procEvtRender = modwevtapi.NewProc("EvtRender")
procEvtClose = modwevtapi.NewProc("EvtClose")
procEvtNext = modwevtapi.NewProc("EvtNext")
procEvtFormatMessage = modwevtapi.NewProc("EvtFormatMessage")
procEvtOpenPublisherMetadata = modwevtapi.NewProc("EvtOpenPublisherMetadata")
procEvtCreateBookmark = modwevtapi.NewProc("EvtCreateBookmark")
procEvtUpdateBookmark = modwevtapi.NewProc("EvtUpdateBookmark")
)
//nolint:revive //argument-limit conditionally more arguments allowed
func evtSubscribe(
session evtHandle,
signalEvent uintptr,
channelPath *uint16,
query *uint16,
bookmark evtHandle,
context uintptr,
callback syscall.Handle,
flags evtSubscribeFlag,
) (evtHandle, error) {
r0, _, e1 := syscall.SyscallN(
procEvtSubscribe.Addr(),
uintptr(session),
signalEvent,
uintptr(unsafe.Pointer(channelPath)), //nolint:gosec // G103: Valid use of unsafe call to pass channelPath
uintptr(unsafe.Pointer(query)), //nolint:gosec // G103: Valid use of unsafe call to pass query
uintptr(bookmark),
context,
uintptr(callback),
uintptr(flags),
)
var err error
handle := evtHandle(r0)
if handle == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return handle, err
}
func evtRender(
fragment evtHandle,
flags evtRenderFlag,
bufferSize uint32,
buffer *byte,
bufferUsed *uint32,
) error {
r1, _, e1 := syscall.SyscallN(
procEvtRender.Addr(),
uintptr(0), // context
uintptr(fragment),
uintptr(flags),
uintptr(bufferSize),
uintptr(unsafe.Pointer(buffer)), //nolint:gosec // G103: Valid use of unsafe call to pass buffer
uintptr(unsafe.Pointer(bufferUsed)), //nolint:gosec // G103: Valid use of unsafe call to pass bufferUsed
uintptr(unsafe.Pointer(nil)), //nolint:gosec // G103: Valid use of unsafe call to pass propertyCount
)
var err error
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return err
}
func evtClose(object evtHandle) error {
r1, _, e1 := syscall.SyscallN(procEvtClose.Addr(), uintptr(object))
var err error
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return err
}
func evtNext(resultSet evtHandle, eventArraySize uint32, eventArray *evtHandle, timeout, flags uint32, numReturned *uint32) error {
r1, _, e1 := syscall.SyscallN(
procEvtNext.Addr(),
uintptr(resultSet),
uintptr(eventArraySize),
uintptr(unsafe.Pointer(eventArray)), //nolint:gosec // G103: Valid use of unsafe call to pass eventArray
uintptr(timeout),
uintptr(flags),
uintptr(unsafe.Pointer(numReturned)), //nolint:gosec // G103: Valid use of unsafe call to pass numReturned
)
var err error
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return err
}
//nolint:revive //argument-limit conditionally more arguments allowed
func evtFormatMessage(
publisherMetadata evtHandle,
event evtHandle,
messageID uint32,
valueCount uint32,
values uintptr,
flags evtFormatMessageFlag,
bufferSize uint32,
buffer *byte,
bufferUsed *uint32,
) error {
r1, _, e1 := syscall.SyscallN(
procEvtFormatMessage.Addr(),
uintptr(publisherMetadata),
uintptr(event),
uintptr(messageID),
uintptr(valueCount),
values,
uintptr(flags),
uintptr(bufferSize),
uintptr(unsafe.Pointer(buffer)), //nolint:gosec // G103: Valid use of unsafe call to pass buffer
uintptr(unsafe.Pointer(bufferUsed)), //nolint:gosec // G103: Valid use of unsafe call to pass bufferUsed
)
var err error
if r1 == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return err
}
func evtOpenPublisherMetadata(session evtHandle, publisherIdentity, logFilePath *uint16, locale, flags uint32) (evtHandle, error) {
r0, _, e1 := syscall.SyscallN(
procEvtOpenPublisherMetadata.Addr(),
uintptr(session),
uintptr(unsafe.Pointer(publisherIdentity)), //nolint:gosec // G103: Valid use of unsafe call to pass publisherIdentity
uintptr(unsafe.Pointer(logFilePath)), //nolint:gosec // G103: Valid use of unsafe call to pass logFilePath
uintptr(locale),
uintptr(flags),
)
var err error
handle := evtHandle(r0)
if handle == 0 {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return handle, err
}
func evtCreateBookmark(bookmarkXML *uint16) (evtHandle, error) {
//nolint:gosec // G103: Valid use of unsafe call to pass bookmarkXML
r0, _, e1 := syscall.SyscallN(procEvtCreateBookmark.Addr(), uintptr(unsafe.Pointer(bookmarkXML)))
handle := evtHandle(r0)
if handle != 0 {
return handle, nil
}
if e1 != 0 {
return handle, errnoErr(e1)
}
return handle, syscall.EINVAL
}
func evtUpdateBookmark(bookmark, event evtHandle) error {
r0, _, e1 := syscall.SyscallN(procEvtUpdateBookmark.Addr(), uintptr(bookmark), uintptr(event))
if r0 != 0 {
return nil
}
if e1 != 0 {
return errnoErr(e1)
}
return syscall.EINVAL
}