fluent-plugin-windows-eventlog 0.8.0 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,572 +1,619 @@
1
- require 'helper'
2
- require 'fileutils'
3
- require 'generate-windows-event'
4
-
5
- # Monkey patch for testing
6
- class Winevt::EventLog::Session
7
- def ==(obj)
8
- self.server == obj.server &&
9
- self.domain == obj.domain &&
10
- self.username == obj.username &&
11
- self.password == obj.password &&
12
- self.flags == obj.flags
13
- end
14
- end
15
-
16
- class WindowsEventLog2InputTest < Test::Unit::TestCase
17
-
18
- def setup
19
- Fluent::Test.setup
20
- end
21
-
22
- CONFIG = config_element("ROOT", "", {"tag" => "fluent.eventlog"}, [
23
- config_element("storage", "", {
24
- '@type' => 'local',
25
- 'persistent' => false
26
- })
27
- ])
28
-
29
- XML_RENDERING_CONFIG = config_element("ROOT", "", {"tag" => "fluent.eventlog",
30
- "render_as_xml" => true}, [
31
- config_element("storage", "", {
32
- '@type' => 'local',
33
- 'persistent' => false
34
- })
35
- ])
36
-
37
- def create_driver(conf = CONFIG)
38
- Fluent::Test::Driver::Input.new(Fluent::Plugin::WindowsEventLog2Input).configure(conf)
39
- end
40
-
41
- def test_configure
42
- d = create_driver CONFIG
43
- assert_equal 'fluent.eventlog', d.instance.tag
44
- assert_equal 2, d.instance.read_interval
45
- assert_equal [], d.instance.channels
46
- assert_false d.instance.read_existing_events
47
- assert_false d.instance.render_as_xml
48
- assert_nil d.instance.refresh_subscription_interval
49
- end
50
-
51
- sub_test_case "configure" do
52
- test "refresh subscription interval" do
53
- d = create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog",
54
- "refresh_subscription_interval" => "2m"}, [
55
- config_element("storage", "", {
56
- '@type' => 'local',
57
- 'persistent' => false
58
- })
59
- ])
60
- assert_equal 120, d.instance.refresh_subscription_interval
61
- end
62
-
63
- test "subscribe directive" do
64
- d = create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog"}, [
65
- config_element("storage", "", {
66
- '@type' => 'local',
67
- 'persistent' => false
68
- }),
69
- config_element("subscribe", "", {
70
- 'channels' => ['System', 'Windows PowerShell'],
71
- }),
72
- config_element("subscribe", "", {
73
- 'channels' => ['Security'],
74
- 'read_existing_events' => true
75
- }),
76
- ])
77
- expected = [["system", false, nil], ["windows powershell", false, nil], ["security", true, nil]]
78
- assert_equal expected, d.instance.instance_variable_get(:@chs)
79
- end
80
-
81
- test "subscribe directive with remote server session" do
82
- d = create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog"}, [
83
- config_element("storage", "", {
84
- '@type' => 'local',
85
- 'persistent' => false
86
- }),
87
- config_element("subscribe", "", {
88
- 'channels' => ['System', 'Windows PowerShell'],
89
- 'remote_server' => '127.0.0.1',
90
- }),
91
- config_element("subscribe", "", {
92
- 'channels' => ['Security'],
93
- 'read_existing_events' => true,
94
- 'remote_server' => '192.168.0.1',
95
- 'remote_username' => 'fluentd',
96
- 'remote_password' => 'changeme!'
97
- }),
98
- ])
99
- localhost_session = Winevt::EventLog::Session.new("127.0.0.1")
100
- remote_session = Winevt::EventLog::Session.new("192.168.0.1",
101
- nil,
102
- "fluentd",
103
- "changeme!")
104
- expected = [["system", false, localhost_session],
105
- ["windows powershell", false, localhost_session],
106
- ["security", true, remote_session]]
107
- assert_equal expected, d.instance.instance_variable_get(:@chs)
108
- end
109
-
110
- test "duplicated subscribe" do
111
- d = create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog",
112
- "channels" => ["System", "Windows PowerShell"]
113
- }, [
114
- config_element("storage", "", {
115
- '@type' => 'local',
116
- 'persistent' => false
117
- }),
118
- config_element("subscribe", "", {
119
- 'channels' => ['System', 'Windows PowerShell'],
120
- }),
121
- config_element("subscribe", "", {
122
- 'channels' => ['Security'],
123
- 'read_existing_events' => true
124
- }),
125
- ])
126
- expected = [["system", false, nil], ["windows powershell", false, nil], ["security", true, nil]]
127
- assert_equal 1, d.instance.instance_variable_get(:@chs).select {|ch, flag| ch == "system"}.size
128
- assert_equal expected, d.instance.instance_variable_get(:@chs)
129
- end
130
-
131
- test "non duplicated subscribe" do
132
- d = create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog",
133
- "channels" => ["System", "Windows PowerShell"]
134
- }, [
135
- config_element("storage", "", {
136
- '@type' => 'local',
137
- 'persistent' => false
138
- }),
139
- config_element("subscribe", "", {
140
- 'channels' => ['System', 'Windows PowerShell'],
141
- 'read_existing_events' => true
142
- }),
143
- config_element("subscribe", "", {
144
- 'channels' => ['Security'],
145
- 'read_existing_events' => true
146
- }),
147
- ])
148
- expected = [["system", false, nil], ["windows powershell", false, nil], ["system", true, nil], ["windows powershell", true, nil], ["security", true, nil]]
149
- assert_equal 2, d.instance.instance_variable_get(:@chs).select {|ch, flag| ch == "system"}.size
150
- assert_equal expected, d.instance.instance_variable_get(:@chs)
151
- end
152
-
153
- test "invalid combination for preserving qualifiers" do
154
- assert_raise(Fluent::ConfigError) do
155
- create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog",
156
- "render_as_xml" => true,
157
- "preserve_qualifiers_on_hash" => true,
158
- }, [
159
- config_element("storage", "", {
160
- '@type' => 'local',
161
- 'persistent' => false
162
- }),
163
- ])
164
- end
165
- end
166
-
167
- test "invalid description locale" do
168
- assert_raise(Fluent::ConfigError) do
169
- create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog",
170
- "description_locale" => "ex_EX"
171
- }, [
172
- config_element("storage", "", {
173
- '@type' => 'local',
174
- 'persistent' => false
175
- })
176
- ])
177
- end
178
- end
179
- end
180
-
181
- data("Japanese" => ["ja_JP", false],
182
- "English (United States)" => ["en_US", false],
183
- "English (UK)" => ["en_GB", false],
184
- "Dutch" => ["nl_NL", false],
185
- "French" => ["fr_FR", false],
186
- "German" => ["de_DE", false],
187
- "Russian" => ["ru_RU", false],
188
- "Spanish" => ["es_ES", false],
189
- "Invalid" => ["ex_EX", true],
190
- )
191
- def test_unsupported_locale_p(data)
192
- description_locale, expected = data
193
- d = create_driver CONFIG
194
- locale = Winevt::EventLog::Locale.new
195
- result = d.instance.unsupported_locale?(locale, description_locale)
196
- assert_equal expected, result
197
- end
198
-
199
- data("application" => ["Application", "Application"],
200
- "windows powershell" => ["Windows PowerShell", "Windows PowerShell"],
201
- "escaped" => ["Should_Be_Escaped_", "Should+Be;Escaped/"]
202
- )
203
- def test_escape_channel(data)
204
- expected, actual = data
205
- d = create_driver CONFIG
206
- assert_equal expected, d.instance.escape_channel(actual)
207
- end
208
-
209
- def test_parse_desc
210
- d = create_driver
211
- desc =<<-DESC
212
- A user's local group membership was enumerated.\r\n\r\nSubject:\r\n\tSecurity ID:\t\tS-X-Y-XX-WWWWWW-VVVV\r\n\tAccount Name:\t\tAdministrator\r\n\tAccount Domain:\t\tDESKTOP-FLUENTTEST\r\n\tLogon ID:\t\t0x3185B1\r\n\r\nUser:\r\n\tSecurity ID:\t\tS-X-Y-XX-WWWWWW-VVVV\r\n\tAccount Name:\t\tAdministrator\r\n\tAccount Domain:\t\tDESKTOP-FLUENTTEST\r\n\r\nProcess Information:\r\n\tProcess ID:\t\t0x50b8\r\n\tProcess Name:\t\tC:\\msys64\\usr\\bin\\make.exe
213
- DESC
214
- h = {"Description" => desc}
215
- expected = {"DescriptionTitle" => "A user's local group membership was enumerated.",
216
- "subject.security_id" => "S-X-Y-XX-WWWWWW-VVVV",
217
- "subject.account_name" => "Administrator",
218
- "subject.account_domain" => "DESKTOP-FLUENTTEST",
219
- "subject.logon_id" => "0x3185B1",
220
- "user.security_id" => "S-X-Y-XX-WWWWWW-VVVV",
221
- "user.account_name" => "Administrator",
222
- "user.account_domain" => "DESKTOP-FLUENTTEST",
223
- "process_information.process_id" => "0x50b8",
224
- "process_information.process_name" => "C:\\msys64\\usr\\bin\\make.exe"}
225
- d.instance.parse_desc(h)
226
- assert_equal(expected, h)
227
- end
228
-
229
- def test_parse_privileges_description
230
- d = create_driver
231
- desc = ["Special privileges assigned to new logon.\r\n\r\nSubject:\r\n\tSecurity ID:\t\tS-X-Y-ZZ\r\n\t",
232
- "AccountName:\t\tSYSTEM\r\n\tAccount Domain:\t\tNT AUTHORITY\r\n\tLogon ID:\t\t0x3E7\r\n\r\n",
233
- "Privileges:\t\tSeAssignPrimaryTokenPrivilege\r\n\t\t\tSeTcbPrivilege\r\n\t\t\t",
234
- "SeSecurityPrivilege\r\n\t\t\tSeTakeOwnershipPrivilege\r\n\t\t\tSeLoadDriverPrivilege\r\n\t\t\t",
235
- "SeBackupPrivilege\r\n\t\t\tSeRestorePrivilege\r\n\t\t\tSeDebugPrivilege\r\n\t\t\t",
236
- "SeAuditPrivilege\r\n\t\t\tSeSystemEnvironmentPrivilege\r\n\t\t\tSeImpersonatePrivilege\r\n\t\t\t",
237
- "SeDelegateSessionUserImpersonatePrivilege"].join("")
238
-
239
- h = {"Description" => desc}
240
- expected = {"DescriptionTitle" => "Special privileges assigned to new logon.",
241
- "subject.security_id" => "S-X-Y-ZZ",
242
- "subject.accountname" => "SYSTEM",
243
- "subject.account_domain" => "NT AUTHORITY",
244
- "subject.logon_id" => "0x3E7",
245
- "privileges" => ["SeAssignPrimaryTokenPrivilege",
246
- "SeTcbPrivilege",
247
- "SeSecurityPrivilege",
248
- "SeTakeOwnershipPrivilege",
249
- "SeLoadDriverPrivilege",
250
- "SeBackupPrivilege",
251
- "SeRestorePrivilege",
252
- "SeDebugPrivilege",
253
- "SeAuditPrivilege",
254
- "SeSystemEnvironmentPrivilege",
255
- "SeImpersonatePrivilege",
256
- "SeDelegateSessionUserImpersonatePrivilege"]}
257
- d.instance.parse_desc(h)
258
- assert_equal(expected, h)
259
- end
260
-
261
- test "A new external device was recognized by the system." do
262
- # using the event log example: eventopedia.cloudapp.net/EventDetails.aspx?id=17ef124e-eb89-4c01-9ba2-d761e06b2b68
263
- d = create_driver
264
- desc = nil
265
- File.open('./test/data/eventid_6416', 'r') do |f|
266
- desc = f.read.gsub(/\R/, "\r\n")
267
- end
268
- h = {"Description" => desc}
269
- expected = {"DescriptionTitle" => "A new external device was recognized by the system.",
270
- "class_id" => "{1ed2bbf9-11f0-4084-b21f-ad83a8e6dcdc}",
271
- "class_name" => "PrintQueue",
272
- "compatible_ids" => ["GenPrintQueue", "SWD\\GenericRaw", "SWD\\Generic"],
273
- "device_id" => "SWD\\PRINTENUM\\{60FA1C6A-1AB2-440A-AEE1-62ABFB9A4650}",
274
- "device_name" => "Microsoft Print to PDF",
275
- "subject.account_domain" => "ITSS",
276
- "subject.account_name" => "IIZHU2016$",
277
- "subject.logon_id" => "0x3E7",
278
- "subject.security_id" => "SYSTEM",
279
- "vendor_ids" => ["PRINTENUM\\{084f01fa-e634-4d77-83ee-074817c03581}",
280
- "PRINTENUM\\LocalPrintQueue",
281
- "{084f01fa-e634-4d77-83ee-074817c03581}"]}
282
- d.instance.parse_desc(h)
283
- assert_equal(expected, h)
284
- end
285
-
286
- def test_write
287
- d = create_driver
288
-
289
- service = Fluent::Plugin::EventService.new
290
-
291
- d.run(expect_emits: 1) do
292
- service.run
293
- end
294
-
295
- assert(d.events.length >= 1)
296
- event = d.events.select {|e| e.last["EventID"] == "65500" }.last
297
- record = event.last
298
-
299
- assert_equal("Application", record["Channel"])
300
- assert_equal("65500", record["EventID"])
301
- assert_equal("4", record["Level"])
302
- assert_equal("fluent-plugins", record["ProviderName"])
303
- end
304
-
305
- CONFIG_KEYS = config_element("ROOT", "", {
306
- "tag" => "fluent.eventlog",
307
- "keys" => ["EventID", "Level", "Channel", "ProviderName"]
308
- }, [
309
- config_element("storage", "", {
310
- '@type' => 'local',
311
- 'persistent' => false
312
- })
313
- ])
314
- def test_write_with_keys
315
- d = create_driver(CONFIG_KEYS)
316
-
317
- service = Fluent::Plugin::EventService.new
318
-
319
- d.run(expect_emits: 1) do
320
- service.run
321
- end
322
-
323
- assert(d.events.length >= 1)
324
- event = d.events.select {|e| e.last["EventID"] == "65500" }.last
325
- record = event.last
326
-
327
- expected = {"EventID" => "65500",
328
- "Level" => "4",
329
- "Channel" => "Application",
330
- "ProviderName" => "fluent-plugins"}
331
-
332
- assert_equal(expected, record)
333
- end
334
-
335
- REMOTING_ACCESS_CONFIG = config_element("ROOT", "", {"tag" => "fluent.eventlog"}, [
336
- config_element("storage", "", {
337
- '@type' => 'local',
338
- 'persistent' => false
339
- }),
340
- config_element("subscribe", "", {
341
- 'channels' => ['Application'],
342
- 'remote_server' => '127.0.0.1',
343
- }),
344
- ])
345
-
346
- def test_write_with_remoting_access
347
- d = create_driver(REMOTING_ACCESS_CONFIG)
348
-
349
- service = Fluent::Plugin::EventService.new
350
-
351
- d.run(expect_emits: 1) do
352
- service.run
353
- end
354
-
355
- assert(d.events.length >= 1)
356
- event = d.events.select {|e| e.last["EventID"] == "65500" }.last
357
- record = event.last
358
-
359
- assert_equal("Application", record["Channel"])
360
- assert_equal("65500", record["EventID"])
361
- assert_equal("4", record["Level"])
362
- assert_equal("fluent-plugins", record["ProviderName"])
363
- end
364
-
365
- class HashRendered < self
366
- def test_write
367
- d = create_driver(config_element("ROOT", "", {"tag" => "fluent.eventlog",
368
- "render_as_xml" => false}, [
369
- config_element("storage", "", {
370
- '@type' => 'local',
371
- 'persistent' => false
372
- })
373
- ]))
374
-
375
- service = Fluent::Plugin::EventService.new
376
-
377
- d.run(expect_emits: 1) do
378
- service.run
379
- end
380
-
381
- assert(d.events.length >= 1)
382
- event = d.events.select {|e| e.last["EventID"] == "65500" }.last
383
- record = event.last
384
-
385
- assert_false(d.instance.render_as_xml)
386
- assert_equal("Application", record["Channel"])
387
- assert_equal("65500", record["EventID"])
388
- assert_equal("4", record["Level"])
389
- assert_equal("fluent-plugins", record["ProviderName"])
390
- end
391
-
392
- def test_write_with_preserving_qualifiers
393
- require 'winevt'
394
-
395
- d = create_driver(config_element("ROOT", "", {"tag" => "fluent.eventlog",
396
- "render_as_xml" => false,
397
- 'preserve_qualifiers_on_hash' => true
398
- }, [
399
- config_element("storage", "", {
400
- '@type' => 'local',
401
- 'persistent' => false
402
- }),
403
- ]))
404
-
405
- service = Fluent::Plugin::EventService.new
406
- subscribe = Winevt::EventLog::Subscribe.new
407
-
408
- omit "@parser.preserve_qualifiers does not respond" unless subscribe.respond_to?(:preserve_qualifiers?)
409
-
410
- d.run(expect_emits: 1) do
411
- service.run
412
- end
413
-
414
- assert(d.events.length >= 1)
415
- event = d.events.last
416
- record = event.last
417
-
418
- assert_true(record.has_key?("Description"))
419
- assert_true(record.has_key?("EventData"))
420
- assert_true(record.has_key?("Qualifiers"))
421
- end
422
- end
423
-
424
- class PersistBookMark < self
425
- TEST_PLUGIN_STORAGE_PATH = File.join( File.dirname(File.dirname(__FILE__)), 'tmp', 'in_windows_eventlog2', 'store' )
426
- CONFIG2 = config_element("ROOT", "", {"tag" => "fluent.eventlog"}, [
427
- config_element("storage", "", {
428
- '@type' => 'local',
429
- '@id' => 'test-02',
430
- '@log_level' => "info",
431
- 'path' => File.join(TEST_PLUGIN_STORAGE_PATH,
432
- 'json', 'test-02.json'),
433
- 'persistent' => true,
434
- })
435
- ])
436
-
437
- def setup
438
- FileUtils.rm_rf(TEST_PLUGIN_STORAGE_PATH)
439
- FileUtils.mkdir_p(File.join(TEST_PLUGIN_STORAGE_PATH, 'json'))
440
- FileUtils.chmod_R(0755, File.join(TEST_PLUGIN_STORAGE_PATH, 'json'))
441
- end
442
-
443
- def test_write
444
- d = create_driver(CONFIG2)
445
-
446
- assert !File.exist?(File.join(TEST_PLUGIN_STORAGE_PATH, 'json', 'test-02.json'))
447
-
448
- service = Fluent::Plugin::EventService.new
449
-
450
- d.run(expect_emits: 1) do
451
- service.run
452
- end
453
-
454
- assert(d.events.length >= 1)
455
- event = d.events.select {|e| e.last["EventID"] == "65500" }.last
456
- record = event.last
457
-
458
- prev_id = record["EventRecordID"].to_i
459
- assert_equal("Application", record["Channel"])
460
- assert_equal("65500", record["EventID"])
461
- assert_equal("4", record["Level"])
462
- assert_equal("fluent-plugins", record["ProviderName"])
463
-
464
- assert File.exist?(File.join(TEST_PLUGIN_STORAGE_PATH, 'json', 'test-02.json'))
465
-
466
- d2 = create_driver(CONFIG2)
467
- d2.run(expect_emits: 1) do
468
- service.run
469
- end
470
-
471
- assert(d2.events.length == 1) # should be tailing after previous context.
472
- event2 = d2.events.last
473
- record2 = event2.last
474
-
475
- curr_id = record2["EventRecordID"].to_i
476
- assert(curr_id > prev_id)
477
-
478
- assert File.exist?(File.join(TEST_PLUGIN_STORAGE_PATH, 'json', 'test-02.json'))
479
- end
480
-
481
- def test_start_with_invalid_bookmark
482
- invalid_storage_contents = <<-EOS
483
- <BookmarkList>\r\n <Bookmark Channel='Application' RecordId='20063' IsCurrent='true'/>\r\n
484
- EOS
485
- d = create_driver(CONFIG2)
486
- storage = d.instance.instance_variable_get(:@bookmarks_storage)
487
- storage.put('application', invalid_storage_contents)
488
- assert File.exist?(File.join(TEST_PLUGIN_STORAGE_PATH, 'json', 'test-02.json'))
489
-
490
- d2 = create_driver(CONFIG2)
491
- assert_raise(Fluent::ConfigError) do
492
- d2.instance.start
493
- end
494
- assert_equal 0, d2.logs.grep(/This stored bookmark is incomplete for using. Referring `read_existing_events` parameter to subscribe:/).length
495
- end
496
-
497
- def test_start_with_empty_bookmark
498
- invalid_storage_contents = <<-EOS
499
- <BookmarkList>\r\n</BookmarkList>
500
- EOS
501
- d = create_driver(CONFIG2)
502
- storage = d.instance.instance_variable_get(:@bookmarks_storage)
503
- storage.put('application', invalid_storage_contents)
504
- assert File.exist?(File.join(TEST_PLUGIN_STORAGE_PATH, 'json', 'test-02.json'))
505
-
506
- d2 = create_driver(CONFIG2)
507
- d2.instance.start
508
- assert_equal 1, d2.logs.grep(/This stored bookmark is incomplete for using. Referring `read_existing_events` parameter to subscribe:/).length
509
- end
510
- end
511
-
512
- def test_write_with_none_parser
513
- d = create_driver(config_element("ROOT", "", {"tag" => "fluent.eventlog",
514
- "render_as_xml" => true}, [
515
- config_element("storage", "", {
516
- '@type' => 'local',
517
- 'persistent' => false
518
- }),
519
- config_element("parse", "", {
520
- '@type' => 'none',
521
- }),
522
- ]))
523
-
524
- service = Fluent::Plugin::EventService.new
525
-
526
- d.run(expect_emits: 1) do
527
- service.run
528
- end
529
-
530
- assert(d.events.length >= 1)
531
- event = d.events.last
532
- record = event.last
533
-
534
- assert do
535
- # record should be {message: <RAW XML EventLog>}.
536
- record["message"]
537
- end
538
-
539
- assert_true(record.has_key?("Description"))
540
- assert_true(record.has_key?("EventData"))
541
- end
542
-
543
- def test_write_with_winevt_xml_parser_without_qualifiers
544
- d = create_driver(config_element("ROOT", "", {"tag" => "fluent.eventlog",
545
- "render_as_xml" => true}, [
546
- config_element("storage", "", {
547
- '@type' => 'local',
548
- 'persistent' => false
549
- }),
550
- config_element("parse", "", {
551
- '@type' => 'winevt_xml',
552
- 'preserve_qualifiers' => false
553
- }),
554
- ]))
555
-
556
- service = Fluent::Plugin::EventService.new
557
-
558
- omit "@parser.preserve_qualifiers does not respond" unless d.instance.instance_variable_get(:@parser).respond_to?(:preserve_qualifiers?)
559
-
560
- d.run(expect_emits: 1) do
561
- service.run
562
- end
563
-
564
- assert(d.events.length >= 1)
565
- event = d.events.last
566
- record = event.last
567
-
568
- assert_true(record.has_key?("Description"))
569
- assert_true(record.has_key?("EventData"))
570
- assert_false(record.has_key?("Qualifiers"))
571
- end
572
- end
1
+ require 'helper'
2
+ require 'fileutils'
3
+ require 'generate-windows-event'
4
+
5
+ # Monkey patch for testing
6
+ class Winevt::EventLog::Session
7
+ def ==(obj)
8
+ self.server == obj.server &&
9
+ self.domain == obj.domain &&
10
+ self.username == obj.username &&
11
+ self.password == obj.password &&
12
+ self.flags == obj.flags
13
+ end
14
+ end
15
+
16
+ class WindowsEventLog2InputTest < Test::Unit::TestCase
17
+
18
+ def setup
19
+ Fluent::Test.setup
20
+ end
21
+
22
+ CONFIG = config_element("ROOT", "", {"tag" => "fluent.eventlog"}, [
23
+ config_element("storage", "", {
24
+ '@type' => 'local',
25
+ 'persistent' => false
26
+ })
27
+ ])
28
+
29
+ XML_RENDERING_CONFIG = config_element("ROOT", "", {"tag" => "fluent.eventlog",
30
+ "render_as_xml" => true}, [
31
+ config_element("storage", "", {
32
+ '@type' => 'local',
33
+ 'persistent' => false
34
+ })
35
+ ])
36
+
37
+ def create_driver(conf = CONFIG)
38
+ Fluent::Test::Driver::Input.new(Fluent::Plugin::WindowsEventLog2Input).configure(conf)
39
+ end
40
+
41
+ def test_configure
42
+ d = create_driver CONFIG
43
+ assert_equal 'fluent.eventlog', d.instance.tag
44
+ assert_equal 2, d.instance.read_interval
45
+ assert_equal [], d.instance.channels
46
+ assert_false d.instance.read_existing_events
47
+ assert_false d.instance.render_as_xml
48
+ assert_nil d.instance.refresh_subscription_interval
49
+ end
50
+
51
+ sub_test_case "configure" do
52
+ test "refresh subscription interval" do
53
+ d = create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog",
54
+ "refresh_subscription_interval" => "2m"}, [
55
+ config_element("storage", "", {
56
+ '@type' => 'local',
57
+ 'persistent' => false
58
+ })
59
+ ])
60
+ assert_equal 120, d.instance.refresh_subscription_interval
61
+ end
62
+
63
+ test "subscribe directive" do
64
+ d = create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog"}, [
65
+ config_element("storage", "", {
66
+ '@type' => 'local',
67
+ 'persistent' => false
68
+ }),
69
+ config_element("subscribe", "", {
70
+ 'channels' => ['System', 'Windows PowerShell'],
71
+ }),
72
+ config_element("subscribe", "", {
73
+ 'channels' => ['Security'],
74
+ 'read_existing_events' => true
75
+ }),
76
+ ])
77
+ expected = [["system", false, nil], ["windows powershell", false, nil], ["security", true, nil]]
78
+ assert_equal expected, d.instance.instance_variable_get(:@chs)
79
+ end
80
+
81
+ test "subscribe directive with remote server session" do
82
+ d = create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog"}, [
83
+ config_element("storage", "", {
84
+ '@type' => 'local',
85
+ 'persistent' => false
86
+ }),
87
+ config_element("subscribe", "", {
88
+ 'channels' => ['System', 'Windows PowerShell'],
89
+ 'remote_server' => '127.0.0.1',
90
+ }),
91
+ config_element("subscribe", "", {
92
+ 'channels' => ['Security'],
93
+ 'read_existing_events' => true,
94
+ 'remote_server' => '192.168.0.1',
95
+ 'remote_username' => 'fluentd',
96
+ 'remote_password' => 'changeme!'
97
+ }),
98
+ ])
99
+ localhost_session = Winevt::EventLog::Session.new("127.0.0.1")
100
+ remote_session = Winevt::EventLog::Session.new("192.168.0.1",
101
+ nil,
102
+ "fluentd",
103
+ "changeme!")
104
+ expected = [["system", false, localhost_session],
105
+ ["windows powershell", false, localhost_session],
106
+ ["security", true, remote_session]]
107
+ assert_equal expected, d.instance.instance_variable_get(:@chs)
108
+ end
109
+
110
+ test "duplicated subscribe" do
111
+ d = create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog",
112
+ "channels" => ["System", "Windows PowerShell"]
113
+ }, [
114
+ config_element("storage", "", {
115
+ '@type' => 'local',
116
+ 'persistent' => false
117
+ }),
118
+ config_element("subscribe", "", {
119
+ 'channels' => ['System', 'Windows PowerShell'],
120
+ }),
121
+ config_element("subscribe", "", {
122
+ 'channels' => ['Security'],
123
+ 'read_existing_events' => true
124
+ }),
125
+ ])
126
+ expected = [["system", false, nil], ["windows powershell", false, nil], ["security", true, nil]]
127
+ assert_equal 1, d.instance.instance_variable_get(:@chs).select {|ch, flag| ch == "system"}.size
128
+ assert_equal expected, d.instance.instance_variable_get(:@chs)
129
+ end
130
+
131
+ test "non duplicated subscribe" do
132
+ d = create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog",
133
+ "channels" => ["System", "Windows PowerShell"]
134
+ }, [
135
+ config_element("storage", "", {
136
+ '@type' => 'local',
137
+ 'persistent' => false
138
+ }),
139
+ config_element("subscribe", "", {
140
+ 'channels' => ['System', 'Windows PowerShell'],
141
+ 'read_existing_events' => true
142
+ }),
143
+ config_element("subscribe", "", {
144
+ 'channels' => ['Security'],
145
+ 'read_existing_events' => true
146
+ }),
147
+ ])
148
+ expected = [["system", false, nil], ["windows powershell", false, nil], ["system", true, nil], ["windows powershell", true, nil], ["security", true, nil]]
149
+ assert_equal 2, d.instance.instance_variable_get(:@chs).select {|ch, flag| ch == "system"}.size
150
+ assert_equal expected, d.instance.instance_variable_get(:@chs)
151
+ end
152
+
153
+ test "invalid combination for preserving qualifiers" do
154
+ assert_raise(Fluent::ConfigError) do
155
+ create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog",
156
+ "render_as_xml" => true,
157
+ "preserve_qualifiers_on_hash" => true,
158
+ }, [
159
+ config_element("storage", "", {
160
+ '@type' => 'local',
161
+ 'persistent' => false
162
+ }),
163
+ ])
164
+ end
165
+ end
166
+
167
+ test "invalid description locale" do
168
+ assert_raise(Fluent::ConfigError) do
169
+ create_driver config_element("ROOT", "", {"tag" => "fluent.eventlog",
170
+ "description_locale" => "ex_EX"
171
+ }, [
172
+ config_element("storage", "", {
173
+ '@type' => 'local',
174
+ 'persistent' => false
175
+ })
176
+ ])
177
+ end
178
+ end
179
+ end
180
+
181
+ data("Japanese" => ["ja_JP", false],
182
+ "English (United States)" => ["en_US", false],
183
+ "English (UK)" => ["en_GB", false],
184
+ "Dutch" => ["nl_NL", false],
185
+ "French" => ["fr_FR", false],
186
+ "German" => ["de_DE", false],
187
+ "Russian" => ["ru_RU", false],
188
+ "Spanish" => ["es_ES", false],
189
+ "Invalid" => ["ex_EX", true],
190
+ )
191
+ def test_unsupported_locale_p(data)
192
+ description_locale, expected = data
193
+ d = create_driver CONFIG
194
+ locale = Winevt::EventLog::Locale.new
195
+ result = d.instance.unsupported_locale?(locale, description_locale)
196
+ assert_equal expected, result
197
+ end
198
+
199
+ data("application" => ["Application", "Application"],
200
+ "windows powershell" => ["Windows PowerShell", "Windows PowerShell"],
201
+ "escaped" => ["Should_Be_Escaped_", "Should+Be;Escaped/"]
202
+ )
203
+ def test_escape_channel(data)
204
+ expected, actual = data
205
+ d = create_driver CONFIG
206
+ assert_equal expected, d.instance.escape_channel(actual)
207
+ end
208
+
209
+ def test_parse_desc
210
+ d = create_driver
211
+ desc =<<-DESC
212
+ A user's local group membership was enumerated.\r\n\r\nSubject:\r\n\tSecurity ID:\t\tS-X-Y-XX-WWWWWW-VVVV\r\n\tAccount Name:\t\tAdministrator\r\n\tAccount Domain:\t\tDESKTOP-FLUENTTEST\r\n\tLogon ID:\t\t0x3185B1\r\n\r\nUser:\r\n\tSecurity ID:\t\tS-X-Y-XX-WWWWWW-VVVV\r\n\tAccount Name:\t\tAdministrator\r\n\tAccount Domain:\t\tDESKTOP-FLUENTTEST\r\n\r\nProcess Information:\r\n\tProcess ID:\t\t0x50b8\r\n\tProcess Name:\t\tC:\\msys64\\usr\\bin\\make.exe
213
+ DESC
214
+ h = {"Description" => desc}
215
+ expected = {"DescriptionTitle" => "A user's local group membership was enumerated.",
216
+ "subject.security_id" => "S-X-Y-XX-WWWWWW-VVVV",
217
+ "subject.account_name" => "Administrator",
218
+ "subject.account_domain" => "DESKTOP-FLUENTTEST",
219
+ "subject.logon_id" => "0x3185B1",
220
+ "user.security_id" => "S-X-Y-XX-WWWWWW-VVVV",
221
+ "user.account_name" => "Administrator",
222
+ "user.account_domain" => "DESKTOP-FLUENTTEST",
223
+ "process_information.process_id" => "0x50b8",
224
+ "process_information.process_name" => "C:\\msys64\\usr\\bin\\make.exe"}
225
+ d.instance.parse_desc(h)
226
+ assert_equal(expected, h)
227
+ end
228
+
229
+ def test_parse_privileges_description
230
+ d = create_driver
231
+ desc = ["Special privileges assigned to new logon.\r\n\r\nSubject:\r\n\tSecurity ID:\t\tS-X-Y-ZZ\r\n\t",
232
+ "AccountName:\t\tSYSTEM\r\n\tAccount Domain:\t\tNT AUTHORITY\r\n\tLogon ID:\t\t0x3E7\r\n\r\n",
233
+ "Privileges:\t\tSeAssignPrimaryTokenPrivilege\r\n\t\t\tSeTcbPrivilege\r\n\t\t\t",
234
+ "SeSecurityPrivilege\r\n\t\t\tSeTakeOwnershipPrivilege\r\n\t\t\tSeLoadDriverPrivilege\r\n\t\t\t",
235
+ "SeBackupPrivilege\r\n\t\t\tSeRestorePrivilege\r\n\t\t\tSeDebugPrivilege\r\n\t\t\t",
236
+ "SeAuditPrivilege\r\n\t\t\tSeSystemEnvironmentPrivilege\r\n\t\t\tSeImpersonatePrivilege\r\n\t\t\t",
237
+ "SeDelegateSessionUserImpersonatePrivilege"].join("")
238
+
239
+ h = {"Description" => desc}
240
+ expected = {"DescriptionTitle" => "Special privileges assigned to new logon.",
241
+ "subject.security_id" => "S-X-Y-ZZ",
242
+ "subject.accountname" => "SYSTEM",
243
+ "subject.account_domain" => "NT AUTHORITY",
244
+ "subject.logon_id" => "0x3E7",
245
+ "privileges" => ["SeAssignPrimaryTokenPrivilege",
246
+ "SeTcbPrivilege",
247
+ "SeSecurityPrivilege",
248
+ "SeTakeOwnershipPrivilege",
249
+ "SeLoadDriverPrivilege",
250
+ "SeBackupPrivilege",
251
+ "SeRestorePrivilege",
252
+ "SeDebugPrivilege",
253
+ "SeAuditPrivilege",
254
+ "SeSystemEnvironmentPrivilege",
255
+ "SeImpersonatePrivilege",
256
+ "SeDelegateSessionUserImpersonatePrivilege"]}
257
+ d.instance.parse_desc(h)
258
+ assert_equal(expected, h)
259
+ end
260
+
261
+ test "A new external device was recognized by the system." do
262
+ # using the event log example: eventopedia.cloudapp.net/EventDetails.aspx?id=17ef124e-eb89-4c01-9ba2-d761e06b2b68
263
+ d = create_driver
264
+ desc = nil
265
+ File.open('./test/data/eventid_6416', 'r') do |f|
266
+ desc = f.read.gsub(/\R/, "\r\n")
267
+ end
268
+ h = {"Description" => desc}
269
+ expected = {"DescriptionTitle" => "A new external device was recognized by the system.",
270
+ "class_id" => "{1ed2bbf9-11f0-4084-b21f-ad83a8e6dcdc}",
271
+ "class_name" => "PrintQueue",
272
+ "compatible_ids" => ["GenPrintQueue", "SWD\\GenericRaw", "SWD\\Generic"],
273
+ "device_id" => "SWD\\PRINTENUM\\{60FA1C6A-1AB2-440A-AEE1-62ABFB9A4650}",
274
+ "device_name" => "Microsoft Print to PDF",
275
+ "subject.account_domain" => "ITSS",
276
+ "subject.account_name" => "IIZHU2016$",
277
+ "subject.logon_id" => "0x3E7",
278
+ "subject.security_id" => "SYSTEM",
279
+ "vendor_ids" => ["PRINTENUM\\{084f01fa-e634-4d77-83ee-074817c03581}",
280
+ "PRINTENUM\\LocalPrintQueue",
281
+ "{084f01fa-e634-4d77-83ee-074817c03581}"]}
282
+ d.instance.parse_desc(h)
283
+ assert_equal(expected, h)
284
+ end
285
+
286
+ def test_write
287
+ d = create_driver
288
+
289
+ service = Fluent::Plugin::EventService.new
290
+
291
+ d.run(expect_emits: 1) do
292
+ service.run
293
+ end
294
+
295
+ assert(d.events.length >= 1)
296
+ event = d.events.select {|e| e.last["EventID"] == "65500" }.last
297
+ record = event.last
298
+
299
+ assert_equal("Application", record["Channel"])
300
+ assert_equal("65500", record["EventID"])
301
+ assert_equal("4", record["Level"])
302
+ assert_equal("fluent-plugins", record["ProviderName"])
303
+ end
304
+
305
+ CONFIG_WITH_NON_EXISTENT_CHANNEL = config_element("ROOT", "", {
306
+ "channels" => ["application", "NonExistentChannel"]
307
+ })
308
+ def test_skip_non_existent_channel
309
+ d = create_driver(CONFIG + CONFIG_WITH_NON_EXISTENT_CHANNEL)
310
+
311
+ service = Fluent::Plugin::EventService.new
312
+
313
+ assert_nothing_raised do
314
+ d.run(expect_emits: 1) do
315
+ service.run
316
+ end
317
+ end
318
+
319
+ assert(d.events.length >= 1)
320
+ assert(d.logs.any?{|log| log.include?("[warn]: Channel Not Found: nonexistentchannel") },
321
+ d.logs.join("\n"))
322
+ end
323
+
324
+ CONFIG_WITH_QUERY = config_element("ROOT", "", {"tag" => "fluent.eventlog",
325
+ "event_query" => "Event/System[EventID=65500]"}, [
326
+ config_element("storage", "", {
327
+ '@type' => 'local',
328
+ 'persistent' => false
329
+ })
330
+ ])
331
+ def test_write_with_event_query
332
+ d = create_driver(CONFIG_WITH_QUERY)
333
+
334
+ service = Fluent::Plugin::EventService.new
335
+
336
+ d.run(expect_emits: 1) do
337
+ service.run
338
+ end
339
+
340
+ assert(d.events.length >= 1)
341
+ event = d.events.last
342
+ record = event.last
343
+
344
+ assert_equal("Application", record["Channel"])
345
+ assert_equal("65500", record["EventID"])
346
+ assert_equal("4", record["Level"])
347
+ assert_equal("fluent-plugins", record["ProviderName"])
348
+ end
349
+
350
+
351
+ CONFIG_KEYS = config_element("ROOT", "", {
352
+ "tag" => "fluent.eventlog",
353
+ "keys" => ["EventID", "Level", "Channel", "ProviderName"]
354
+ }, [
355
+ config_element("storage", "", {
356
+ '@type' => 'local',
357
+ 'persistent' => false
358
+ })
359
+ ])
360
+ def test_write_with_keys
361
+ d = create_driver(CONFIG_KEYS)
362
+
363
+ service = Fluent::Plugin::EventService.new
364
+
365
+ d.run(expect_emits: 1) do
366
+ service.run
367
+ end
368
+
369
+ assert(d.events.length >= 1)
370
+ event = d.events.select {|e| e.last["EventID"] == "65500" }.last
371
+ record = event.last
372
+
373
+ expected = {"EventID" => "65500",
374
+ "Level" => "4",
375
+ "Channel" => "Application",
376
+ "ProviderName" => "fluent-plugins"}
377
+
378
+ assert_equal(expected, record)
379
+ end
380
+
381
+ REMOTING_ACCESS_CONFIG = config_element("ROOT", "", {"tag" => "fluent.eventlog"}, [
382
+ config_element("storage", "", {
383
+ '@type' => 'local',
384
+ 'persistent' => false
385
+ }),
386
+ config_element("subscribe", "", {
387
+ 'channels' => ['Application'],
388
+ 'remote_server' => '127.0.0.1',
389
+ }),
390
+ ])
391
+
392
+ def test_write_with_remoting_access
393
+ d = create_driver(REMOTING_ACCESS_CONFIG)
394
+
395
+ service = Fluent::Plugin::EventService.new
396
+
397
+ d.run(expect_emits: 1) do
398
+ service.run
399
+ end
400
+
401
+ assert(d.events.length >= 1)
402
+ event = d.events.select {|e| e.last["EventID"] == "65500" }.last
403
+ record = event.last
404
+
405
+ assert_equal("Application", record["Channel"])
406
+ assert_equal("65500", record["EventID"])
407
+ assert_equal("4", record["Level"])
408
+ assert_equal("fluent-plugins", record["ProviderName"])
409
+ end
410
+
411
+ class HashRendered < self
412
+ def test_write
413
+ d = create_driver(config_element("ROOT", "", {"tag" => "fluent.eventlog",
414
+ "render_as_xml" => false}, [
415
+ config_element("storage", "", {
416
+ '@type' => 'local',
417
+ 'persistent' => false
418
+ })
419
+ ]))
420
+
421
+ service = Fluent::Plugin::EventService.new
422
+
423
+ d.run(expect_emits: 1) do
424
+ service.run
425
+ end
426
+
427
+ assert(d.events.length >= 1)
428
+ event = d.events.select {|e| e.last["EventID"] == "65500" }.last
429
+ record = event.last
430
+
431
+ assert_false(d.instance.render_as_xml)
432
+ assert_equal("Application", record["Channel"])
433
+ assert_equal("65500", record["EventID"])
434
+ assert_equal("4", record["Level"])
435
+ assert_equal("fluent-plugins", record["ProviderName"])
436
+ end
437
+
438
+ def test_write_with_preserving_qualifiers
439
+ require 'winevt'
440
+
441
+ d = create_driver(config_element("ROOT", "", {"tag" => "fluent.eventlog",
442
+ "render_as_xml" => false,
443
+ 'preserve_qualifiers_on_hash' => true
444
+ }, [
445
+ config_element("storage", "", {
446
+ '@type' => 'local',
447
+ 'persistent' => false
448
+ }),
449
+ ]))
450
+
451
+ service = Fluent::Plugin::EventService.new
452
+ subscribe = Winevt::EventLog::Subscribe.new
453
+
454
+ omit "@parser.preserve_qualifiers does not respond" unless subscribe.respond_to?(:preserve_qualifiers?)
455
+
456
+ d.run(expect_emits: 1) do
457
+ service.run
458
+ end
459
+
460
+ assert(d.events.length >= 1)
461
+ event = d.events.last
462
+ record = event.last
463
+
464
+ assert_true(record.has_key?("Description"))
465
+ assert_true(record.has_key?("EventData"))
466
+ assert_true(record.has_key?("Qualifiers"))
467
+ end
468
+ end
469
+
470
+ class PersistBookMark < self
471
+ TEST_PLUGIN_STORAGE_PATH = File.join( File.dirname(File.dirname(__FILE__)), 'tmp', 'in_windows_eventlog2', 'store' )
472
+ CONFIG2 = config_element("ROOT", "", {"tag" => "fluent.eventlog"}, [
473
+ config_element("storage", "", {
474
+ '@type' => 'local',
475
+ '@id' => 'test-02',
476
+ '@log_level' => "info",
477
+ 'path' => File.join(TEST_PLUGIN_STORAGE_PATH,
478
+ 'json', 'test-02.json'),
479
+ 'persistent' => true,
480
+ })
481
+ ])
482
+
483
+ def setup
484
+ FileUtils.rm_rf(TEST_PLUGIN_STORAGE_PATH)
485
+ FileUtils.mkdir_p(File.join(TEST_PLUGIN_STORAGE_PATH, 'json'))
486
+ FileUtils.chmod_R(0755, File.join(TEST_PLUGIN_STORAGE_PATH, 'json'))
487
+ end
488
+
489
+ def test_write
490
+ d = create_driver(CONFIG2)
491
+
492
+ assert !File.exist?(File.join(TEST_PLUGIN_STORAGE_PATH, 'json', 'test-02.json'))
493
+
494
+ service = Fluent::Plugin::EventService.new
495
+
496
+ d.run(expect_emits: 1) do
497
+ service.run
498
+ end
499
+
500
+ assert(d.events.length >= 1)
501
+ event = d.events.select {|e| e.last["EventID"] == "65500" }.last
502
+ record = event.last
503
+
504
+ prev_id = record["EventRecordID"].to_i
505
+ assert_equal("Application", record["Channel"])
506
+ assert_equal("65500", record["EventID"])
507
+ assert_equal("4", record["Level"])
508
+ assert_equal("fluent-plugins", record["ProviderName"])
509
+
510
+ assert File.exist?(File.join(TEST_PLUGIN_STORAGE_PATH, 'json', 'test-02.json'))
511
+
512
+ d2 = create_driver(CONFIG2)
513
+ d2.run(expect_emits: 1) do
514
+ service.run
515
+ end
516
+
517
+ events = d2.events.select {|e| e.last["EventID"] == "65500" }
518
+ assert(events.length == 1) # should be tailing after previous context.
519
+ event2 = events.last
520
+ record2 = event2.last
521
+
522
+ curr_id = record2["EventRecordID"].to_i
523
+ assert(curr_id > prev_id)
524
+
525
+ assert File.exist?(File.join(TEST_PLUGIN_STORAGE_PATH, 'json', 'test-02.json'))
526
+ end
527
+
528
+ def test_start_with_invalid_bookmark
529
+ invalid_storage_contents = <<-EOS
530
+ <BookmarkList>\r\n <Bookmark Channel='Application' RecordId='20063' IsCurrent='true'/>\r\n
531
+ EOS
532
+ d = create_driver(CONFIG2)
533
+ storage = d.instance.instance_variable_get(:@bookmarks_storage)
534
+ storage.put('application', invalid_storage_contents)
535
+ assert File.exist?(File.join(TEST_PLUGIN_STORAGE_PATH, 'json', 'test-02.json'))
536
+
537
+ d2 = create_driver(CONFIG2)
538
+ assert_raise(Fluent::ConfigError) do
539
+ d2.instance.start
540
+ end
541
+ assert_equal 0, d2.logs.grep(/This stored bookmark is incomplete for using. Referring `read_existing_events` parameter to subscribe:/).length
542
+ end
543
+
544
+ def test_start_with_empty_bookmark
545
+ invalid_storage_contents = <<-EOS
546
+ <BookmarkList>\r\n</BookmarkList>
547
+ EOS
548
+ d = create_driver(CONFIG2)
549
+ storage = d.instance.instance_variable_get(:@bookmarks_storage)
550
+ storage.put('application', invalid_storage_contents)
551
+ assert File.exist?(File.join(TEST_PLUGIN_STORAGE_PATH, 'json', 'test-02.json'))
552
+
553
+ d2 = create_driver(CONFIG2)
554
+ d2.instance.start
555
+ assert_equal 1, d2.logs.grep(/This stored bookmark is incomplete for using. Referring `read_existing_events` parameter to subscribe:/).length
556
+ end
557
+ end
558
+
559
+ def test_write_with_none_parser
560
+ d = create_driver(config_element("ROOT", "", {"tag" => "fluent.eventlog",
561
+ "render_as_xml" => true}, [
562
+ config_element("storage", "", {
563
+ '@type' => 'local',
564
+ 'persistent' => false
565
+ }),
566
+ config_element("parse", "", {
567
+ '@type' => 'none',
568
+ }),
569
+ ]))
570
+
571
+ service = Fluent::Plugin::EventService.new
572
+
573
+ d.run(expect_emits: 1) do
574
+ service.run
575
+ end
576
+
577
+ assert(d.events.length >= 1)
578
+ event = d.events.last
579
+ record = event.last
580
+
581
+ assert do
582
+ # record should be {message: <RAW XML EventLog>}.
583
+ record["message"]
584
+ end
585
+
586
+ assert_true(record.has_key?("Description"))
587
+ assert_true(record.has_key?("EventData"))
588
+ end
589
+
590
+ def test_write_with_winevt_xml_parser_without_qualifiers
591
+ d = create_driver(config_element("ROOT", "", {"tag" => "fluent.eventlog",
592
+ "render_as_xml" => true}, [
593
+ config_element("storage", "", {
594
+ '@type' => 'local',
595
+ 'persistent' => false
596
+ }),
597
+ config_element("parse", "", {
598
+ '@type' => 'winevt_xml',
599
+ 'preserve_qualifiers' => false
600
+ }),
601
+ ]))
602
+
603
+ service = Fluent::Plugin::EventService.new
604
+
605
+ omit "@parser.preserve_qualifiers does not respond" unless d.instance.instance_variable_get(:@parser).respond_to?(:preserve_qualifiers?)
606
+
607
+ d.run(expect_emits: 1) do
608
+ service.run
609
+ end
610
+
611
+ assert(d.events.length >= 1)
612
+ event = d.events.last
613
+ record = event.last
614
+
615
+ assert_true(record.has_key?("Description"))
616
+ assert_true(record.has_key?("EventData"))
617
+ assert_false(record.has_key?("Qualifiers"))
618
+ end
619
+ end