fluentd 1.16.11-x86-mingw32 → 1.17.0-x86-mingw32

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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.github/DISCUSSION_TEMPLATE/q-a-japanese.yml +50 -0
  3. data/.github/DISCUSSION_TEMPLATE/q-a.yml +47 -0
  4. data/.github/workflows/test-ruby-head.yml +31 -0
  5. data/.github/workflows/test.yml +6 -38
  6. data/CHANGELOG.md +33 -122
  7. data/README.md +4 -1
  8. data/Rakefile +1 -1
  9. data/SECURITY.md +4 -6
  10. data/fluentd.gemspec +5 -11
  11. data/lib/fluent/command/binlog_reader.rb +1 -1
  12. data/lib/fluent/command/fluentd.rb +1 -1
  13. data/lib/fluent/compat/formatter.rb +0 -6
  14. data/lib/fluent/config/configure_proxy.rb +2 -2
  15. data/lib/fluent/config/parser.rb +3 -15
  16. data/lib/fluent/config/types.rb +1 -1
  17. data/lib/fluent/config/v1_parser.rb +1 -1
  18. data/lib/fluent/config/yaml_parser/parser.rb +0 -4
  19. data/lib/fluent/configurable.rb +2 -2
  20. data/lib/fluent/counter/mutex_hash.rb +1 -1
  21. data/lib/fluent/fluent_log_event_router.rb +0 -2
  22. data/lib/fluent/plugin/buf_file.rb +1 -1
  23. data/lib/fluent/plugin/buffer/file_chunk.rb +1 -1
  24. data/lib/fluent/plugin/buffer/file_single_chunk.rb +2 -3
  25. data/lib/fluent/plugin/filter_parser.rb +26 -8
  26. data/lib/fluent/plugin/formatter_csv.rb +4 -18
  27. data/lib/fluent/plugin/in_http.rb +17 -52
  28. data/lib/fluent/plugin/in_tail.rb +35 -3
  29. data/lib/fluent/plugin/out_file.rb +0 -8
  30. data/lib/fluent/plugin/out_forward.rb +0 -10
  31. data/lib/fluent/plugin/out_http.rb +125 -13
  32. data/lib/fluent/plugin/owned_by_mixin.rb +0 -1
  33. data/lib/fluent/plugin/parser_json.rb +34 -9
  34. data/lib/fluent/plugin/parser_msgpack.rb +24 -3
  35. data/lib/fluent/plugin_helper/metrics.rb +2 -2
  36. data/lib/fluent/plugin_helper/server.rb +1 -4
  37. data/lib/fluent/registry.rb +6 -6
  38. data/lib/fluent/supervisor.rb +3 -3
  39. data/lib/fluent/test/output_test.rb +1 -1
  40. data/lib/fluent/test.rb +2 -5
  41. data/lib/fluent/unique_id.rb +1 -1
  42. data/lib/fluent/version.rb +1 -1
  43. data/lib/fluent/winsvc.rb +8 -38
  44. data/test/command/test_cat.rb +2 -2
  45. data/test/command/test_fluentd.rb +11 -58
  46. data/test/config/test_dsl.rb +1 -8
  47. data/test/config/test_element.rb +2 -2
  48. data/test/config/test_plugin_configuration.rb +6 -6
  49. data/test/helper.rb +7 -27
  50. data/test/log/test_console_adapter.rb +10 -3
  51. data/test/plugin/data/log_numeric/01.log +0 -0
  52. data/test/plugin/data/log_numeric/02.log +0 -0
  53. data/test/plugin/data/log_numeric/12.log +0 -0
  54. data/test/plugin/data/log_numeric/14.log +0 -0
  55. data/test/plugin/in_tail/test_io_handler.rb +14 -13
  56. data/test/plugin/in_tail/test_position_file.rb +7 -6
  57. data/test/plugin/out_forward/test_ack_handler.rb +3 -3
  58. data/test/plugin/out_forward/test_socket_cache.rb +3 -3
  59. data/test/plugin/test_buffer.rb +2 -2
  60. data/test/plugin/test_filter_grep.rb +1 -1
  61. data/test/plugin/test_filter_stdout.rb +4 -4
  62. data/test/plugin/test_formatter_hash.rb +2 -2
  63. data/test/plugin/test_in_forward.rb +1 -2
  64. data/test/plugin/test_in_http.rb +23 -1
  65. data/test/plugin/test_in_monitor_agent.rb +6 -6
  66. data/test/plugin/test_in_object_space.rb +0 -4
  67. data/test/plugin/test_in_syslog.rb +18 -25
  68. data/test/plugin/test_in_tail.rb +153 -5
  69. data/test/plugin/test_in_tcp.rb +1 -1
  70. data/test/plugin/test_in_udp.rb +10 -16
  71. data/test/plugin/test_out_exec_filter.rb +7 -12
  72. data/test/plugin/test_out_file.rb +2 -22
  73. data/test/plugin/test_out_forward.rb +37 -61
  74. data/test/plugin/test_out_http.rb +128 -0
  75. data/test/plugin/test_out_stdout.rb +3 -3
  76. data/test/plugin/test_out_stream.rb +1 -1
  77. data/test/plugin/test_output.rb +1 -1
  78. data/test/plugin/test_output_as_buffered.rb +2 -2
  79. data/test/plugin/test_output_as_buffered_retries.rb +2 -2
  80. data/test/plugin/test_owned_by.rb +0 -1
  81. data/test/plugin/test_parser_csv.rb +1 -1
  82. data/test/plugin/test_parser_json.rb +106 -31
  83. data/test/plugin/test_parser_msgpack.rb +127 -0
  84. data/test/plugin/test_storage.rb +0 -1
  85. data/test/plugin_helper/test_http_server_helper.rb +1 -1
  86. data/test/plugin_helper/test_server.rb +41 -83
  87. data/test/plugin_helper/test_socket.rb +1 -1
  88. data/test/test_config.rb +0 -6
  89. data/test/test_configdsl.rb +1 -8
  90. data/test/test_event_router.rb +2 -2
  91. data/test/test_plugin_helper.rb +1 -1
  92. data/test/test_supervisor.rb +21 -32
  93. data/test/test_tls.rb +1 -1
  94. metadata +66 -52
  95. data/test/scripts/windows_service_test.ps1 +0 -73
@@ -130,7 +130,7 @@ class FileOutputTest < Test::Unit::TestCase
130
130
  'path' => "#{TMP_DIR}/${tag}/${type}/conf_test.%Y%m%d.%H%M.log",
131
131
  'add_path_suffix' => 'false',
132
132
  'append' => "true",
133
- 'symlink_path' => "#{TMP_DIR}/${tag}/conf_test.current.log",
133
+ 'symlink_path' => "#{TMP_DIR}/conf_test.current.log",
134
134
  'compress' => 'gzip',
135
135
  'recompress' => 'true',
136
136
  }, [
@@ -183,26 +183,6 @@ class FileOutputTest < Test::Unit::TestCase
183
183
  Fluent::Test::Driver::Output.new(Fluent::Plugin::NullOutput).configure(conf)
184
184
  end
185
185
  end
186
-
187
- test 'warning for symlink_path not including correct placeholders corresponding to chunk keys' do
188
- omit "Windows doesn't support symlink" if Fluent.windows?
189
- conf = config_element('match', '**', {
190
- 'path' => "#{TMP_DIR}/${tag}/${key1}/${key2}/conf_test.%Y%m%d.%H%M.log",
191
- 'symlink_path' => "#{TMP_DIR}/conf_test.current.log",
192
- }, [
193
- config_element('buffer', 'time,tag,key1,key2', {
194
- '@type' => 'file',
195
- 'timekey' => '1d',
196
- 'path' => "#{TMP_DIR}/buf_conf_test",
197
- }),
198
- ])
199
- assert_nothing_raised do
200
- d = create_driver(conf)
201
- assert do
202
- d.logs.count { |log| log.include?("multiple chunks are competing for a single symlink_path") } == 2
203
- end
204
- end
205
- end
206
186
  end
207
187
 
208
188
  sub_test_case 'fully configured output' do
@@ -334,7 +314,7 @@ class FileOutputTest < Test::Unit::TestCase
334
314
  assert_equal r5, d.formatted[4]
335
315
 
336
316
  read_gunzip = ->(path){
337
- File.open(path, 'rb'){ |fio|
317
+ File.open(path){ |fio|
338
318
  Zlib::GzipReader.new(StringIO.new(fio.read)).read
339
319
  }
340
320
  }
@@ -12,8 +12,7 @@ class ForwardOutputTest < Test::Unit::TestCase
12
12
  FileUtils.rm_rf(TMP_DIR)
13
13
  FileUtils.mkdir_p(TMP_DIR)
14
14
  @d = nil
15
- # forward plugin uses TCP and UDP sockets on the same port number
16
- @target_port = unused_port(protocol: :all)
15
+ @target_port = unused_port
17
16
  end
18
17
 
19
18
  def teardown
@@ -178,42 +177,6 @@ EOL
178
177
  assert{ logs.any?{|log| log.include?(expected_log) && log.include?(expected_detail) } }
179
178
  end
180
179
 
181
- sub_test_case 'configure compress' do
182
- data('default', ['', :text])
183
- data('gzip', ['compress gzip', :gzip])
184
- test 'should be applied' do |(option, expected)|
185
- @d = d = create_driver(config + option)
186
- node = d.instance.nodes.first
187
-
188
- assert_equal(
189
- [expected, expected],
190
- [d.instance.compress, node.instance_variable_get(:@compress)]
191
- )
192
- end
193
-
194
- # TODO add tests that we cannot configure the different compress type between owner and buffer except for :text
195
- data('gzip', ['compress gzip', :text, :gzip])
196
- test 'can configure buffer compress separately when owner uses :text' do |(buffer_option, expected_owner_compress, expected_buffer_compress)|
197
- @d = d = create_driver(config + %[
198
- <buffer>
199
- type memory
200
- #{buffer_option}
201
- </buffer>
202
- ])
203
- node = d.instance.nodes.first
204
-
205
- assert_equal(
206
- [expected_owner_compress, expected_owner_compress, expected_buffer_compress],
207
- [d.instance.compress, node.instance_variable_get(:@compress), d.instance.buffer.compress],
208
- )
209
-
210
- log_message = "buffer is compressed. If you also want to save the bandwidth of a network, Add `compress` configuration in <match>"
211
- assert do
212
- d.logs.any? { |log| log.include?(log_message) }
213
- end
214
- end
215
- end
216
-
217
180
  data('CA cert' => 'tls_ca_cert_path',
218
181
  'non CA cert' => 'tls_cert_path')
219
182
  test 'configure tls_cert_path/tls_ca_cert_path' do |param|
@@ -362,6 +325,40 @@ EOL
362
325
  assert_equal 1234, d.instance.discovery_manager.services[0].port
363
326
  end
364
327
 
328
+ test 'compress_default_value' do
329
+ @d = d = create_driver
330
+ assert_equal :text, d.instance.compress
331
+
332
+ node = d.instance.nodes.first
333
+ assert_equal :text, node.instance_variable_get(:@compress)
334
+ end
335
+
336
+ test 'set_compress_is_gzip' do
337
+ @d = d = create_driver(config + %[compress gzip])
338
+ assert_equal :gzip, d.instance.compress
339
+ assert_equal :gzip, d.instance.buffer.compress
340
+
341
+ node = d.instance.nodes.first
342
+ assert_equal :gzip, node.instance_variable_get(:@compress)
343
+ end
344
+
345
+ test 'set_compress_is_gzip_in_buffer_section' do
346
+ mock = flexmock($log)
347
+ mock.should_receive(:log).with("buffer is compressed. If you also want to save the bandwidth of a network, Add `compress` configuration in <match>")
348
+
349
+ @d = d = create_driver(config + %[
350
+ <buffer>
351
+ type memory
352
+ compress gzip
353
+ </buffer>
354
+ ])
355
+ assert_equal :text, d.instance.compress
356
+ assert_equal :gzip, d.instance.buffer.compress
357
+
358
+ node = d.instance.nodes.first
359
+ assert_equal :text, node.instance_variable_get(:@compress)
360
+ end
361
+
365
362
  test 'phi_failure_detector disabled' do
366
363
  @d = d = create_driver(config + %[phi_failure_detector false \n phi_threshold 0])
367
364
  node = d.instance.nodes.first
@@ -613,6 +610,7 @@ EOL
613
610
 
614
611
  @d = d = create_driver(config + %[
615
612
  require_ack_response true
613
+ ack_response_timeout 1s
616
614
  <buffer tag>
617
615
  flush_mode immediate
618
616
  retry_type periodic
@@ -660,6 +658,7 @@ EOL
660
658
 
661
659
  @d = d = create_driver(config + %[
662
660
  require_ack_response true
661
+ ack_response_timeout 10s
663
662
  <buffer tag>
664
663
  flush_mode immediate
665
664
  retry_type periodic
@@ -1347,27 +1346,4 @@ EOL
1347
1346
  end
1348
1347
  end
1349
1348
  end
1350
-
1351
- test 'establish_connection_timeout' do
1352
- @d = d = create_driver(%[
1353
- hard_timeout 1
1354
- <server>
1355
- host #{TARGET_HOST}
1356
- port #{@target_port}
1357
- </server>
1358
- ])
1359
-
1360
- node = d.instance.nodes.first
1361
- mock_sock = flexmock('socket')
1362
- mock_sock.should_receive(:read_nonblock).with(512).and_return('').at_least.once
1363
-
1364
- ri = Fluent::Plugin::ForwardOutput::ConnectionManager::RequestInfo.new(:helo)
1365
-
1366
- assert_true node.available?
1367
- node.establish_connection(mock_sock, ri)
1368
- assert_false node.available?
1369
-
1370
- logs = d.logs
1371
- assert{ logs.any?{|log| log.include?('handshake timeout after 1.0s') } }
1372
- end
1373
1349
  end
@@ -7,6 +7,7 @@ require 'webrick/https'
7
7
  require 'net/http'
8
8
  require 'uri'
9
9
  require 'json'
10
+ require 'aws-sdk-core'
10
11
 
11
12
  # WEBrick's ProcHandler doesn't handle PUT by default
12
13
  module WEBrick::HTTPServlet
@@ -390,6 +391,97 @@ class HTTPOutputTest < Test::Unit::TestCase
390
391
  end
391
392
  end
392
393
 
394
+
395
+ sub_test_case 'aws sigv4 auth' do
396
+ setup do
397
+ @@fake_aws_credentials = Aws::Credentials.new(
398
+ 'fakeaccess',
399
+ 'fakesecret',
400
+ 'fake session token'
401
+ )
402
+ end
403
+
404
+ def server_port
405
+ 19883
406
+ end
407
+
408
+ def test_aws_sigv4_sts_role_arn
409
+ stub(Aws::AssumeRoleCredentials).new do |credentials_provider|
410
+ stub(credentials_provider).credentials {
411
+ @@fake_aws_credentials
412
+ }
413
+ credentials_provider
414
+ end
415
+
416
+ d = create_driver(config + %[
417
+ <auth>
418
+ method aws_sigv4
419
+ aws_service someservice
420
+ aws_region my-region-1
421
+ aws_role_arn arn:aws:iam::123456789012:role/MyRole
422
+ </auth>
423
+ ])
424
+ d.run(default_tag: 'test.http') do
425
+ test_events.each { |event|
426
+ d.feed(event)
427
+ }
428
+ end
429
+
430
+ result = @@result
431
+ assert_equal 'POST', result.method
432
+ assert_equal 'application/x-ndjson', result.content_type
433
+ assert_equal test_events, result.data
434
+ assert_not_empty result.headers
435
+ assert_not_nil result.headers['authorization']
436
+ assert_match /AWS4-HMAC-SHA256 Credential=[a-zA-Z0-9]*\/\d+\/my-region-1\/someservice\/aws4_request/, result.headers['authorization']
437
+ assert_match /SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-amz-security-token/, result.headers['authorization']
438
+ assert_equal @@fake_aws_credentials.session_token, result.headers['x-amz-security-token']
439
+ assert_not_nil result.headers['x-amz-content-sha256']
440
+ assert_not_empty result.headers['x-amz-content-sha256']
441
+ assert_not_nil result.headers['x-amz-security-token']
442
+ assert_not_empty result.headers['x-amz-security-token']
443
+ assert_not_nil result.headers['x-amz-date']
444
+ assert_not_empty result.headers['x-amz-date']
445
+ end
446
+
447
+ def test_aws_sigv4_no_role
448
+ stub(Aws::CredentialProviderChain).new do |provider_chain|
449
+ stub(provider_chain).resolve {
450
+ @@fake_aws_credentials
451
+ }
452
+ provider_chain
453
+ end
454
+ d = create_driver(config + %[
455
+ <auth>
456
+ method aws_sigv4
457
+ aws_service someservice
458
+ aws_region my-region-1
459
+ </auth>
460
+ ])
461
+ d.run(default_tag: 'test.http') do
462
+ test_events.each { |event|
463
+ d.feed(event)
464
+ }
465
+ end
466
+
467
+ result = @@result
468
+ assert_equal 'POST', result.method
469
+ assert_equal 'application/x-ndjson', result.content_type
470
+ assert_equal test_events, result.data
471
+ assert_not_empty result.headers
472
+ assert_not_nil result.headers['authorization']
473
+ assert_match /AWS4-HMAC-SHA256 Credential=[a-zA-Z0-9]*\/\d+\/my-region-1\/someservice\/aws4_request/, result.headers['authorization']
474
+ assert_match /SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-amz-security-token/, result.headers['authorization']
475
+ assert_equal @@fake_aws_credentials.session_token, result.headers['x-amz-security-token']
476
+ assert_not_nil result.headers['x-amz-content-sha256']
477
+ assert_not_empty result.headers['x-amz-content-sha256']
478
+ assert_not_nil result.headers['x-amz-security-token']
479
+ assert_not_empty result.headers['x-amz-security-token']
480
+ assert_not_nil result.headers['x-amz-date']
481
+ assert_not_empty result.headers['x-amz-date']
482
+ end
483
+ end
484
+
393
485
  sub_test_case 'HTTPS' do
394
486
  def server_port
395
487
  19882
@@ -426,4 +518,40 @@ class HTTPOutputTest < Test::Unit::TestCase
426
518
  assert_not_empty result.headers
427
519
  end
428
520
  end
521
+
522
+ sub_test_case 'connection_reuse' do
523
+ def server_port
524
+ 19883
525
+ end
526
+
527
+ def test_connection_recreation
528
+ d = create_driver(%[
529
+ endpoint http://127.0.0.1:#{server_port}/test
530
+ reuse_connections true
531
+ ])
532
+
533
+ d.run(default_tag: 'test.http', shutdown: false) do
534
+ d.feed(test_events[0])
535
+ end
536
+
537
+ data = @@result.data
538
+
539
+ # Restart server to simulate connection loss
540
+ @@http_server_thread.kill
541
+ @@http_server_thread.join
542
+ @@http_server_thread = Thread.new do
543
+ run_http_server
544
+ end
545
+
546
+ d.run(default_tag: 'test.http') do
547
+ d.feed(test_events[1])
548
+ end
549
+
550
+ result = @@result
551
+ assert_equal 'POST', result.method
552
+ assert_equal 'application/x-ndjson', result.content_type
553
+ assert_equal test_events, data.concat(result.data)
554
+ assert_not_empty result.headers
555
+ end
556
+ end
429
557
  end
@@ -95,11 +95,11 @@ class StdoutOutputTest < Test::Unit::TestCase
95
95
  d.feed(time, {'test' => 'test2'})
96
96
  end
97
97
  end
98
- assert_equal "#{Time.at(time).localtime.strftime(TIME_FORMAT)} test: {\"test\"=>\"test2\"}\n", out.gsub(' => ', '=>')
98
+ assert_equal "#{Time.at(time).localtime.strftime(TIME_FORMAT)} test: {\"test\"=>\"test2\"}\n", out
99
99
 
100
100
  # NOTE: Float::NAN is not jsonable, but hash string can output it.
101
101
  out = capture_log { d.feed('test', time, {'test' => Float::NAN}) }
102
- assert_equal "#{Time.at(time).localtime.strftime(TIME_FORMAT)} test: {\"test\"=>NaN}\n", out.gsub(' => ', '=>')
102
+ assert_equal "#{Time.at(time).localtime.strftime(TIME_FORMAT)} test: {\"test\"=>NaN}\n", out
103
103
  end
104
104
  end
105
105
 
@@ -171,7 +171,7 @@ class StdoutOutputTest < Test::Unit::TestCase
171
171
  end
172
172
  end
173
173
 
174
- assert_equal "#{Time.at(time).localtime.strftime(TIME_FORMAT)} test: {\"test\"=>\"test\"}\n", out.gsub(' => ', '=>')
174
+ assert_equal "#{Time.at(time).localtime.strftime(TIME_FORMAT)} test: {\"test\"=>\"test\"}\n", out
175
175
  end
176
176
  end
177
177
  end
@@ -54,7 +54,7 @@ class TcpOutputTest < Test::Unit::TestCase
54
54
 
55
55
  def setup
56
56
  super
57
- @port = unused_port(protocol: :tcp)
57
+ @port = unused_port
58
58
  end
59
59
 
60
60
  def teardown
@@ -1111,7 +1111,7 @@ class OutputTest < Test::Unit::TestCase
1111
1111
  config: config_element(
1112
1112
  "ROOT", "", {},
1113
1113
  [
1114
- config_element("buffer", "", {}),
1114
+ config_element("buffer", "", {}),
1115
1115
  config_element("secondary", "", {"@type" => "test", "name" => "test"}),
1116
1116
  ]
1117
1117
  ),
@@ -1343,7 +1343,6 @@ class BufferedOutputTest < Test::Unit::TestCase
1343
1343
  hash = {
1344
1344
  'flush_interval' => 10,
1345
1345
  'flush_thread_count' => 1,
1346
- 'flush_thread_interval' => 0.1,
1347
1346
  'flush_thread_burst_interval' => 0.1,
1348
1347
  'chunk_limit_size' => 1024,
1349
1348
  }
@@ -1446,7 +1445,7 @@ class BufferedOutputTest < Test::Unit::TestCase
1446
1445
 
1447
1446
  assert{ @i.buffer.stage.size == 3 }
1448
1447
 
1449
- # to trigger try_flush with flush_thread_interval
1448
+ # to trigger try_flush with flush_thread_burst_interval
1450
1449
  Timecop.freeze( Time.parse('2016-04-13 14:04:11 +0900') )
1451
1450
  @i.enqueue_thread_wait
1452
1451
  Timecop.freeze( Time.parse('2016-04-13 14:04:12 +0900') )
@@ -1455,6 +1454,7 @@ class BufferedOutputTest < Test::Unit::TestCase
1455
1454
  @i.enqueue_thread_wait
1456
1455
  Timecop.freeze( Time.parse('2016-04-13 14:04:14 +0900') )
1457
1456
  @i.enqueue_thread_wait
1457
+ @i.flush_thread_wakeup
1458
1458
 
1459
1459
  assert{ @i.buffer.stage.size == 0 }
1460
1460
 
@@ -941,7 +941,7 @@ class BufferedOutputRetryTest < Test::Unit::TestCase
941
941
  @i.enqueue_thread_wait
942
942
 
943
943
  @i.flush_thread_wakeup
944
- waiting(4){ Thread.pass until @i.write_count > 0 && @i.num_errors > 0 }
944
+ waiting(4){ Thread.pass until @i.write_count > 0 }
945
945
  waiting(4) do
946
946
  state = @i.instance_variable_get(:@output_flush_threads).first
947
947
  state.thread.status == 'sleep'
@@ -953,7 +953,7 @@ class BufferedOutputRetryTest < Test::Unit::TestCase
953
953
  now = @i.next_flush_time
954
954
  Timecop.freeze( now )
955
955
  @i.flush_thread_wakeup
956
- waiting(4){ Thread.pass until @i.write_count > 1 && @i.num_errors > 1 }
956
+ waiting(4){ Thread.pass until @i.write_count > 1 }
957
957
  waiting(4) do
958
958
  state = @i.instance_variable_get(:@output_flush_threads).first
959
959
  state.thread.status == 'sleep'
@@ -26,7 +26,6 @@ class OwnedByMixinTest < Test::Unit::TestCase
26
26
 
27
27
  assert_equal parent.object_id, child.owner.object_id
28
28
 
29
- assert child.instance_eval{ @_plugin_id_configured }
30
29
  assert_equal 'my_parent_id', child.instance_eval{ @_plugin_id }
31
30
 
32
31
  assert_equal Fluent::Log::LEVEL_TRACE, child.log.level
@@ -164,7 +164,7 @@ class CSVParserTest < ::Test::Unit::TestCase
164
164
  text = 'a"b,"a"""c"'
165
165
  assert_raise(CSV::MalformedCSVError) {
166
166
  normal.instance.parse(text) { |t, r| }
167
- }
167
+ }
168
168
  assert_nothing_raised {
169
169
  # generate broken record
170
170
  fast.instance.parse(text) { |t, r| }
@@ -8,37 +8,6 @@ class JsonParserTest < ::Test::Unit::TestCase
8
8
  @parser = Fluent::Test::Driver::Parser.new(Fluent::Plugin::JSONParser)
9
9
  end
10
10
 
11
- sub_test_case "configure_json_parser" do
12
- data("oj", [:oj, [Oj.method(:load), Oj::ParseError]])
13
- data("json", [:json, [JSON.method(:load), JSON::ParserError]])
14
- data("yajl", [:yajl, [Yajl.method(:load), Yajl::ParseError]])
15
- def test_return_each_loader((input, expected_return))
16
- result = @parser.instance.configure_json_parser(input)
17
- assert_equal expected_return, result
18
- end
19
-
20
- def test_raise_exception_for_unknown_input
21
- assert_raise RuntimeError do
22
- @parser.instance.configure_json_parser(:unknown)
23
- end
24
- end
25
-
26
- def test_fall_back_oj_to_yajl_if_oj_not_available
27
- stub(Fluent::OjOptions).available? { false }
28
-
29
- result = @parser.instance.configure_json_parser(:oj)
30
-
31
- assert_equal [Yajl.method(:load), Yajl::ParseError], result
32
- logs = @parser.logs.collect do |log|
33
- log.gsub(/\A\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [-+]\d{4} /, "")
34
- end
35
- assert_equal(
36
- ["[info]: Oj is not installed, and failing back to Yajl for json parser\n"],
37
- logs
38
- )
39
- end
40
- end
41
-
42
11
  data('oj' => 'oj', 'yajl' => 'yajl')
43
12
  def test_parse(data)
44
13
  @parser.configure('json_parser' => data)
@@ -166,4 +135,110 @@ class JsonParserTest < ::Test::Unit::TestCase
166
135
  end
167
136
  end
168
137
  end
138
+
139
+ sub_test_case "various record pattern" do
140
+ data("Only string", { record: '"message"', expected: [nil] }, keep: true)
141
+ data("Only string without quotation", { record: "message", expected: [nil] }, keep: true)
142
+ data("Only number", { record: "0", expected: [nil] }, keep: true)
143
+ data(
144
+ "Array of Hash",
145
+ {
146
+ record: '[{"k1": 1}, {"k2": 2}]',
147
+ expected: [{"k1" => 1}, {"k2" => 2}]
148
+ },
149
+ keep: true,
150
+ )
151
+ data(
152
+ "Array of both Hash and invalid",
153
+ {
154
+ record: '[{"k1": 1}, "string", {"k2": 2}, 0]',
155
+ expected: [{"k1" => 1}, nil, {"k2" => 2}, nil]
156
+ },
157
+ keep: true,
158
+ )
159
+ data(
160
+ "Array of all invalid",
161
+ {
162
+ record: '["string", 0, [{"k": 0}]]',
163
+ expected: [nil, nil, nil]
164
+ },
165
+ keep: true,
166
+ )
167
+
168
+ def test_oj(data)
169
+ parsed_records = []
170
+ @parser.configure("json_parser" => "oj")
171
+ @parser.instance.parse(data[:record]) { |time, record|
172
+ parsed_records.append(record)
173
+ }
174
+ assert_equal(data[:expected], parsed_records)
175
+ end
176
+
177
+ def test_yajl(data)
178
+ parsed_records = []
179
+ @parser.configure("json_parser" => "yajl")
180
+ @parser.instance.parse(data[:record]) { |time, record|
181
+ parsed_records.append(record)
182
+ }
183
+ assert_equal(data[:expected], parsed_records)
184
+ end
185
+
186
+ def test_json(json)
187
+ parsed_records = []
188
+ @parser.configure("json_parser" => "json")
189
+ @parser.instance.parse(data[:record]) { |time, record|
190
+ parsed_records.append(record)
191
+ }
192
+ assert_equal(data[:expected], parsed_records)
193
+ end
194
+ end
195
+
196
+ # This becomes NoMethodError if a non-Hash object is passed to convert_values.
197
+ # https://github.com/fluent/fluentd/issues/4100
198
+ sub_test_case "execute_convert_values with null_empty_string" do
199
+ data("Only string", { record: '"message"', expected: [nil] }, keep: true)
200
+ data(
201
+ "Hash",
202
+ {
203
+ record: '{"k1": 1, "k2": ""}',
204
+ expected: [{"k1" => 1, "k2" => nil}]
205
+ },
206
+ keep: true,
207
+ )
208
+ data(
209
+ "Array of Hash",
210
+ {
211
+ record: '[{"k1": 1}, {"k2": ""}]',
212
+ expected: [{"k1" => 1}, {"k2" => nil}]
213
+ },
214
+ keep: true,
215
+ )
216
+
217
+ def test_oj(data)
218
+ parsed_records = []
219
+ @parser.configure("json_parser" => "oj", "null_empty_string" => true)
220
+ @parser.instance.parse(data[:record]) { |time, record|
221
+ parsed_records.append(record)
222
+ }
223
+ assert_equal(data[:expected], parsed_records)
224
+ end
225
+
226
+ def test_yajl(data)
227
+ parsed_records = []
228
+ @parser.configure("json_parser" => "yajl", "null_empty_string" => true)
229
+ @parser.instance.parse(data[:record]) { |time, record|
230
+ parsed_records.append(record)
231
+ }
232
+ assert_equal(data[:expected], parsed_records)
233
+ end
234
+
235
+ def test_json(json)
236
+ parsed_records = []
237
+ @parser.configure("json_parser" => "json", "null_empty_string" => true)
238
+ @parser.instance.parse(data[:record]) { |time, record|
239
+ parsed_records.append(record)
240
+ }
241
+ assert_equal(data[:expected], parsed_records)
242
+ end
243
+ end
169
244
  end