fluent-plugin-windows-eventlog 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/unit-test.yml +36 -34
- data/.gitignore +14 -14
- data/CHANGELOG.md +77 -74
- data/Gemfile +4 -4
- data/LICENSE.txt +203 -203
- data/README.md +387 -387
- data/Rakefile +10 -10
- data/appveyor.yml +24 -24
- data/fluent-plugin-winevtlog.gemspec +28 -28
- data/lib/fluent/plugin/bookmark_sax_parser.rb +30 -30
- data/lib/fluent/plugin/in_windows_eventlog.rb +241 -241
- data/lib/fluent/plugin/in_windows_eventlog2.rb +410 -406
- data/test/generate-windows-event.rb +47 -47
- data/test/helper.rb +34 -34
- data/test/plugin/test_bookmark_sax_parser.rb +41 -41
- data/test/plugin/test_in_windows_eventlog2.rb +619 -600
- data/test/plugin/test_in_winevtlog.rb +48 -48
- metadata +8 -8
@@ -1,406 +1,410 @@
|
|
1
|
-
require 'winevt'
|
2
|
-
require 'fluent/plugin/input'
|
3
|
-
require 'fluent/plugin'
|
4
|
-
require_relative 'bookmark_sax_parser'
|
5
|
-
|
6
|
-
module Fluent::Plugin
|
7
|
-
class WindowsEventLog2Input < Input
|
8
|
-
Fluent::Plugin.register_input('windows_eventlog2', self)
|
9
|
-
|
10
|
-
class ReconnectError < Fluent::UnrecoverableError; end
|
11
|
-
|
12
|
-
helpers :timer, :storage, :parser
|
13
|
-
|
14
|
-
DEFAULT_STORAGE_TYPE = 'local'
|
15
|
-
KEY_MAP = {"ProviderName" => ["ProviderName", :string],
|
16
|
-
"ProviderGUID" => ["ProviderGUID", :string],
|
17
|
-
"EventID" => ["EventID", :string],
|
18
|
-
"Qualifiers" => ["Qualifiers", :string],
|
19
|
-
"Level" => ["Level", :string],
|
20
|
-
"Task" => ["Task", :string],
|
21
|
-
"Opcode" => ["Opcode", :string],
|
22
|
-
"Keywords" => ["Keywords", :string],
|
23
|
-
"TimeCreated" => ["TimeCreated", :string],
|
24
|
-
"EventRecordID" => ["EventRecordID", :string],
|
25
|
-
"ActivityID" => ["ActivityID", :string],
|
26
|
-
"RelatedActivityID" => ["RelatedActivityID", :string],
|
27
|
-
"ProcessID" => ["ProcessID", :string],
|
28
|
-
"ThreadID" => ["ThreadID", :string],
|
29
|
-
"Channel" => ["Channel", :string],
|
30
|
-
"Computer" => ["Computer", :string],
|
31
|
-
"UserID" => ["UserID", :string],
|
32
|
-
"Version" => ["Version", :string],
|
33
|
-
"Description" => ["Description", :string],
|
34
|
-
"EventData" => ["EventData", :array]}
|
35
|
-
|
36
|
-
config_param :tag, :string
|
37
|
-
config_param :read_interval, :time, default: 2
|
38
|
-
config_param :channels, :array, default: []
|
39
|
-
config_param :keys, :array, default: []
|
40
|
-
config_param :read_from_head, :bool, default: false, deprecated: "Use `read_existing_events' instead."
|
41
|
-
config_param :read_existing_events, :bool, default: false
|
42
|
-
config_param :parse_description, :bool, default: false
|
43
|
-
config_param :render_as_xml, :bool, default: false
|
44
|
-
config_param :rate_limit, :integer, default: Winevt::EventLog::Subscribe::RATE_INFINITE
|
45
|
-
config_param :preserve_qualifiers_on_hash, :bool, default: false
|
46
|
-
config_param :read_all_channels, :bool, default: false
|
47
|
-
config_param :description_locale, :string, default: nil
|
48
|
-
config_param :refresh_subscription_interval, :time, default: nil
|
49
|
-
config_param :event_query, :string, default: "*"
|
50
|
-
|
51
|
-
config_section :subscribe, param_name: :subscribe_configs, required: false, multi: true do
|
52
|
-
config_param :channels, :array
|
53
|
-
config_param :read_existing_events, :bool, default: false
|
54
|
-
config_param :remote_server, :string, default: nil
|
55
|
-
config_param :remote_domain, :string, default: nil
|
56
|
-
config_param :remote_username, :string, default: nil
|
57
|
-
config_param :remote_password, :string, default: nil, secret: true
|
58
|
-
end
|
59
|
-
|
60
|
-
config_section :storage do
|
61
|
-
config_set_default :usage, "bookmarks"
|
62
|
-
config_set_default :@type, DEFAULT_STORAGE_TYPE
|
63
|
-
config_set_default :persistent, true
|
64
|
-
end
|
65
|
-
|
66
|
-
config_section :parse do
|
67
|
-
config_set_default :@type, 'winevt_xml'
|
68
|
-
config_set_default :estimate_current_event, false
|
69
|
-
end
|
70
|
-
|
71
|
-
def initalize
|
72
|
-
super
|
73
|
-
@chs = []
|
74
|
-
@keynames = []
|
75
|
-
end
|
76
|
-
|
77
|
-
def configure(conf)
|
78
|
-
super
|
79
|
-
@session = nil
|
80
|
-
@chs = []
|
81
|
-
@subscriptions = {}
|
82
|
-
@all_chs = Winevt::EventLog::Channel.new
|
83
|
-
@all_chs.force_enumerate = false
|
84
|
-
@timers = {}
|
85
|
-
|
86
|
-
if @read_all_channels
|
87
|
-
@all_chs.each do |ch|
|
88
|
-
uch = ch.strip.downcase
|
89
|
-
@chs.push([uch, @read_existing_events])
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
@read_existing_events = @read_from_head || @read_existing_events
|
94
|
-
if @channels.empty? && @subscribe_configs.empty? && !@read_all_channels
|
95
|
-
@chs.push(['application', @read_existing_events, nil])
|
96
|
-
else
|
97
|
-
@channels.map {|ch| ch.strip.downcase }.uniq.each do |uch|
|
98
|
-
@chs.push([uch, @read_existing_events, nil])
|
99
|
-
end
|
100
|
-
@subscribe_configs.each do |subscribe|
|
101
|
-
if subscribe.remote_server
|
102
|
-
@session = Winevt::EventLog::Session.new(subscribe.remote_server,
|
103
|
-
subscribe.remote_domain,
|
104
|
-
subscribe.remote_username,
|
105
|
-
subscribe.remote_password)
|
106
|
-
|
107
|
-
log.debug("connect to remote box (server: #{subscribe.remote_server}) domain: #{subscribe.remote_domain} username: #{subscribe.remote_username})")
|
108
|
-
end
|
109
|
-
subscribe.channels.map {|ch| ch.strip.downcase }.uniq.each do |uch|
|
110
|
-
@chs.push([uch, subscribe.read_existing_events, @session])
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
@chs.uniq!
|
115
|
-
@keynames = @keys.map {|k| k.strip }.uniq
|
116
|
-
if @keynames.empty?
|
117
|
-
@keynames = KEY_MAP.keys
|
118
|
-
end
|
119
|
-
|
120
|
-
@tag = tag
|
121
|
-
@bookmarks_storage = storage_create(usage: "bookmarks")
|
122
|
-
@winevt_xml = false
|
123
|
-
@parser = nil
|
124
|
-
if @render_as_xml
|
125
|
-
@parser = parser_create
|
126
|
-
@winevt_xml = @parser.respond_to?(:winevt_xml?) && @parser.winevt_xml?
|
127
|
-
class << self
|
128
|
-
alias_method :on_notify, :on_notify_xml
|
129
|
-
end
|
130
|
-
else
|
131
|
-
class << self
|
132
|
-
alias_method :on_notify, :on_notify_hash
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
if @render_as_xml && @preserve_qualifiers_on_hash
|
137
|
-
raise Fluent::ConfigError, "preserve_qualifiers_on_hash must be used with Hash object rendering(render_as_xml as false)."
|
138
|
-
end
|
139
|
-
if !@render_as_xml && !@preserve_qualifiers_on_hash
|
140
|
-
@keynames.delete('Qualifiers')
|
141
|
-
elsif @parser.respond_to?(:preserve_qualifiers?) && !@parser.preserve_qualifiers?
|
142
|
-
@keynames.delete('Qualifiers')
|
143
|
-
end
|
144
|
-
@keynames.delete('EventData') if @parse_description
|
145
|
-
|
146
|
-
locale = Winevt::EventLog::Locale.new
|
147
|
-
if @description_locale && unsupported_locale?(locale, @description_locale)
|
148
|
-
raise Fluent::ConfigError, "'#{@description_locale}' is not supported. Supported locales are: #{locale.each.map{|code, _desc| code}.join(" ")}"
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def unsupported_locale?(locale, description_locale)
|
153
|
-
locale.each.select {|c, _d| c.downcase == description_locale.downcase}.empty?
|
154
|
-
end
|
155
|
-
|
156
|
-
def start
|
157
|
-
super
|
158
|
-
|
159
|
-
refresh_subscriptions
|
160
|
-
if @refresh_subscription_interval
|
161
|
-
timer_execute(:in_windows_eventlog_refresh_subscription_timer, @refresh_subscription_interval, &method(:refresh_subscriptions))
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def shutdown
|
166
|
-
super
|
167
|
-
|
168
|
-
@subscriptions.keys.each do |ch|
|
169
|
-
subscription = @subscriptions.delete(ch)
|
170
|
-
if subscription
|
171
|
-
subscription.cancel
|
172
|
-
log.debug "channel (#{ch}) subscription is canceled."
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def retry_on_error(channel, times: 15)
|
178
|
-
try = 0
|
179
|
-
begin
|
180
|
-
log.debug "Retry to subscribe for #{channel}...." if try > 1
|
181
|
-
try += 1
|
182
|
-
yield
|
183
|
-
log.info "Retry to subscribe for #{channel} succeeded." if try > 1
|
184
|
-
try = 0
|
185
|
-
rescue Winevt::EventLog::Subscribe::RemoteHandlerError => e
|
186
|
-
raise ReconnectError, "Retrying limit is exceeded." if try > times
|
187
|
-
log.warn "#{e.message}. Remaining retry count(s): #{times - try}"
|
188
|
-
sleep 2**try
|
189
|
-
retry
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
def refresh_subscriptions
|
194
|
-
clear_subscritpions
|
195
|
-
|
196
|
-
@chs.each do |ch, read_existing_events, session|
|
197
|
-
retry_on_error(ch) do
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
@subscriptions
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
log.
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
end
|
275
|
-
|
276
|
-
def
|
277
|
-
|
278
|
-
end
|
279
|
-
|
280
|
-
def
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
1
|
+
require 'winevt'
|
2
|
+
require 'fluent/plugin/input'
|
3
|
+
require 'fluent/plugin'
|
4
|
+
require_relative 'bookmark_sax_parser'
|
5
|
+
|
6
|
+
module Fluent::Plugin
|
7
|
+
class WindowsEventLog2Input < Input
|
8
|
+
Fluent::Plugin.register_input('windows_eventlog2', self)
|
9
|
+
|
10
|
+
class ReconnectError < Fluent::UnrecoverableError; end
|
11
|
+
|
12
|
+
helpers :timer, :storage, :parser
|
13
|
+
|
14
|
+
DEFAULT_STORAGE_TYPE = 'local'
|
15
|
+
KEY_MAP = {"ProviderName" => ["ProviderName", :string],
|
16
|
+
"ProviderGUID" => ["ProviderGUID", :string],
|
17
|
+
"EventID" => ["EventID", :string],
|
18
|
+
"Qualifiers" => ["Qualifiers", :string],
|
19
|
+
"Level" => ["Level", :string],
|
20
|
+
"Task" => ["Task", :string],
|
21
|
+
"Opcode" => ["Opcode", :string],
|
22
|
+
"Keywords" => ["Keywords", :string],
|
23
|
+
"TimeCreated" => ["TimeCreated", :string],
|
24
|
+
"EventRecordID" => ["EventRecordID", :string],
|
25
|
+
"ActivityID" => ["ActivityID", :string],
|
26
|
+
"RelatedActivityID" => ["RelatedActivityID", :string],
|
27
|
+
"ProcessID" => ["ProcessID", :string],
|
28
|
+
"ThreadID" => ["ThreadID", :string],
|
29
|
+
"Channel" => ["Channel", :string],
|
30
|
+
"Computer" => ["Computer", :string],
|
31
|
+
"UserID" => ["UserID", :string],
|
32
|
+
"Version" => ["Version", :string],
|
33
|
+
"Description" => ["Description", :string],
|
34
|
+
"EventData" => ["EventData", :array]}
|
35
|
+
|
36
|
+
config_param :tag, :string
|
37
|
+
config_param :read_interval, :time, default: 2
|
38
|
+
config_param :channels, :array, default: []
|
39
|
+
config_param :keys, :array, default: []
|
40
|
+
config_param :read_from_head, :bool, default: false, deprecated: "Use `read_existing_events' instead."
|
41
|
+
config_param :read_existing_events, :bool, default: false
|
42
|
+
config_param :parse_description, :bool, default: false
|
43
|
+
config_param :render_as_xml, :bool, default: false
|
44
|
+
config_param :rate_limit, :integer, default: Winevt::EventLog::Subscribe::RATE_INFINITE
|
45
|
+
config_param :preserve_qualifiers_on_hash, :bool, default: false
|
46
|
+
config_param :read_all_channels, :bool, default: false
|
47
|
+
config_param :description_locale, :string, default: nil
|
48
|
+
config_param :refresh_subscription_interval, :time, default: nil
|
49
|
+
config_param :event_query, :string, default: "*"
|
50
|
+
|
51
|
+
config_section :subscribe, param_name: :subscribe_configs, required: false, multi: true do
|
52
|
+
config_param :channels, :array
|
53
|
+
config_param :read_existing_events, :bool, default: false
|
54
|
+
config_param :remote_server, :string, default: nil
|
55
|
+
config_param :remote_domain, :string, default: nil
|
56
|
+
config_param :remote_username, :string, default: nil
|
57
|
+
config_param :remote_password, :string, default: nil, secret: true
|
58
|
+
end
|
59
|
+
|
60
|
+
config_section :storage do
|
61
|
+
config_set_default :usage, "bookmarks"
|
62
|
+
config_set_default :@type, DEFAULT_STORAGE_TYPE
|
63
|
+
config_set_default :persistent, true
|
64
|
+
end
|
65
|
+
|
66
|
+
config_section :parse do
|
67
|
+
config_set_default :@type, 'winevt_xml'
|
68
|
+
config_set_default :estimate_current_event, false
|
69
|
+
end
|
70
|
+
|
71
|
+
def initalize
|
72
|
+
super
|
73
|
+
@chs = []
|
74
|
+
@keynames = []
|
75
|
+
end
|
76
|
+
|
77
|
+
def configure(conf)
|
78
|
+
super
|
79
|
+
@session = nil
|
80
|
+
@chs = []
|
81
|
+
@subscriptions = {}
|
82
|
+
@all_chs = Winevt::EventLog::Channel.new
|
83
|
+
@all_chs.force_enumerate = false
|
84
|
+
@timers = {}
|
85
|
+
|
86
|
+
if @read_all_channels
|
87
|
+
@all_chs.each do |ch|
|
88
|
+
uch = ch.strip.downcase
|
89
|
+
@chs.push([uch, @read_existing_events])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
@read_existing_events = @read_from_head || @read_existing_events
|
94
|
+
if @channels.empty? && @subscribe_configs.empty? && !@read_all_channels
|
95
|
+
@chs.push(['application', @read_existing_events, nil])
|
96
|
+
else
|
97
|
+
@channels.map {|ch| ch.strip.downcase }.uniq.each do |uch|
|
98
|
+
@chs.push([uch, @read_existing_events, nil])
|
99
|
+
end
|
100
|
+
@subscribe_configs.each do |subscribe|
|
101
|
+
if subscribe.remote_server
|
102
|
+
@session = Winevt::EventLog::Session.new(subscribe.remote_server,
|
103
|
+
subscribe.remote_domain,
|
104
|
+
subscribe.remote_username,
|
105
|
+
subscribe.remote_password)
|
106
|
+
|
107
|
+
log.debug("connect to remote box (server: #{subscribe.remote_server}) domain: #{subscribe.remote_domain} username: #{subscribe.remote_username})")
|
108
|
+
end
|
109
|
+
subscribe.channels.map {|ch| ch.strip.downcase }.uniq.each do |uch|
|
110
|
+
@chs.push([uch, subscribe.read_existing_events, @session])
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
@chs.uniq!
|
115
|
+
@keynames = @keys.map {|k| k.strip }.uniq
|
116
|
+
if @keynames.empty?
|
117
|
+
@keynames = KEY_MAP.keys
|
118
|
+
end
|
119
|
+
|
120
|
+
@tag = tag
|
121
|
+
@bookmarks_storage = storage_create(usage: "bookmarks")
|
122
|
+
@winevt_xml = false
|
123
|
+
@parser = nil
|
124
|
+
if @render_as_xml
|
125
|
+
@parser = parser_create
|
126
|
+
@winevt_xml = @parser.respond_to?(:winevt_xml?) && @parser.winevt_xml?
|
127
|
+
class << self
|
128
|
+
alias_method :on_notify, :on_notify_xml
|
129
|
+
end
|
130
|
+
else
|
131
|
+
class << self
|
132
|
+
alias_method :on_notify, :on_notify_hash
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
if @render_as_xml && @preserve_qualifiers_on_hash
|
137
|
+
raise Fluent::ConfigError, "preserve_qualifiers_on_hash must be used with Hash object rendering(render_as_xml as false)."
|
138
|
+
end
|
139
|
+
if !@render_as_xml && !@preserve_qualifiers_on_hash
|
140
|
+
@keynames.delete('Qualifiers')
|
141
|
+
elsif @parser.respond_to?(:preserve_qualifiers?) && !@parser.preserve_qualifiers?
|
142
|
+
@keynames.delete('Qualifiers')
|
143
|
+
end
|
144
|
+
@keynames.delete('EventData') if @parse_description
|
145
|
+
|
146
|
+
locale = Winevt::EventLog::Locale.new
|
147
|
+
if @description_locale && unsupported_locale?(locale, @description_locale)
|
148
|
+
raise Fluent::ConfigError, "'#{@description_locale}' is not supported. Supported locales are: #{locale.each.map{|code, _desc| code}.join(" ")}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def unsupported_locale?(locale, description_locale)
|
153
|
+
locale.each.select {|c, _d| c.downcase == description_locale.downcase}.empty?
|
154
|
+
end
|
155
|
+
|
156
|
+
def start
|
157
|
+
super
|
158
|
+
|
159
|
+
refresh_subscriptions
|
160
|
+
if @refresh_subscription_interval
|
161
|
+
timer_execute(:in_windows_eventlog_refresh_subscription_timer, @refresh_subscription_interval, &method(:refresh_subscriptions))
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def shutdown
|
166
|
+
super
|
167
|
+
|
168
|
+
@subscriptions.keys.each do |ch|
|
169
|
+
subscription = @subscriptions.delete(ch)
|
170
|
+
if subscription
|
171
|
+
subscription.cancel
|
172
|
+
log.debug "channel (#{ch}) subscription is canceled."
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def retry_on_error(channel, times: 15)
|
178
|
+
try = 0
|
179
|
+
begin
|
180
|
+
log.debug "Retry to subscribe for #{channel}...." if try > 1
|
181
|
+
try += 1
|
182
|
+
yield
|
183
|
+
log.info "Retry to subscribe for #{channel} succeeded." if try > 1
|
184
|
+
try = 0
|
185
|
+
rescue Winevt::EventLog::Subscribe::RemoteHandlerError => e
|
186
|
+
raise ReconnectError, "Retrying limit is exceeded." if try > times
|
187
|
+
log.warn "#{e.message}. Remaining retry count(s): #{times - try}"
|
188
|
+
sleep 2**try
|
189
|
+
retry
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def refresh_subscriptions
|
194
|
+
clear_subscritpions
|
195
|
+
|
196
|
+
@chs.each do |ch, read_existing_events, session|
|
197
|
+
retry_on_error(ch) do
|
198
|
+
begin
|
199
|
+
ch, subscribe = subscription(ch, read_existing_events, session)
|
200
|
+
@subscriptions[ch] = subscribe
|
201
|
+
rescue Winevt::EventLog::ChannelNotFoundError => e
|
202
|
+
log.warn "#{e.message}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
subscribe_channels(@subscriptions)
|
207
|
+
end
|
208
|
+
|
209
|
+
def clear_subscritpions
|
210
|
+
@subscriptions.keys.each do |ch|
|
211
|
+
subscription = @subscriptions.delete(ch)
|
212
|
+
if subscription
|
213
|
+
if subscription.cancel
|
214
|
+
log.debug "channel (#{ch}) subscription is cancelled."
|
215
|
+
subscription.close
|
216
|
+
log.debug "channel (#{ch}) subscription handles are closed forcibly."
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
@timers.keys.each do |ch|
|
221
|
+
timer = @timers.delete(ch)
|
222
|
+
if timer
|
223
|
+
event_loop_detach(timer)
|
224
|
+
log.debug "channel (#{ch}) subscription watcher is detached."
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def subscription(ch, read_existing_events, remote_session)
|
230
|
+
bookmarkXml = @bookmarks_storage.get(ch) || ""
|
231
|
+
bookmark = nil
|
232
|
+
if bookmark_validator(bookmarkXml, ch)
|
233
|
+
bookmark = Winevt::EventLog::Bookmark.new(bookmarkXml)
|
234
|
+
end
|
235
|
+
subscribe = Winevt::EventLog::Subscribe.new
|
236
|
+
subscribe.read_existing_events = read_existing_events
|
237
|
+
begin
|
238
|
+
subscribe.subscribe(ch, event_query, bookmark, remote_session)
|
239
|
+
if !@render_as_xml && @preserve_qualifiers_on_hash
|
240
|
+
subscribe.preserve_qualifiers = @preserve_qualifiers_on_hash
|
241
|
+
end
|
242
|
+
rescue Winevt::EventLog::Query::Error => e
|
243
|
+
raise Fluent::ConfigError, "Invalid Bookmark XML is loaded. #{e}"
|
244
|
+
end
|
245
|
+
subscribe.render_as_xml = @render_as_xml
|
246
|
+
subscribe.rate_limit = @rate_limit
|
247
|
+
subscribe.locale = @description_locale if @description_locale
|
248
|
+
[ch, subscribe]
|
249
|
+
end
|
250
|
+
|
251
|
+
def subscribe_channels(subscriptions)
|
252
|
+
subscriptions.each do |ch, subscribe|
|
253
|
+
log.trace "Subscribing Windows EventLog at #{ch} channel"
|
254
|
+
@timers[ch] = timer_execute("in_windows_eventlog_#{escape_channel(ch)}".to_sym, @read_interval) do
|
255
|
+
on_notify(ch, subscribe)
|
256
|
+
end
|
257
|
+
log.debug "channel (#{ch}) subscription is subscribed."
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def bookmark_validator(bookmarkXml, channel)
|
262
|
+
return false if bookmarkXml.empty?
|
263
|
+
|
264
|
+
evtxml = WinevtBookmarkDocument.new
|
265
|
+
parser = Nokogiri::XML::SAX::Parser.new(evtxml)
|
266
|
+
parser.parse(bookmarkXml)
|
267
|
+
result = evtxml.result
|
268
|
+
if !result.empty? && (result[:channel].downcase == channel.downcase) && result[:is_current]
|
269
|
+
true
|
270
|
+
else
|
271
|
+
log.warn "This stored bookmark is incomplete for using. Referring `read_existing_events` parameter to subscribe: #{bookmarkXml}, channel: #{channel}"
|
272
|
+
false
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def escape_channel(ch)
|
277
|
+
ch.gsub(/[^a-zA-Z0-9\s]/, '_')
|
278
|
+
end
|
279
|
+
|
280
|
+
def on_notify(ch, subscribe)
|
281
|
+
# for safety.
|
282
|
+
end
|
283
|
+
|
284
|
+
def on_notify_xml(ch, subscribe)
|
285
|
+
es = Fluent::MultiEventStream.new
|
286
|
+
begin
|
287
|
+
subscribe.each do |xml, message, string_inserts|
|
288
|
+
@parser.parse(xml) do |time, record|
|
289
|
+
# record.has_key?("EventData") for none parser checking.
|
290
|
+
if @winevt_xml
|
291
|
+
record["Description"] = message
|
292
|
+
record["EventData"] = string_inserts
|
293
|
+
|
294
|
+
h = {}
|
295
|
+
@keynames.each do |k|
|
296
|
+
type = KEY_MAP[k][1]
|
297
|
+
value = record[KEY_MAP[k][0]]
|
298
|
+
h[k]=case type
|
299
|
+
when :string
|
300
|
+
value.to_s
|
301
|
+
when :array
|
302
|
+
value.map {|v| v.to_s}
|
303
|
+
else
|
304
|
+
raise "Unknown value type: #{type}"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
parse_desc(h) if @parse_description
|
308
|
+
es.add(Fluent::Engine.now, h)
|
309
|
+
else
|
310
|
+
record["Description"] = message
|
311
|
+
record["EventData"] = string_inserts
|
312
|
+
# for none parser
|
313
|
+
es.add(Fluent::Engine.now, record)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
router.emit_stream(@tag, es)
|
318
|
+
@bookmarks_storage.put(ch, subscribe.bookmark)
|
319
|
+
log.trace "Collecting Windows EventLog from #{ch} channel. Collected size: #{es.size}"
|
320
|
+
rescue Winevt::EventLog::Query::Error => e
|
321
|
+
log.warn "Invalid XML data on #{ch}.", error: e
|
322
|
+
log.warn_backtrace
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def on_notify_hash(ch, subscribe)
|
327
|
+
es = Fluent::MultiEventStream.new
|
328
|
+
begin
|
329
|
+
subscribe.each do |record, message, string_inserts|
|
330
|
+
record["Description"] = message
|
331
|
+
record["EventData"] = string_inserts
|
332
|
+
h = {}
|
333
|
+
@keynames.each do |k|
|
334
|
+
type = KEY_MAP[k][1]
|
335
|
+
value = record[KEY_MAP[k][0]]
|
336
|
+
h[k]=case type
|
337
|
+
when :string
|
338
|
+
value.to_s
|
339
|
+
when :array
|
340
|
+
value.map {|v| v.to_s}
|
341
|
+
else
|
342
|
+
raise "Unknown value type: #{type}"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
parse_desc(h) if @parse_description
|
346
|
+
es.add(Fluent::Engine.now, h)
|
347
|
+
end
|
348
|
+
router.emit_stream(@tag, es)
|
349
|
+
@bookmarks_storage.put(ch, subscribe.bookmark)
|
350
|
+
log.trace "Collecting Windows EventLog from #{ch} channel. Collected size: #{es.size}"
|
351
|
+
rescue Winevt::EventLog::Query::Error => e
|
352
|
+
log.warn "Invalid Hash data on #{ch}.", error: e
|
353
|
+
log.warn_backtrace
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
#### These lines copied from in_windows_eventlog plugin:
|
358
|
+
#### https://github.com/fluent/fluent-plugin-windows-eventlog/blob/528290d896a885c7721f850943daa3a43a015f3d/lib/fluent/plugin/in_windows_eventlog.rb#L192-L232
|
359
|
+
GROUP_DELIMITER = "\r\n\r\n".freeze
|
360
|
+
RECORD_DELIMITER = "\r\n\t".freeze
|
361
|
+
FIELD_DELIMITER = "\t\t".freeze
|
362
|
+
NONE_FIELD_DELIMITER = "\t".freeze
|
363
|
+
|
364
|
+
def parse_desc(record)
|
365
|
+
desc = record.delete("Description".freeze)
|
366
|
+
return if desc.nil?
|
367
|
+
|
368
|
+
elems = desc.split(GROUP_DELIMITER)
|
369
|
+
record['DescriptionTitle'] = elems.shift
|
370
|
+
previous_key = nil
|
371
|
+
elems.each { |elem|
|
372
|
+
parent_key = nil
|
373
|
+
elem.split(RECORD_DELIMITER).each { |r|
|
374
|
+
key, value = if r.index(FIELD_DELIMITER)
|
375
|
+
r.split(FIELD_DELIMITER)
|
376
|
+
else
|
377
|
+
r.split(NONE_FIELD_DELIMITER)
|
378
|
+
end
|
379
|
+
key = "" if key.nil?
|
380
|
+
key.chop! # remove ':' from key
|
381
|
+
if value.nil?
|
382
|
+
parent_key = to_key(key)
|
383
|
+
else
|
384
|
+
# parsed value sometimes contain unexpected "\t". So remove it.
|
385
|
+
value.strip!
|
386
|
+
# merge empty key values into the previous non-empty key record.
|
387
|
+
if key.empty?
|
388
|
+
record[previous_key] = [record[previous_key], value].flatten.reject {|e| e.nil?}
|
389
|
+
elsif parent_key.nil?
|
390
|
+
record[to_key(key)] = value
|
391
|
+
else
|
392
|
+
k = "#{parent_key}.#{to_key(key)}"
|
393
|
+
record[k] = value
|
394
|
+
end
|
395
|
+
end
|
396
|
+
# XXX: This is for empty privileges record key.
|
397
|
+
# We should investigate whether an another case exists or not.
|
398
|
+
previous_key = to_key(key) unless key.empty?
|
399
|
+
}
|
400
|
+
}
|
401
|
+
end
|
402
|
+
|
403
|
+
def to_key(key)
|
404
|
+
key.downcase!
|
405
|
+
key.gsub!(' '.freeze, '_'.freeze)
|
406
|
+
key
|
407
|
+
end
|
408
|
+
####
|
409
|
+
end
|
410
|
+
end
|