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.
@@ -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
- ch, subscribe = subscription(ch, read_existing_events, session)
199
- @subscriptions[ch] = subscribe
200
- end
201
- end
202
- subscribe_channels(@subscriptions)
203
- end
204
-
205
- def clear_subscritpions
206
- @subscriptions.keys.each do |ch|
207
- subscription = @subscriptions.delete(ch)
208
- if subscription
209
- if subscription.cancel
210
- log.debug "channel (#{ch}) subscription is cancelled."
211
- subscription.close
212
- log.debug "channel (#{ch}) subscription handles are closed forcibly."
213
- end
214
- end
215
- end
216
- @timers.keys.each do |ch|
217
- timer = @timers.delete(ch)
218
- if timer
219
- event_loop_detach(timer)
220
- log.debug "channel (#{ch}) subscription watcher is detached."
221
- end
222
- end
223
- end
224
-
225
- def subscription(ch, read_existing_events, remote_session)
226
- bookmarkXml = @bookmarks_storage.get(ch) || ""
227
- bookmark = nil
228
- if bookmark_validator(bookmarkXml, ch)
229
- bookmark = Winevt::EventLog::Bookmark.new(bookmarkXml)
230
- end
231
- subscribe = Winevt::EventLog::Subscribe.new
232
- subscribe.read_existing_events = read_existing_events
233
- begin
234
- subscribe.subscribe(ch, event_query, bookmark, remote_session)
235
- if !@render_as_xml && @preserve_qualifiers_on_hash
236
- subscribe.preserve_qualifiers = @preserve_qualifiers_on_hash
237
- end
238
- rescue Winevt::EventLog::Query::Error => e
239
- raise Fluent::ConfigError, "Invalid Bookmark XML is loaded. #{e}"
240
- end
241
- subscribe.render_as_xml = @render_as_xml
242
- subscribe.rate_limit = @rate_limit
243
- subscribe.locale = @description_locale if @description_locale
244
- [ch, subscribe]
245
- end
246
-
247
- def subscribe_channels(subscriptions)
248
- subscriptions.each do |ch, subscribe|
249
- log.trace "Subscribing Windows EventLog at #{ch} channel"
250
- @timers[ch] = timer_execute("in_windows_eventlog_#{escape_channel(ch)}".to_sym, @read_interval) do
251
- on_notify(ch, subscribe)
252
- end
253
- log.debug "channel (#{ch}) subscription is subscribed."
254
- end
255
- end
256
-
257
- def bookmark_validator(bookmarkXml, channel)
258
- return false if bookmarkXml.empty?
259
-
260
- evtxml = WinevtBookmarkDocument.new
261
- parser = Nokogiri::XML::SAX::Parser.new(evtxml)
262
- parser.parse(bookmarkXml)
263
- result = evtxml.result
264
- if !result.empty? && (result[:channel].downcase == channel.downcase) && result[:is_current]
265
- true
266
- else
267
- log.warn "This stored bookmark is incomplete for using. Referring `read_existing_events` parameter to subscribe: #{bookmarkXml}, channel: #{channel}"
268
- false
269
- end
270
- end
271
-
272
- def escape_channel(ch)
273
- ch.gsub(/[^a-zA-Z0-9\s]/, '_')
274
- end
275
-
276
- def on_notify(ch, subscribe)
277
- # for safety.
278
- end
279
-
280
- def on_notify_xml(ch, subscribe)
281
- es = Fluent::MultiEventStream.new
282
- begin
283
- subscribe.each do |xml, message, string_inserts|
284
- @parser.parse(xml) do |time, record|
285
- # record.has_key?("EventData") for none parser checking.
286
- if @winevt_xml
287
- record["Description"] = message
288
- record["EventData"] = string_inserts
289
-
290
- h = {}
291
- @keynames.each do |k|
292
- type = KEY_MAP[k][1]
293
- value = record[KEY_MAP[k][0]]
294
- h[k]=case type
295
- when :string
296
- value.to_s
297
- when :array
298
- value.map {|v| v.to_s}
299
- else
300
- raise "Unknown value type: #{type}"
301
- end
302
- end
303
- parse_desc(h) if @parse_description
304
- es.add(Fluent::Engine.now, h)
305
- else
306
- record["Description"] = message
307
- record["EventData"] = string_inserts
308
- # for none parser
309
- es.add(Fluent::Engine.now, record)
310
- end
311
- end
312
- end
313
- router.emit_stream(@tag, es)
314
- @bookmarks_storage.put(ch, subscribe.bookmark)
315
- log.trace "Collecting Windows EventLog from #{ch} channel. Collected size: #{es.size}"
316
- rescue Winevt::EventLog::Query::Error => e
317
- log.warn "Invalid XML data on #{ch}.", error: e
318
- log.warn_backtrace
319
- end
320
- end
321
-
322
- def on_notify_hash(ch, subscribe)
323
- es = Fluent::MultiEventStream.new
324
- begin
325
- subscribe.each do |record, message, string_inserts|
326
- record["Description"] = message
327
- record["EventData"] = string_inserts
328
- h = {}
329
- @keynames.each do |k|
330
- type = KEY_MAP[k][1]
331
- value = record[KEY_MAP[k][0]]
332
- h[k]=case type
333
- when :string
334
- value.to_s
335
- when :array
336
- value.map {|v| v.to_s}
337
- else
338
- raise "Unknown value type: #{type}"
339
- end
340
- end
341
- parse_desc(h) if @parse_description
342
- es.add(Fluent::Engine.now, h)
343
- end
344
- router.emit_stream(@tag, es)
345
- @bookmarks_storage.put(ch, subscribe.bookmark)
346
- log.trace "Collecting Windows EventLog from #{ch} channel. Collected size: #{es.size}"
347
- rescue Winevt::EventLog::Query::Error => e
348
- log.warn "Invalid Hash data on #{ch}.", error: e
349
- log.warn_backtrace
350
- end
351
- end
352
-
353
- #### These lines copied from in_windows_eventlog plugin:
354
- #### https://github.com/fluent/fluent-plugin-windows-eventlog/blob/528290d896a885c7721f850943daa3a43a015f3d/lib/fluent/plugin/in_windows_eventlog.rb#L192-L232
355
- GROUP_DELIMITER = "\r\n\r\n".freeze
356
- RECORD_DELIMITER = "\r\n\t".freeze
357
- FIELD_DELIMITER = "\t\t".freeze
358
- NONE_FIELD_DELIMITER = "\t".freeze
359
-
360
- def parse_desc(record)
361
- desc = record.delete("Description".freeze)
362
- return if desc.nil?
363
-
364
- elems = desc.split(GROUP_DELIMITER)
365
- record['DescriptionTitle'] = elems.shift
366
- previous_key = nil
367
- elems.each { |elem|
368
- parent_key = nil
369
- elem.split(RECORD_DELIMITER).each { |r|
370
- key, value = if r.index(FIELD_DELIMITER)
371
- r.split(FIELD_DELIMITER)
372
- else
373
- r.split(NONE_FIELD_DELIMITER)
374
- end
375
- key = "" if key.nil?
376
- key.chop! # remove ':' from key
377
- if value.nil?
378
- parent_key = to_key(key)
379
- else
380
- # parsed value sometimes contain unexpected "\t". So remove it.
381
- value.strip!
382
- # merge empty key values into the previous non-empty key record.
383
- if key.empty?
384
- record[previous_key] = [record[previous_key], value].flatten.reject {|e| e.nil?}
385
- elsif parent_key.nil?
386
- record[to_key(key)] = value
387
- else
388
- k = "#{parent_key}.#{to_key(key)}"
389
- record[k] = value
390
- end
391
- end
392
- # XXX: This is for empty privileges record key.
393
- # We should investigate whether an another case exists or not.
394
- previous_key = to_key(key) unless key.empty?
395
- }
396
- }
397
- end
398
-
399
- def to_key(key)
400
- key.downcase!
401
- key.gsub!(' '.freeze, '_'.freeze)
402
- key
403
- end
404
- ####
405
- end
406
- end
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