fix(inputs.win_eventlog): Do not replay the channel on restart - #19445
fix(inputs.win_eventlog): Do not replay the channel on restart#19445skartikey wants to merge 4 commits into
Conversation
The bookmark is only advanced for events the plugin received, so a run that saw no event persists a well-formed but positionless bookmark list. Restoring it switched the subscription to start-after-bookmark unconditionally, overriding from_beginning, and Windows cannot resume from a bookmark without a position so it started at the channel's oldest record and replayed the whole history on every subsequent start. Keep the bookmark and the subscription flag derived from from_beginning when the restored bookmark holds no position, and anchor the bookmark at the newest event matching the query before subscribing, so the state file carries a real position from the first start and events arriving while Telegraf is stopped are still picked up.
d426640 to
d71d02a
Compare
srebhan
left a comment
There was a problem hiding this comment.
Thanks @skartikey for your PR!
However, I think your approach is flawed.
Imagine you start Telegraf with from_beginning = false on an empty category log (or more general without any match on your query). Through the lifetime of this Telegraf instance no new log arrives.
We now take Telegraf down and for me the "saved state" should be the start of that log as that is where we left. After Telegraf is down many logs matching the query arrive.
Once Telegraf is restarted, your approach will miss all the log arriving in the meantime as you simply continue on the end of the stream, don't you?
As I already tried to say in the issue, I think what should be done is:
- tack if the bookmark was updated, i.e. if at least one event arrived
- when the state should be saved (i.e. in
GetState()) check- if the bookmark was updated, return the bookmark. That's the normal case where new data arrives.
- if the bookmark was not updated and it is not empty, return the (previous) bookmark. That's the case where we had a position in the stream before but nothing new arrived.
- if the bookmark was not updated and it is empty, enforce a bookmark update by
runningEvtQuerywith theEvtQueryReverseDirectionflag and runEvtNextwithEventsSizebeing once on the result-set to get the latest event.- if the set is empty (i.e.
Returnedis zero), save the empty bookmark as we are on the beginning of the stream. - otherwise, call
EvtUpdateBookmarkwith the returned event. Render the updated bookmark XML and return it.
- if the set is empty (i.e.
This approach guarantees that we really continue on where we left.
Drawback: The above cannot fix the current issue with existing saved states. The user has to manually delete the saved state for the eventlog plugin. However, I don't think this one-time effort justifies adding future issues with potential data loss when we leave on the start but come back at the end.
… run Anchoring the bookmark before subscribing leaves it positionless when the channel holds no event matching the query. Keeping the configured start for such a state made the next run subscribe to future events only, so everything arriving while Telegraf was stopped was lost. Record why the bookmark has no position: with the marker we know we are at the beginning of the stream and the next run continues at the oldest record, without it the position is unknown and the configured start is kept. The persisted state grows a field for that and still accepts the plain bookmark string written by earlier versions, so existing state files keep their meaning.
|
@srebhan You are right, that case loses everything arriving while we are down, thanks. Pushed a fix, though I took a slightly different route than the one you sketched and I would like your read on it. The plugin now remembers why the bookmark has no position. If the anchoring query ran and the channel had no matching event, we know we are sitting at the beginning of the stream, and the state records that next to the bookmark. Restoring such a state starts the subscription at the oldest record, so the logs that arrived while Telegraf was down are picked up. A positionless bookmark without that marker still means "we have no idea where we are", so it keeps the configured start and does not replay. That is also what avoids the manual state deletion: the state is now a small struct whose I kept the anchoring before the subscription rather than moving the query into |
|
@skartikey your approach is much more complex than what I had in mind. For me what you need to do is to
func (w *WinEventLog) GetState() interface{} {
// Render the Bookmark XML from the handle
bookmarkXML, err := w.renderBookmark()
if err != nil {
w.Log.Errorf("State-persistence failed, cannot render bookmark: %v", err)
return nil
}
// The bookmark was either updated or was filled in a previous run.
// In both cases the bookmark represents a valid position.
if w.eventReceived || !w.emptyBookmark(){
return bookmarkXML
}
// Force a bookmark update by finding the last event matching the query
// and use this position.
eventHandle, err := w.seekLastEvent()
if err != nil {
// If not a single event matches, we should start at the beginning
if err == errNoMoreItems {
return bookmarkXML
}
w.Log.Errorf("Seeking latest even failed: %v", err)
return nil
}
if err := evtUpdateBookmark(w.bookmark, eventHandle); err != nil {
w.Log.Errorf("Updating bookmark failed: %v", err)
}
// Return the updated bookmark
// Render the Bookmark XML from the handle
bookmarkXML, err == w.renderBookmark()
if err != nil {
w.Log.Errorf("State-persistence failed, cannot render updated bookmark: %v", err)
return nil
}
return bookmarkXML
}
func (w *WinEventLog) seekLastEvent() (evtHandle, error) {
// Query the log in reverse order to find matching events
logNamePtr, err := syscall.UTF16PtrFromString(w.EventlogName)
if err != nil {
return 0, err
}
xqueryPtr, err := syscall.UTF16PtrFromString(w.Query)
if err != nil {
return 0, err
}
resultSet, err := evtQuery(0, logNamePtr, xqueryPtr, evtQueryChannelPath|evtQueryReverseDirection)
if err != nil {
return 0, fmt.Errorf("querying failed: %w", err)
}
// Get the last event
var evtReturned uint32
eventHandles := make([]evtHandle, 1)
if err := evtNext(subsHandle, 1, &eventHandles[0], 0, 0, &evtReturned); err != nil {
if errors.Is(err, errInvalidOperation) && evtReturned == 0 {
return 0, errNoMoreItems
}
return 0, err
}
if evtReturned == 0 {
return 0, errNoMoreItems
}
return eventHandles[0], nil
}This means we only need to map |
Restoring a positionless bookmark already starts at the channel's oldest record, so an empty bookmark states on its own that we left at the beginning of the stream. The extra state field marking that case and the migration it required are not needed.
|
any progress in here :? |
|
@srebhan You are right, that was more complex than needed. I dropped the state struct, the persisted state is the plain bookmark XML again, unchanged from today. The marker was redundant: a positionless bookmark restored with I kept the query before the subscription rather than in The manual state deletion is avoided too: an old positionless state replays the channel once after the upgrade and carries a real position from then on. Side note on returning nil from |
|
Download PR build artifacts for linux_amd64.tar.gz, darwin_arm64.tar.gz, and windows_amd64.zip. 📦 Click here to get additional PR build artifactsArtifact URLs |
Summary
With
from_beginning = falseand astatefile, the plugin replays a channel's entire history on every start after the first, for as long as the channel stays quiet. The bookmark is only advanced for events we actually receive, so a run that receives none persists a well-formed but positionless bookmark, and Windows cannot resume from that: it starts at the channel's oldest record instead.The subscription now anchors the bookmark at the newest event matching the query before subscribing, so the state carries a real position from the first start. The current run collects the same events, as starting after the newest event and subscribing to the future ones are the same thing.
A channel that holds no event matching the query has no position to anchor to, but there the empty bookmark is already correct: it means we are at the beginning of the stream, and restoring it starts at the oldest record, so the next run picks up everything that arrived while Telegraf was stopped. The persisted state is unchanged, still the bookmark XML. A state file written by an earlier version replays the channel once on the first start after the upgrade and carries a real position from then on.
from_beginning = trueis left alone, and a failed anchoring query warns and keeps the configured start point.This needs
EvtQuery, which was not bound yet.Checklist
Related issues
resolves #19355