Skip to content

Commit 141c230

Browse files
authored
Use query XML for windows events logs (#2201)
1 parent 60b2321 commit 141c230

10 files changed

Lines changed: 378 additions & 321 deletions

File tree

go.mod

Lines changed: 44 additions & 41 deletions
Large diffs are not rendered by default.

go.sum

Lines changed: 84 additions & 84 deletions
Large diffs are not rendered by default.

translator/tocwconfig/sampleConfig/opentelemetry/default_otel_config_windows.yaml

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -191,22 +191,6 @@ processors:
191191
send_batch_max_size: 10000
192192
send_batch_size: 10000
193193
timeout: 30s
194-
filter/windows_events_application_1:
195-
error_mode: ignore
196-
logs:
197-
log_record:
198-
- not((severity_number == 17 or severity_number == 13))
199-
metrics: {}
200-
spans: {}
201-
traces: {}
202-
filter/windows_events_system_0:
203-
error_mode: ignore
204-
logs:
205-
log_record:
206-
- not((severity_number == 17 or severity_number == 13))
207-
metrics: {}
208-
spans: {}
209-
traces: {}
210194
resourcedetection/opentelemetry:
211195
aks:
212196
resource_attributes:
@@ -610,12 +594,13 @@ receivers:
610594
read_header_timeout: 0s
611595
traces_url_path: /v1/traces
612596
write_timeout: 0s
613-
windowseventlog/application_2122316671:
614-
channel: Application
597+
windowseventlog/application_3497854245:
598+
channel: ""
615599
id: windows_eventlog_input
616600
max_reads: 100
617601
operators: []
618602
poll_interval: 1s
603+
query: <QueryList><Query Id="0"><Select Path="Application">*[System[(Level='2' or Level='3') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000]]]</Select></Query></QueryList>
619604
resource:
620605
aws.log.channel: Application
621606
aws.log.source: windows_events
@@ -627,12 +612,13 @@ receivers:
627612
start_at: end
628613
storage: file_storage/opentelemetry
629614
type: windows_eventlog_input
630-
windowseventlog/system_653521908:
631-
channel: System
615+
windowseventlog/system_396641996:
616+
channel: ""
632617
id: windows_eventlog_input
633618
max_reads: 100
634619
operators: []
635620
poll_interval: 1s
621+
query: <QueryList><Query Id="0"><Select Path="System">*[System[(Level='2' or Level='3') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000]]]</Select></Query></QueryList>
636622
resource:
637623
aws.log.channel: System
638624
aws.log.source: windows_events
@@ -681,18 +667,16 @@ service:
681667
exporters:
682668
- forward/opentelemetry
683669
processors:
684-
- filter/windows_events_application_1
685670
- transform/windows_events_scope
686671
receivers:
687-
- windowseventlog/application_2122316671
672+
- windowseventlog/application_3497854245
688673
logs/windows_events_system_0:
689674
exporters:
690675
- forward/opentelemetry
691676
processors:
692-
- filter/windows_events_system_0
693677
- transform/windows_events_scope
694678
receivers:
695-
- windowseventlog/system_653521908
679+
- windowseventlog/system_396641996
696680
metrics/opentelemetry:
697681
exporters:
698682
- otlphttp/metrics

translator/tocwconfig/sampleConfig/opentelemetry/windows_events_config.yaml

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,6 @@ processors:
8989
send_batch_max_size: 10000
9090
send_batch_size: 10000
9191
timeout: 30s
92-
filter/windows_events_application_1:
93-
error_mode: ignore
94-
logs:
95-
log_record:
96-
- not((severity_number == 17 or severity_number == 13))
97-
metrics: {}
98-
spans: {}
99-
traces: {}
100-
filter/windows_events_system_0:
101-
error_mode: ignore
102-
logs:
103-
log_record:
104-
- not((severity_number == 17 or severity_number == 13 or severity_number == 9))
105-
metrics: {}
106-
spans: {}
107-
traces: {}
10892
resource/windows_events_system_0:
10993
attributes:
11094
- action: upsert
@@ -466,12 +450,13 @@ processors:
466450
metric_statements: []
467451
trace_statements: []
468452
receivers:
469-
windowseventlog/application_2122316671:
470-
channel: Application
453+
windowseventlog/application_3497854245:
454+
channel: ""
471455
id: windows_eventlog_input
472456
max_reads: 100
473457
operators: []
474458
poll_interval: 1s
459+
query: <QueryList><Query Id="0"><Select Path="Application">*[System[(Level='2' or Level='3') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000]]]</Select></Query></QueryList>
475460
resource:
476461
aws.log.channel: Application
477462
aws.log.source: windows_events
@@ -483,12 +468,13 @@ receivers:
483468
start_at: end
484469
storage: file_storage/opentelemetry
485470
type: windows_eventlog_input
486-
windowseventlog/system_653521908:
487-
channel: System
471+
windowseventlog/system_1384911762:
472+
channel: ""
488473
id: windows_eventlog_input
489474
max_reads: 100
490475
operators: []
491476
poll_interval: 1s
477+
query: <QueryList><Query Id="0"><Select Path="System">*[System[(Level='2' or Level='4' or Level='0' or Level='3') and TimeCreated[timediff(@SystemTime) &lt;= 1209600000]]]</Select></Query></QueryList>
492478
resource:
493479
aws.log.channel: System
494480
aws.log.source: windows_events
@@ -524,19 +510,17 @@ service:
524510
exporters:
525511
- forward/opentelemetry
526512
processors:
527-
- filter/windows_events_application_1
528513
- transform/windows_events_scope
529514
receivers:
530-
- windowseventlog/application_2122316671
515+
- windowseventlog/application_3497854245
531516
logs/windows_events_system_0:
532517
exporters:
533518
- forward/opentelemetry
534519
processors:
535-
- filter/windows_events_system_0
536520
- resource/windows_events_system_0
537521
- transform/windows_events_scope
538522
receivers:
539-
- windowseventlog/system_653521908
523+
- windowseventlog/system_1384911762
540524
telemetry:
541525
logs:
542526
encoding: console

translator/translate/otel/pipeline/opentelemetry/windowsevents/translator.go

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,48 +6,61 @@ package windowsevents
66
import (
77
"fmt"
88
"strings"
9+
"time"
910

1011
"go.opentelemetry.io/collector/component"
1112
"go.opentelemetry.io/collector/confmap"
1213
"go.opentelemetry.io/collector/pipeline"
1314

15+
"github.com/aws/amazon-cloudwatch-agent/internal/util/hash"
1416
"github.com/aws/amazon-cloudwatch-agent/translator/translate/otel/common"
1517
"github.com/aws/amazon-cloudwatch-agent/translator/translate/otel/connector/forward"
1618
"github.com/aws/amazon-cloudwatch-agent/translator/translate/otel/extension/filestorage"
17-
"github.com/aws/amazon-cloudwatch-agent/translator/translate/otel/processor/filterprocessor"
1819
"github.com/aws/amazon-cloudwatch-agent/translator/translate/otel/processor/resourceprocessor"
1920
"github.com/aws/amazon-cloudwatch-agent/translator/translate/otel/processor/transformprocessor"
2021
"github.com/aws/amazon-cloudwatch-agent/translator/translate/otel/receiver/windowseventlog"
2122
)
2223

23-
// severityNumbers maps config level names to OTel severity numbers.
24-
// Derived from pkg/stanza/operator/input/windows/xml.go parseSeverity():
25-
//
26-
// Level "1"/Critical → Fatal(21), "2"/Error → Error(17),
27-
// "3"/Warning → Warn(13), "4"/Information → Info(9), default → Default(0)
28-
//
29-
// TODO: Replace with upstream Query XML filtering when collector is bumped past v0.124.
30-
var severityNumbers = map[string]int{
31-
"CRITICAL": 21,
32-
"ERROR": 17,
33-
"WARNING": 13,
34-
"INFORMATION": 9,
35-
"VERBOSE": 0,
24+
// eventLevelNumbers maps config level names to Windows Event Log numeric levels.
25+
var eventLevelNumbers = map[string][]string{
26+
"CRITICAL": {"1"},
27+
"ERROR": {"2"},
28+
"WARNING": {"3"},
29+
"INFORMATION": {"4", "0"},
30+
"VERBOSE": {"5"},
3631
}
3732

3833
type eventEntry struct {
39-
name string
40-
receiverName string
34+
index int
4135
channel string
42-
raw bool
36+
format string
4337
resource map[string]string
4438
logGroupName string
4539
logStreamName string
4640
eventLevels []string
4741
eventIDs []int
4842
}
4943

44+
func (e eventEntry) raw() bool {
45+
return e.format == "xml"
46+
}
47+
48+
func (e eventEntry) name() string {
49+
return fmt.Sprintf("%s_%d", sanitizeName(e.channel), e.index)
50+
}
51+
52+
func (e eventEntry) receiverName() string {
53+
return fmt.Sprintf("%s_%s", sanitizeName(e.channel), e.receiverHash())
54+
}
55+
56+
func (e eventEntry) receiverHash() string {
57+
return hash.HashName(fmt.Sprintf("%s\x00%s\x00%v\x00%v", e.channel, e.format, e.eventLevels, e.eventIDs))
58+
}
59+
5060
func (e eventEntry) routingAttributes() map[string]string {
61+
if e.logGroupName == "" && e.logStreamName == "" {
62+
return nil
63+
}
5164
attrs := make(map[string]string)
5265
if e.logGroupName != "" {
5366
attrs["aws.log.group.name"] = e.logGroupName
@@ -58,38 +71,41 @@ func (e eventEntry) routingAttributes() map[string]string {
5871
return attrs
5972
}
6073

61-
// filterCondition builds an OTTL drop condition. When both levels and IDs are present, they are ANDed.
62-
func (e eventEntry) filterCondition() string {
63-
var parts []string
74+
const ignoreOlderThanTwoWeeksMs = int64(14*24*time.Hour) / int64(time.Millisecond)
75+
76+
// queryXML builds a Windows Event Log XPath query XML for native OS-level filtering.
77+
// Always includes a 2-week time cutoff to prevent replaying stale events on restart.
78+
func (e eventEntry) queryXML() string {
79+
var filters []string
6480

6581
if len(e.eventLevels) > 0 {
66-
var severityChecks []string
82+
var levelChecks []string
6783
for _, level := range e.eventLevels {
68-
if sev, ok := severityNumbers[level]; ok {
69-
severityChecks = append(severityChecks, fmt.Sprintf("severity_number == %d", sev))
84+
if nums, ok := eventLevelNumbers[level]; ok {
85+
for _, n := range nums {
86+
levelChecks = append(levelChecks, fmt.Sprintf("Level='%s'", n))
87+
}
7088
}
7189
}
72-
if len(severityChecks) > 0 {
73-
parts = append(parts, "("+strings.Join(severityChecks, " or ")+")")
90+
if len(levelChecks) > 0 {
91+
filters = append(filters, "("+strings.Join(levelChecks, " or ")+")")
7492
}
7593
}
7694

7795
if len(e.eventIDs) > 0 {
7896
var idChecks []string
7997
for _, id := range e.eventIDs {
80-
idChecks = append(idChecks, fmt.Sprintf("body[\"event_id\"][\"id\"] == %d", id))
98+
idChecks = append(idChecks, fmt.Sprintf("EventID='%d'", id))
8199
}
82100
if len(idChecks) > 0 {
83-
parts = append(parts, "("+strings.Join(idChecks, " or ")+")")
101+
filters = append(filters, "("+strings.Join(idChecks, " or ")+")")
84102
}
85103
}
86104

87-
if len(parts) == 0 {
88-
return ""
89-
}
105+
filters = append(filters, fmt.Sprintf("TimeCreated[timediff(@SystemTime) &lt;= %d]", ignoreOlderThanTwoWeeksMs))
90106

91-
// Drop records that DON'T match the filter (filter processor drops when condition is true)
92-
return "not(" + strings.Join(parts, " and ") + ")"
107+
return fmt.Sprintf(`<QueryList><Query Id="0"><Select Path="%s">*[System[%s]]</Select></Query></QueryList>`,
108+
e.channel, strings.Join(filters, " and "))
93109
}
94110

95111
type windowsEventsPipelineTranslator struct {
@@ -99,30 +115,21 @@ type windowsEventsPipelineTranslator struct {
99115
var _ common.PipelineTranslator = (*windowsEventsPipelineTranslator)(nil)
100116

101117
func (t *windowsEventsPipelineTranslator) ID() pipeline.ID {
102-
return pipeline.NewIDWithName(pipeline.SignalLogs, "windows_events_"+t.entry.name)
118+
return pipeline.NewIDWithName(pipeline.SignalLogs, "windows_events_"+t.entry.name())
103119
}
104120

105121
func (t *windowsEventsPipelineTranslator) Translate(_ *confmap.Conf) (*common.ComponentTranslators, error) {
106-
if t.entry.raw && len(t.entry.eventIDs) > 0 {
107-
return nil, fmt.Errorf("event_ids filtering is not supported with event_format \"xml\" for channel %q", t.entry.channel)
108-
}
109-
110122
fwdConnector := forward.NewTranslator(common.OpenTelemetryKey)
111123

124+
query := t.entry.queryXML()
112125
receivers := common.NewTranslatorMap[component.Config, component.ID]()
113-
receivers.Set(windowseventlog.NewTranslator(t.entry.receiverName, t.entry.channel, t.entry.raw, t.entry.resource))
126+
receivers.Set(windowseventlog.NewTranslator(t.entry.receiverName(), t.entry.channel, t.entry.raw(), query, t.entry.resource))
114127

115128
processors := common.NewTranslatorMap[component.Config, component.ID]()
116129

117-
// TODO: Replace with upstream Query XML filtering when collector is bumped past v0.124.
118-
condition := t.entry.filterCondition()
119-
if condition != "" {
120-
processors.Set(filterprocessor.NewTranslatorWithLogCondition("windows_events_"+t.entry.name, condition, common.OTTLErrorModeIgnore))
121-
}
122-
123130
if attrs := t.entry.routingAttributes(); len(attrs) > 0 {
124131
processors.Set(resourceprocessor.NewTranslator(
125-
common.WithName("windows_events_"+t.entry.name),
132+
common.WithName("windows_events_"+t.entry.name()),
126133
resourceprocessor.WithAttributes(attrs),
127134
))
128135
}

0 commit comments

Comments
 (0)