fluentd 0.14.12 → 0.14.13

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +2 -0
  3. data/ChangeLog +33 -0
  4. data/bin/fluent-plugin-config-format +5 -0
  5. data/bin/fluent-plugin-generate +5 -0
  6. data/lib/fluent/command/plugin_config_formatter.rb +258 -0
  7. data/lib/fluent/command/plugin_generator.rb +301 -0
  8. data/lib/fluent/compat/detach_process_mixin.rb +25 -0
  9. data/lib/fluent/compat/filter.rb +1 -1
  10. data/lib/fluent/compat/input.rb +1 -0
  11. data/lib/fluent/compat/output.rb +8 -5
  12. data/lib/fluent/config/configure_proxy.rb +31 -8
  13. data/lib/fluent/configurable.rb +2 -2
  14. data/lib/fluent/output.rb +3 -0
  15. data/lib/fluent/plugin/buffer/file_chunk.rb +52 -11
  16. data/lib/fluent/plugin/filter.rb +1 -1
  17. data/lib/fluent/plugin/filter_record_transformer.rb +5 -1
  18. data/lib/fluent/plugin/in_tail.rb +13 -6
  19. data/lib/fluent/plugin/input.rb +1 -1
  20. data/lib/fluent/plugin/output.rb +26 -6
  21. data/lib/fluent/plugin/parser_apache.rb +1 -1
  22. data/lib/fluent/plugin/parser_apache_error.rb +1 -1
  23. data/lib/fluent/plugin/parser_multiline.rb +1 -0
  24. data/lib/fluent/plugin/parser_nginx.rb +1 -1
  25. data/lib/fluent/plugin/parser_regexp.rb +18 -0
  26. data/lib/fluent/plugin_helper.rb +19 -1
  27. data/lib/fluent/plugin_helper/retry_state.rb +40 -16
  28. data/lib/fluent/process.rb +22 -0
  29. data/lib/fluent/supervisor.rb +2 -21
  30. data/lib/fluent/test/helpers.rb +14 -0
  31. data/lib/fluent/version.rb +1 -1
  32. data/templates/new_gem/Gemfile +3 -0
  33. data/templates/new_gem/README.md.erb +43 -0
  34. data/templates/new_gem/Rakefile +13 -0
  35. data/templates/new_gem/fluent-plugin.gemspec.erb +27 -0
  36. data/templates/new_gem/lib/fluent/plugin/filter.rb.erb +14 -0
  37. data/templates/new_gem/lib/fluent/plugin/formatter.rb.erb +14 -0
  38. data/templates/new_gem/lib/fluent/plugin/input.rb.erb +11 -0
  39. data/templates/new_gem/lib/fluent/plugin/output.rb.erb +11 -0
  40. data/templates/new_gem/lib/fluent/plugin/parser.rb.erb +15 -0
  41. data/templates/new_gem/test/helper.rb.erb +8 -0
  42. data/templates/new_gem/test/plugin/test_filter.rb.erb +18 -0
  43. data/templates/new_gem/test/plugin/test_formatter.rb.erb +18 -0
  44. data/templates/new_gem/test/plugin/test_input.rb.erb +18 -0
  45. data/templates/new_gem/test/plugin/test_output.rb.erb +18 -0
  46. data/templates/new_gem/test/plugin/test_parser.rb.erb +18 -0
  47. data/templates/plugin_config_formatter/param.md-compact.erb +25 -0
  48. data/templates/plugin_config_formatter/param.md.erb +34 -0
  49. data/templates/plugin_config_formatter/section.md.erb +12 -0
  50. data/test/command/test_binlog_reader.rb +0 -9
  51. data/test/command/test_plugin_config_formatter.rb +275 -0
  52. data/test/command/test_plugin_generator.rb +66 -0
  53. data/test/config/test_configure_proxy.rb +89 -45
  54. data/test/plugin/data/log/foo/bar2 +0 -0
  55. data/test/plugin/test_in_tail.rb +97 -8
  56. data/test/plugin/test_output.rb +18 -0
  57. data/test/plugin/test_output_as_buffered.rb +35 -0
  58. data/test/plugin_helper/test_compat_parameters.rb +2 -0
  59. data/test/plugin_helper/test_retry_state.rb +23 -0
  60. data/test/test_output.rb +5 -0
  61. data/test/test_process.rb +14 -0
  62. metadata +37 -3
@@ -400,45 +400,54 @@ module Fluent::Config
400
400
  end
401
401
  end
402
402
 
403
- sub_test_case '#dump' do
403
+ sub_test_case '#dump_config_definition' do
404
404
  setup do
405
405
  @proxy = Fluent::Config::ConfigureProxy.new(:section, type_lookup: @type_lookup)
406
406
  end
407
407
 
408
408
  test 'empty proxy' do
409
- assert_equal("", @proxy.dump)
409
+ assert_equal({}, @proxy.dump_config_definition)
410
410
  end
411
411
 
412
412
  test 'plain proxy w/o default value' do
413
413
  @proxy.config_param(:name, :string)
414
- assert_equal(<<CONFIG, @proxy.dump)
415
- name: string: <nil>
416
- CONFIG
414
+ expected = {
415
+ name: { type: :string, required: true }
416
+ }
417
+ assert_equal(expected, @proxy.dump_config_definition)
417
418
  end
418
419
 
419
420
  test 'plain proxy w/ default value' do
420
421
  @proxy.config_param(:name, :string, default: "name1")
421
- assert_equal(<<CONFIG, @proxy.dump)
422
- name: string: <"name1">
423
- CONFIG
422
+ expected = {
423
+ name: { type: :string, default: "name1", required: false }
424
+ }
425
+ assert_equal(expected, @proxy.dump_config_definition)
424
426
  end
425
427
 
426
428
  test 'plain proxy w/ default value using config_set_default' do
427
429
  @proxy.config_param(:name, :string)
428
430
  @proxy.config_set_default(:name, "name1")
429
- assert_equal(<<CONFIG, @proxy.dump)
430
- name: string: <"name1">
431
- CONFIG
431
+ expected = {
432
+ name: { type: :string, default: "name1", required: false }
433
+ }
434
+ assert_equal(expected, @proxy.dump_config_definition)
432
435
  end
433
436
 
434
437
  test 'single sub proxy' do
435
438
  @proxy.config_section(:sub) do
436
439
  config_param(:name, :string, default: "name1")
437
440
  end
438
- assert_equal(<<CONFIG, @proxy.dump)
439
- sub
440
- name: string: <"name1">
441
- CONFIG
441
+ expected = {
442
+ sub: {
443
+ alias: nil,
444
+ multi: true,
445
+ required: false,
446
+ section: true,
447
+ name: { type: :string, default: "name1", required: false }
448
+ }
449
+ }
450
+ assert_equal(expected, @proxy.dump_config_definition)
442
451
  end
443
452
 
444
453
  test 'nested sub proxy' do
@@ -450,30 +459,43 @@ CONFIG
450
459
  config_param(:name4, :string, default: "name4")
451
460
  end
452
461
  end
453
- assert_equal(<<CONFIG, @proxy.dump)
454
- sub
455
- name1: string: <"name1">
456
- name2: string: <"name2">
457
- sub2
458
- name3: string: <"name3">
459
- name4: string: <"name4">
460
- CONFIG
462
+ expected = {
463
+ sub: {
464
+ alias: nil,
465
+ multi: true,
466
+ required: false,
467
+ section: true,
468
+ name1: { type: :string, default: "name1", required: false },
469
+ name2: { type: :string, default: "name2", required: false },
470
+ sub2: {
471
+ alias: nil,
472
+ multi: true,
473
+ required: false,
474
+ section: true,
475
+ name3: { type: :string, default: "name3", required: false },
476
+ name4: { type: :string, default: "name4", required: false },
477
+ }
478
+ }
479
+ }
480
+ assert_equal(expected, @proxy.dump_config_definition)
461
481
  end
462
482
 
463
483
  sub_test_case 'w/ description' do
464
484
  test 'single proxy' do
465
485
  @proxy.config_param(:name, :string, desc: "description for name")
466
- assert_equal(<<CONFIG, @proxy.dump)
467
- name: string: <nil> # description for name
468
- CONFIG
486
+ expected = {
487
+ name: { type: :string, desc: "description for name", required: true }
488
+ }
489
+ assert_equal(expected, @proxy.dump_config_definition)
469
490
  end
470
491
 
471
492
  test 'single proxy using config_set_desc' do
472
493
  @proxy.config_param(:name, :string)
473
494
  @proxy.config_set_desc(:name, "description for name")
474
- assert_equal(<<CONFIG, @proxy.dump)
475
- name: string: <nil> # description for name
476
- CONFIG
495
+ expected = {
496
+ name: { type: :string, desc: "description for name", required: true }
497
+ }
498
+ assert_equal(expected, @proxy.dump_config_definition)
477
499
  end
478
500
 
479
501
  test 'sub proxy' do
@@ -485,14 +507,25 @@ CONFIG
485
507
  config_param(:name4, :string, default: "name4", desc: "desc4")
486
508
  end
487
509
  end
488
- assert_equal(<<CONFIG, @proxy.dump)
489
- sub
490
- name1: string: <"name1"> # desc1
491
- name2: string: <"name2"> # desc2
492
- sub2
493
- name3: string: <"name3">
494
- name4: string: <"name4"> # desc4
495
- CONFIG
510
+ expected = {
511
+ sub: {
512
+ alias: nil,
513
+ multi: true,
514
+ required: false,
515
+ section: true,
516
+ name1: { type: :string, default: "name1", desc: "desc1", required: false },
517
+ name2: { type: :string, default: "name2", desc: "desc2", required: false },
518
+ sub2: {
519
+ alias: nil,
520
+ multi: true,
521
+ required: false,
522
+ section: true,
523
+ name3: { type: :string, default: "name3", required: false },
524
+ name4: { type: :string, default: "name4", desc: "desc4", required: false },
525
+ }
526
+ }
527
+ }
528
+ assert_equal(expected, @proxy.dump_config_definition)
496
529
  end
497
530
 
498
531
  test 'sub proxy w/ desc method' do
@@ -506,14 +539,25 @@ CONFIG
506
539
  config_param(:name4, :string, default: "name4")
507
540
  end
508
541
  end
509
- assert_equal(<<CONFIG, @proxy.dump)
510
- sub
511
- name1: string: <"name1"> # desc1
512
- name2: string: <"name2"> # desc2
513
- sub2
514
- name3: string: <"name3">
515
- name4: string: <"name4"> # desc4
516
- CONFIG
542
+ expected = {
543
+ sub: {
544
+ alias: nil,
545
+ multi: true,
546
+ required: false,
547
+ section: true,
548
+ name1: { type: :string, default: "name1", desc: "desc1", required: false },
549
+ name2: { type: :string, default: "name2", desc: "desc2", required: false },
550
+ sub2: {
551
+ alias: nil,
552
+ multi: true,
553
+ required: false,
554
+ section: true,
555
+ name3: { type: :string, default: "name3", required: false },
556
+ name4: { type: :string, default: "name4", desc: "desc4", required: false },
557
+ }
558
+ }
559
+ }
560
+ assert_equal(expected, @proxy.dump_config_definition)
517
561
  end
518
562
  end
519
563
  end
File without changes
@@ -12,14 +12,7 @@ class TailInputTest < Test::Unit::TestCase
12
12
 
13
13
  def setup
14
14
  Fluent::Test.setup
15
- FileUtils.rm_rf(TMP_DIR, secure: true)
16
- if File.exist?(TMP_DIR)
17
- # ensure files are closed for Windows, on which deleted files
18
- # are still visible from filesystem
19
- GC.start(full_mark: true, immediate_mark: true, immediate_sweep: true)
20
- FileUtils.remove_entry_secure(TMP_DIR, true)
21
- end
22
- FileUtils.mkdir_p(TMP_DIR)
15
+ cleanup_directory(TMP_DIR)
23
16
  end
24
17
 
25
18
  def teardown
@@ -27,6 +20,17 @@ class TailInputTest < Test::Unit::TestCase
27
20
  Fluent::Engine.stop
28
21
  end
29
22
 
23
+ def cleanup_directory(path)
24
+ FileUtils.rm_rf(path, secure: true)
25
+ if File.exist?(path)
26
+ # ensure files are closed for Windows, on which deleted files
27
+ # are still visible from filesystem
28
+ GC.start(full_mark: true, immediate_mark: true, immediate_sweep: true)
29
+ FileUtils.remove_entry_secure(path, true)
30
+ end
31
+ FileUtils.mkdir_p(path)
32
+ end
33
+
30
34
  TMP_DIR = File.dirname(__FILE__) + "/../tmp/tail#{ENV['TEST_ENV_NUMBER']}"
31
35
 
32
36
  CONFIG = config_element("ROOT", "", {
@@ -847,6 +851,67 @@ class TailInputTest < Test::Unit::TestCase
847
851
  plugin = create_driver(exclude_config, false).instance
848
852
  assert_equal EX_PATHS - [EX_PATHS.last], plugin.expand_paths.sort
849
853
  end
854
+
855
+ def test_log_file_without_extension
856
+ expected_files = [
857
+ 'test/plugin/data/log/bar',
858
+ 'test/plugin/data/log/foo/bar.log',
859
+ 'test/plugin/data/log/foo/bar2',
860
+ 'test/plugin/data/log/test.log'
861
+ ]
862
+
863
+ config = config_element("", "", {
864
+ "tag" => "tail",
865
+ "path" => "test/plugin/data/log/**/*",
866
+ "format" => "none",
867
+ "pos_file" => "#{TMP_DIR}/tail.pos"
868
+ })
869
+
870
+ plugin = create_driver(config, false).instance
871
+ assert_equal expected_files, plugin.expand_paths.sort
872
+ end
873
+
874
+ # For https://github.com/fluent/fluentd/issues/1455
875
+ # This test is fragile because test content depends on internal implementaion.
876
+ # So if you modify in_tail internal, this test may break.
877
+ def test_unwatched_files_should_be_removed
878
+ config = config_element("", "", {
879
+ "tag" => "tail",
880
+ "path" => "#{TMP_DIR}/*.txt",
881
+ "format" => "none",
882
+ "pos_file" => "#{TMP_DIR}/tail.pos",
883
+ "read_from_head" => true,
884
+ "refresh_interval" => 1,
885
+ })
886
+ d = create_driver(config, false)
887
+ d.run(expect_emits: 1, shutdown: false) do
888
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f| f.puts "test3\n" }
889
+ end
890
+
891
+ assert_equal 1, d.instance.instance_variable_get(:@tails).keys.size
892
+ cleanup_directory(TMP_DIR)
893
+ waiting(20) { sleep 0.1 until Dir.glob("#{TMP_DIR}/*.txt").size == 0 } # Ensure file is deleted on Windows
894
+ waiting(5) { sleep 0.1 until d.instance.instance_variable_get(:@tails).keys.size == 0 }
895
+
896
+ # Previous implementaion has an infinite watcher creation bug.
897
+ # Following code checks such unexpected bug by couting actual object allocation.
898
+ base_num = count_timer_object
899
+ 2.times {
900
+ sleep 1
901
+ num = count_timer_object
902
+ assert_equal base_num, num
903
+ }
904
+
905
+ d.instance_shutdown
906
+ end
907
+
908
+ def count_timer_object
909
+ num = 0
910
+ ObjectSpace.each_object(Fluent::PluginHelper::Timer::TimerWatcher) { |obj|
911
+ num += 1
912
+ }
913
+ num
914
+ end
850
915
  end
851
916
 
852
917
  def test_z_refresh_watchers
@@ -1109,4 +1174,28 @@ class TailInputTest < Test::Unit::TestCase
1109
1174
  assert_equal(files, [events[2][2]["path"], events[3][2]["path"]].sort)
1110
1175
  end
1111
1176
  end
1177
+
1178
+ def test_limit_recently_modified
1179
+ now = Time.new(2010, 1, 2, 3, 4, 5)
1180
+ FileUtils.touch("#{TMP_DIR}/tail_unwatch.txt", mtime: (now - 3601))
1181
+ FileUtils.touch("#{TMP_DIR}/tail_watch1.txt", mtime: (now - 3600))
1182
+ FileUtils.touch("#{TMP_DIR}/tail_watch2.txt", mtime: now)
1183
+
1184
+ config = config_element('', '', {
1185
+ 'tag' => 'tail',
1186
+ 'path' => "#{TMP_DIR}/*.txt",
1187
+ 'format' => 'none',
1188
+ 'limit_recently_modified' => '3600s'
1189
+ })
1190
+
1191
+ expected_files = [
1192
+ "#{TMP_DIR}/tail_watch1.txt",
1193
+ "#{TMP_DIR}/tail_watch2.txt"
1194
+ ]
1195
+
1196
+ Timecop.freeze(now) do
1197
+ plugin = create_driver(config, false).instance
1198
+ assert_equal expected_files, plugin.expand_paths.sort
1199
+ end
1200
+ end
1112
1201
  end
@@ -706,6 +706,24 @@ class OutputTest < Test::Unit::TestCase
706
706
  i.stop; i.before_shutdown; i.shutdown; i.after_shutdown; i.close; i.terminate
707
707
  end
708
708
 
709
+ test 'flush_interval is ignored when flush_mode is not interval' do
710
+ mock(@i.log).warn("'flush_interval' is ignored because default 'flush_mode' is not 'interval': 'lazy'")
711
+ @i.configure(config_element('ROOT', '', {}, [config_element('buffer', 'time', {'timekey' => 60*30, 'flush_interval' => 10})]))
712
+ end
713
+
714
+ data(:lazy => 'lazy', :immediate => 'immediate')
715
+ test 'flush_interval and non-interval flush_mode is exclusive ' do |mode|
716
+ assert_raise Fluent::ConfigError.new("'flush_interval' can't be specified when 'flush_mode' is not 'interval' explicitly: '#{mode}'") do
717
+ @i.configure(config_element('ROOT', '', {}, [config_element('buffer', '', {'flush_mode' => mode, 'flush_interval' => 10})]))
718
+ end
719
+ end
720
+
721
+ test 'flush_mode is set to interval when flush_interval with v0.12 configuration is given' do
722
+ mock(@i.log).info("'flush_interval' is configured at out side of <buffer>. 'flush_mode' is set to 'interval' to keep existing behaviour")
723
+ @i.configure(config_element('ROOT', '', {'flush_interval' => 60}, []))
724
+ assert_equal :interval, @i.instance_variable_get(:@flush_mode)
725
+ end
726
+
709
727
  test "Warn if primary type is different from secondary type and either primary or secondary has custom_format" do
710
728
  o = create_output(:buffered)
711
729
  mock(o.log).warn("secondary type should be same with primary one",
@@ -339,6 +339,41 @@ class BufferedOutputTest < Test::Unit::TestCase
339
339
  end
340
340
  end
341
341
 
342
+ data(:handle_stream_simple => '',
343
+ :handle_stream_with_custom_format => 'tag,message')
344
+ test 'plugin using custom format can skip record chunk when format return nil' do |chunk_keys|
345
+ events_from_chunk = []
346
+ @i = create_output(:custom)
347
+ @i.configure(config_element('ROOT', '', {}, [config_element('buffer', chunk_keys, @hash)]))
348
+ @i.register(:prefer_delayed_commit) { false }
349
+ @i.register(:format) { |tag, time, record|
350
+ if record['message'] == 'test1'
351
+ nil
352
+ else
353
+ [tag,time,record].to_msgpack
354
+ end
355
+ }
356
+ @i.register(:format_type_is_msgpack) { true }
357
+ @i.register(:write){ |chunk| e = []; chunk.each { |ta, t, r| e << [ta, t, r] }; events_from_chunk << [:write, e] }
358
+ @i.start
359
+ @i.after_start
360
+
361
+ events = [
362
+ [event_time('2016-10-05 16:16:16 -0700'), {"message" => "test1"}],
363
+ [event_time('2016-10-05 16:16:17 -0700'), {"message" => "test2"}],
364
+ ]
365
+ @i.emit_events("test.tag", Fluent::ArrayEventStream.new(events))
366
+
367
+ waiting(5) { sleep 0.1 until events_from_chunk.size == 1 }
368
+
369
+ assert_equal 1, events_from_chunk.size
370
+ assert_equal :write, events_from_chunk[0][0]
371
+ each_pushed = events_from_chunk[0][1]
372
+ assert_equal 1, each_pushed.size
373
+ assert_equal 'test.tag', each_pushed[0][0]
374
+ assert_equal "test2", each_pushed[0][2]['message']
375
+ end
376
+
342
377
  test 'plugin using custom format can iterate chunk in #try_write if #format returns msgpack' do
343
378
  events_from_chunk = []
344
379
  @i = create_output(:custom)
@@ -106,6 +106,7 @@ class CompatParameterTest < Test::Unit::TestCase
106
106
  assert_equal [], @i.buffer_config.chunk_keys
107
107
  assert_equal 8, @i.buffer_config.flush_thread_count
108
108
  assert_equal 10, @i.buffer_config.flush_interval
109
+ assert_equal :default, @i.buffer_config.flush_mode
109
110
  assert @i.buffer_config.flush_at_shutdown
110
111
 
111
112
  assert_equal 8*1024*1024, @i.buffer.chunk_limit_size
@@ -131,6 +132,7 @@ class CompatParameterTest < Test::Unit::TestCase
131
132
  assert @i.buffer_config.retry_forever
132
133
  assert_equal 60*60, @i.buffer_config.retry_max_interval
133
134
  assert_equal :block, @i.buffer_config.overflow_action
135
+ assert_equal :default, @i.buffer_config.flush_mode
134
136
 
135
137
  assert !@i.chunk_key_tag
136
138
  assert_equal [], @i.chunk_keys
@@ -178,6 +178,16 @@ class RetryStateHelperTest < Test::Unit::TestCase
178
178
  assert_equal (dummy_current_time + 100 * 0.75), s.secondary_transition_at
179
179
  end
180
180
 
181
+ test 'periodic retries with secondary and max_steps' do
182
+ s = @d.retry_state_create(:t3, :periodic, 3, 100, max_steps: 5, randomize: false, secondary: true)
183
+ dummy_current_time = s.start
184
+ override_current_time(s, dummy_current_time)
185
+
186
+ assert_equal dummy_current_time, s.current_time
187
+ assert_equal (dummy_current_time + 100), s.timeout_at
188
+ assert_equal (dummy_current_time + 3 * 5 * 0.8), s.secondary_transition_at
189
+ end
190
+
181
191
  test 'exponential backoff forever without randomization' do
182
192
  s = @d.retry_state_create(:t11, :exponential_backoff, 0.1, 300, randomize: false, forever: true, backoff_base: 2)
183
193
  dummy_current_time = s.start
@@ -396,4 +406,17 @@ class RetryStateHelperTest < Test::Unit::TestCase
396
406
  assert_equal (dummy_current_time + 100), s.timeout_at
397
407
  assert_equal (dummy_current_time + 100 * 0.75), s.secondary_transition_at
398
408
  end
409
+
410
+ test 'exponential backoff retries with secondary and max_steps' do
411
+ s = @d.retry_state_create(:t15, :exponential_backoff, 1, 100, randomize: false, max_steps: 5, backoff_base: 2, secondary: true) # threshold 0.8
412
+ dummy_current_time = s.start
413
+ override_current_time(s, dummy_current_time)
414
+
415
+ timeout = 0
416
+ 5.times { |i| timeout += 1.0 * (2 ** (i - 1)) }
417
+
418
+ assert_equal dummy_current_time, s.current_time
419
+ assert_equal (dummy_current_time + 100), s.timeout_at
420
+ assert_equal (dummy_current_time + timeout * 0.8), s.secondary_transition_at
421
+ end
399
422
  end
@@ -2,6 +2,7 @@ require_relative 'helper'
2
2
  require 'fluent/test'
3
3
  require 'fluent/output'
4
4
  require 'fluent/output_chain'
5
+ require 'fluent/plugin/buffer'
5
6
  require 'timecop'
6
7
  require 'flexmock/test_unit'
7
8
 
@@ -142,6 +143,10 @@ module FluentOutputTest
142
143
 
143
144
  assert_not_nil d.instance.instance_variable_get(:@secondary).router
144
145
  end
146
+
147
+ test 'BufferQueueLimitError compatibility' do
148
+ assert_equal Fluent::Plugin::Buffer::BufferOverflowError, Fluent::BufferQueueLimitError
149
+ end
145
150
  end
146
151
 
147
152
  class ObjectBufferedOutputTest < ::Test::Unit::TestCase