@@ -10,23 +10,48 @@ package notableeventsimpl
1010import (
1111 "context"
1212 "fmt"
13+ "strings"
1314 "sync"
1415 "time"
1516
1617 "github.com/cenkalti/backoff"
18+ "golang.org/x/sys/windows"
1719
20+ "github.com/DataDog/datadog-agent/pkg/logs/util/windowsevent"
1821 "github.com/DataDog/datadog-agent/pkg/util/log"
1922 evtapi "github.com/DataDog/datadog-agent/pkg/util/winutil/eventlog/api"
2023 winevtapi "github.com/DataDog/datadog-agent/pkg/util/winutil/eventlog/api/windows"
2124 evtsubscribe "github.com/DataDog/datadog-agent/pkg/util/winutil/eventlog/subscription"
2225)
2326
27+ // eventDefinition describes a notable event type and how to collect/process it
28+ type eventDefinition struct {
29+ // Event identification (for lookup after receiving event)
30+ Provider string
31+ EventID uint
32+
33+ // Event metadata (for payload)
34+ EventType string
35+ Title string
36+ Message string
37+
38+ // Query definition - inner content of <Query> block
39+ Channel string
40+ QueryBody string
41+ }
42+
43+ // eventKey uniquely identifies an event by provider and event ID
44+ type eventKey struct {
45+ Provider string
46+ EventID uint
47+ }
48+
2449// collector monitors Windows Event Log for notable events
2550type collector struct {
2651 // in
2752 api evtapi.API
28- channelPath string
2953 query string
54+ eventLookup map [eventKey ]* eventDefinition
3055 // out
3156 outChan chan <- eventPayload
3257 // internal
@@ -35,15 +60,63 @@ type collector struct {
3560 wg sync.WaitGroup
3661}
3762
63+ // getEventDefinitions returns the list of notable events to collect
64+ func getEventDefinitions () []eventDefinition {
65+ e := []eventDefinition {
66+ {
67+ Provider : "Microsoft-Windows-Kernel-Power" ,
68+ EventID : 41 ,
69+ Channel : "System" ,
70+ QueryBody : ` <Select Path="System">*[System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=41]]</Select>` ,
71+ EventType : "Unexpected reboot" ,
72+ Title : "Unexpected reboot" ,
73+ Message : "The system has rebooted without cleanly shutting down first" ,
74+ },
75+ }
76+ return e
77+ }
78+
79+ // buildEventLookup creates a map for fast event definition lookup.
80+ // Returns error if duplicate event keys are found.
81+ func buildEventLookup (events []eventDefinition ) (map [eventKey ]* eventDefinition , error ) {
82+ lookup := make (map [eventKey ]* eventDefinition )
83+ for i := range events {
84+ def := & events [i ]
85+ key := eventKey {Provider : def .Provider , EventID : def .EventID }
86+ if _ , exists := lookup [key ]; exists {
87+ return nil , fmt .Errorf ("duplicate event definition: %s/%d" , def .Provider , def .EventID )
88+ }
89+ lookup [key ] = def
90+ }
91+ return lookup , nil
92+ }
93+
94+ // buildQuery generates full XML query from event definitions.
95+ // Each event gets its own <Query> block with auto-generated ID.
96+ func buildQuery (events []eventDefinition ) string {
97+ var queries []string
98+ for i , def := range events {
99+ query := fmt .Sprintf (` <Query Id="%d" Path="%s">
100+ %s
101+ </Query>` , i , def .Channel , def .QueryBody )
102+ queries = append (queries , query )
103+ }
104+ return fmt .Sprintf ("<QueryList>\n %s\n </QueryList>" , strings .Join (queries , "\n " ))
105+ }
106+
38107// newCollector creates a new collector instance
39- func newCollector (outChan chan <- eventPayload ) * collector {
40- // TODO(WINA-1970): make real query
108+ func newCollector (outChan chan <- eventPayload ) (* collector , error ) {
109+ events := getEventDefinitions ()
110+ lookup , err := buildEventLookup (events )
111+ if err != nil {
112+ return nil , fmt .Errorf ("failed to build event lookup: %w" , err )
113+ }
41114 return & collector {
42115 api : winevtapi .New (),
43- channelPath : "System" ,
44- query : "*[System[(Level=1 or Level=2 or Level=3)]]" ,
116+ query : buildQuery (events ),
45117 outChan : outChan ,
46- }
118+ eventLookup : lookup ,
119+ }, nil
47120}
48121
49122// start begins monitoring Windows Event Log
@@ -54,12 +127,12 @@ func (c *collector) start() error {
54127
55128 // Create subscription object (will be started in the event loop)
56129 c .sub = evtsubscribe .NewPullSubscription (
57- c . channelPath ,
130+ "" , // empty chennel path when XML query is used
58131 c .query ,
59132 evtsubscribe .WithWindowsEventLogAPI (c .api ),
60133 )
61134
62- log .Infof ("Initialized Windows Event Log subscription: channel=%s, query=%s" , c . channelPath , c .query )
135+ log .Debugf ("Initialized Windows Event Log subscription: query=%s" , c .query )
63136
64137 // Start processing events in background
65138 c .wg .Add (1 )
@@ -109,7 +182,7 @@ func (c *collector) run(ctx context.Context) {
109182 // Check if loop should exit
110183 select {
111184 case <- ctx .Done ():
112- log .Info ("Notable events collector context cancelled, shutting down" )
185+ log .Debug ("Notable events collector context cancelled, shutting down" )
113186 return
114187 default :
115188 }
@@ -123,7 +196,7 @@ func (c *collector) run(ctx context.Context) {
123196 return err
124197 }
125198 // Subscription started successfully
126- log .Infof ("Started Windows Event Log subscription: channel=%s, query=%s" , c . channelPath , c .query )
199+ log .Debugf ("Started Windows Event Log subscription: query=%s" , c .query )
127200 return nil
128201 })
129202 if err != nil {
@@ -136,7 +209,7 @@ func (c *collector) run(ctx context.Context) {
136209 // Subscription is running, wait for events or cancellation
137210 select {
138211 case <- ctx .Done ():
139- log .Info ("Notable events collector context cancelled, shutting down" )
212+ log .Debug ("Notable events collector context cancelled, shutting down" )
140213 return
141214 case events , ok := <- c .sub .GetEvents ():
142215 if ! ok {
@@ -169,16 +242,45 @@ func (c *collector) processEvent(renderCtx evtapi.EventRenderContextHandle, even
169242 }
170243 defer vals .Close ()
171244
172- // Extract Event ID
245+ // Extract provider and event ID for lookup
246+ providerName , err := vals .String (evtapi .EvtSystemProviderName )
247+ if err != nil {
248+ return fmt .Errorf ("failed to get provider name: %w" , err )
249+ }
173250 eventID , err := vals .UInt (evtapi .EvtSystemEventID )
174251 if err != nil {
175252 return fmt .Errorf ("failed to get event ID: %w" , err )
176253 }
177- providerName , err := vals .String (evtapi .EvtSystemProviderName )
254+
255+ // Lookup event definition
256+ def , found := c .eventLookup [eventKey {Provider : providerName , EventID : uint (eventID )}]
257+ if ! found {
258+ return fmt .Errorf ("unknown event: %s/%d" , providerName , eventID )
259+ }
260+
261+ // Render full event XML
262+ xmlUTF16 , err := c .api .EvtRenderEventXml (eventRecord .EventRecordHandle )
178263 if err != nil {
179- return fmt .Errorf ("failed to get provider name: %w" , err )
264+ return fmt .Errorf ("failed to render event XML: %w" , err )
265+ }
266+ xmlString := windows .UTF16ToString (xmlUTF16 )
267+
268+ // Convert XML to JSON map using windowsevent package
269+ eventMap , err := windowsevent .NewMapXMLWithOptions ([]byte (xmlString ), windowsevent.TransformOptions {
270+ FormatEventData : true ,
271+ FormatBinaryData : false , // Skip buggy binary transform
272+ NormalizeEventID : true ,
273+ })
274+ if err != nil {
275+ return fmt .Errorf ("failed to parse event XML: %w" , err )
276+ }
277+
278+ // Build custom data with windows_event_log
279+ customData := map [string ]interface {}{
280+ "windows_event_log" : eventMap .Map ,
180281 }
181- // TODO: get nanoseconds precision from event log
282+
283+ // Get timestamp
182284 var timestamp time.Time
183285 unixTimestamp , err := vals .Time (evtapi .EvtSystemTimeCreated )
184286 if err != nil {
@@ -188,13 +290,15 @@ func (c *collector) processEvent(renderCtx evtapi.EventRenderContextHandle, even
188290 timestamp = time .Unix (unixTimestamp , 0 )
189291 }
190292
191- log .Debugf ("Collected notable event: channel =%s, event_id=%d" , c . channelPath , eventID )
293+ log .Debugf ("Collected notable event: provider =%s, event_id=%d, title=%s " , providerName , eventID , def . Title )
192294
295+ // Build and send payload
193296 payload := eventPayload {
194- Channel : c .channelPath ,
195- Provider : providerName ,
196- EventID : uint (eventID ),
197297 Timestamp : timestamp ,
298+ EventType : def .EventType ,
299+ Title : def .Title ,
300+ Message : def .Message ,
301+ Custom : customData ,
198302 }
199303 c .outChan <- payload
200304
0 commit comments