fluentd 1.12.0-x64-mingw32 → 1.13.0-x64-mingw32

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.deepsource.toml +13 -0
  3. data/.github/ISSUE_TEMPLATE/config.yml +2 -2
  4. data/.github/workflows/linux-test.yaml +36 -0
  5. data/.github/workflows/macos-test.yaml +30 -0
  6. data/.github/workflows/windows-test.yaml +46 -0
  7. data/.gitlab-ci.yml +41 -19
  8. data/CHANGELOG.md +164 -2
  9. data/CONTRIBUTING.md +2 -2
  10. data/MAINTAINERS.md +5 -2
  11. data/README.md +7 -4
  12. data/example/counter.conf +1 -1
  13. data/fluentd.gemspec +5 -4
  14. data/lib/fluent/command/bundler_injection.rb +1 -1
  15. data/lib/fluent/command/ca_generate.rb +6 -3
  16. data/lib/fluent/command/cat.rb +19 -4
  17. data/lib/fluent/command/fluentd.rb +5 -2
  18. data/lib/fluent/command/plugin_config_formatter.rb +16 -1
  19. data/lib/fluent/command/plugin_generator.rb +31 -1
  20. data/lib/fluent/compat/parser.rb +2 -2
  21. data/lib/fluent/config/section.rb +2 -2
  22. data/lib/fluent/config/types.rb +2 -2
  23. data/lib/fluent/event.rb +3 -13
  24. data/lib/fluent/load.rb +0 -1
  25. data/lib/fluent/log.rb +1 -0
  26. data/lib/fluent/plugin/file_wrapper.rb +49 -4
  27. data/lib/fluent/plugin/formatter_ltsv.rb +2 -2
  28. data/lib/fluent/plugin/in_http.rb +11 -1
  29. data/lib/fluent/plugin/in_monitor_agent.rb +1 -1
  30. data/lib/fluent/plugin/in_tail.rb +141 -41
  31. data/lib/fluent/plugin/in_tail/position_file.rb +15 -1
  32. data/lib/fluent/plugin/out_copy.rb +18 -5
  33. data/lib/fluent/plugin/out_exec_filter.rb +3 -3
  34. data/lib/fluent/plugin/out_forward.rb +74 -58
  35. data/lib/fluent/plugin/out_http.rb +9 -2
  36. data/lib/fluent/plugin/output.rb +11 -9
  37. data/lib/fluent/plugin/parser_csv.rb +2 -2
  38. data/lib/fluent/plugin/parser_syslog.rb +2 -2
  39. data/lib/fluent/plugin/storage_local.rb +4 -4
  40. data/lib/fluent/plugin_helper/server.rb +4 -2
  41. data/lib/fluent/plugin_helper/service_discovery.rb +39 -1
  42. data/lib/fluent/plugin_helper/service_discovery/manager.rb +11 -5
  43. data/lib/fluent/plugin_helper/socket_option.rb +2 -2
  44. data/lib/fluent/supervisor.rb +28 -5
  45. data/lib/fluent/system_config.rb +16 -1
  46. data/lib/fluent/time.rb +57 -1
  47. data/lib/fluent/version.rb +1 -1
  48. data/templates/new_gem/fluent-plugin.gemspec.erb +3 -3
  49. data/templates/plugin_config_formatter/param.md-table.erb +10 -0
  50. data/test/command/test_cat.rb +96 -0
  51. data/test/command/test_fluentd.rb +38 -0
  52. data/test/command/test_plugin_config_formatter.rb +67 -0
  53. data/test/config/test_configurable.rb +1 -1
  54. data/test/config/test_system_config.rb +46 -0
  55. data/test/plugin/in_tail/test_io_handler.rb +4 -4
  56. data/test/plugin/in_tail/test_position_file.rb +59 -5
  57. data/test/plugin/test_file_wrapper.rb +115 -0
  58. data/test/plugin/test_in_exec.rb +1 -1
  59. data/test/plugin/test_in_http.rb +15 -0
  60. data/test/plugin/test_in_tail.rb +309 -26
  61. data/test/plugin/test_out_copy.rb +87 -0
  62. data/test/plugin/test_out_forward.rb +104 -11
  63. data/test/plugin/test_out_http.rb +20 -1
  64. data/test/plugin/test_output.rb +15 -3
  65. data/test/plugin/test_output_as_buffered_backup.rb +2 -0
  66. data/test/plugin/test_parser_csv.rb +14 -0
  67. data/test/plugin/test_parser_syslog.rb +14 -0
  68. data/test/plugin/test_sd_file.rb +1 -1
  69. data/test/plugin_helper/service_discovery/test_manager.rb +1 -1
  70. data/test/plugin_helper/test_child_process.rb +5 -2
  71. data/test/plugin_helper/test_http_server_helper.rb +4 -2
  72. data/test/plugin_helper/test_server.rb +26 -7
  73. data/test/plugin_helper/test_service_discovery.rb +74 -14
  74. data/test/test_config.rb +2 -1
  75. data/test/test_event.rb +16 -0
  76. data/test/test_formatter.rb +30 -0
  77. data/test/test_output.rb +2 -2
  78. data/test/test_supervisor.rb +66 -0
  79. data/test/test_time_parser.rb +109 -0
  80. metadata +58 -31
  81. data/.travis.yml +0 -77
  82. data/appveyor.yml +0 -31
@@ -27,14 +27,14 @@ module Fluent
27
27
 
28
28
  config_param :delimiter, :string, default: "\t".freeze
29
29
  config_param :label_delimiter, :string, default: ":".freeze
30
+ config_param :replacement, :string, default: " ".freeze
30
31
  config_param :add_newline, :bool, default: true
31
32
 
32
- # TODO: escaping for \t in values
33
33
  def format(tag, time, record)
34
34
  formatted = ""
35
35
  record.each do |label, value|
36
36
  formatted << @delimiter if formatted.length.nonzero?
37
- formatted << "#{label}#{@label_delimiter}#{value}"
37
+ formatted << "#{label}#{@label_delimiter}#{value.to_s.gsub(@delimiter, @replacement)}"
38
38
  end
39
39
  formatted << @newline if @add_newline
40
40
  formatted
@@ -461,6 +461,12 @@ module Fluent::Plugin
461
461
  RES_200_STATUS = "200 OK".freeze
462
462
  RES_403_STATUS = "403 Forbidden".freeze
463
463
 
464
+ # Azure App Service sends GET requests for health checking purpose.
465
+ # Respond with `200 OK` to accommodate it.
466
+ def handle_get_request
467
+ return send_response_and_close(RES_200_STATUS, {}, "")
468
+ end
469
+
464
470
  # Web browsers can send an OPTIONS request before performing POST
465
471
  # to check if cross-origin requests are supported.
466
472
  def handle_options_request
@@ -494,6 +500,10 @@ module Fluent::Plugin
494
500
  def on_message_complete
495
501
  return if closing?
496
502
 
503
+ if @parser.http_method == 'GET'.freeze
504
+ return handle_get_request()
505
+ end
506
+
497
507
  if @parser.http_method == 'OPTIONS'.freeze
498
508
  return handle_options_request()
499
509
  end
@@ -503,7 +513,7 @@ module Fluent::Plugin
503
513
  # For every incoming request, we check if we have some CORS
504
514
  # restrictions and allow listed origins through @cors_allow_origins.
505
515
  unless @cors_allow_origins.nil?
506
- unless @cors_allow_origins.include?('*') or include_cors_allow_origin
516
+ unless @cors_allow_origins.include?('*') || include_cors_allow_origin
507
517
  send_response_and_close(RES_403_STATUS, {'Connection' => 'close'}, "")
508
518
  return
509
519
  end
@@ -338,7 +338,7 @@ module Fluent::Plugin
338
338
  obj.merge!(pe.statistics['output'] || {})
339
339
  end
340
340
 
341
- obj['retry'] = get_retry_info(pe.retry) if opts[:with_retry] and pe.instance_variable_defined?(:@retry)
341
+ obj['retry'] = get_retry_info(pe.retry) if opts[:with_retry] && pe.instance_variable_defined?(:@retry)
342
342
 
343
343
  # include all instance variables if :with_debug_info is set
344
344
  if opts[:with_debug_info]
@@ -56,6 +56,7 @@ module Fluent::Plugin
56
56
  @pf_file = nil
57
57
  @pf = nil
58
58
  @ignore_list = []
59
+ @shutdown_start_time = nil
59
60
  end
60
61
 
61
62
  desc 'The paths to read. Multiple paths can be specified, separated by comma.'
@@ -81,6 +82,8 @@ module Fluent::Plugin
81
82
  config_param :refresh_interval, :time, default: 60
82
83
  desc 'The number of reading lines at each IO.'
83
84
  config_param :read_lines_limit, :integer, default: 1000
85
+ desc 'The number of reading bytes per second'
86
+ config_param :read_bytes_limit_per_second, :size, default: -1
84
87
  desc 'The interval of flushing the buffer for multiline format'
85
88
  config_param :multiline_flush_interval, :time, default: nil
86
89
  desc 'Enable the option to emit unmatched lines.'
@@ -178,6 +181,16 @@ module Fluent::Plugin
178
181
  # parser is already created by parser helper
179
182
  @parser = parser_create(usage: parser_config['usage'] || @parser_configs.first.usage)
180
183
  @capability = Fluent::Capability.new(:current_process)
184
+ if @read_bytes_limit_per_second > 0
185
+ if !@enable_watch_timer
186
+ raise Fluent::ConfigError, "Need to enable watch timer when using log throttling feature"
187
+ end
188
+ min_bytes = TailWatcher::IOHandler::BYTES_TO_READ
189
+ if @read_bytes_limit_per_second < min_bytes
190
+ log.warn "Should specify greater equal than #{min_bytes}. Use #{min_bytes} for read_bytes_limit_per_second"
191
+ @read_bytes_limit_per_second = min_bytes
192
+ end
193
+ end
181
194
  end
182
195
 
183
196
  def configure_tag
@@ -244,6 +257,7 @@ module Fluent::Plugin
244
257
  end
245
258
 
246
259
  def shutdown
260
+ @shutdown_start_time = Fluent::Clock.now
247
261
  # during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
248
262
  stop_watchers(existence_path, immediate: true, remove_watcher: false)
249
263
  @pf_file.close if @pf_file
@@ -290,7 +304,7 @@ module Fluent::Plugin
290
304
  end
291
305
  false
292
306
  end
293
- rescue Errno::ENOENT
307
+ rescue Errno::ENOENT, Errno::EACCES
294
308
  log.debug("#{p} is missing after refresh file list")
295
309
  false
296
310
  end
@@ -313,11 +327,17 @@ module Fluent::Plugin
313
327
  (paths - excluded).select { |path|
314
328
  FileTest.exist?(path)
315
329
  }.each { |path|
316
- target_info = TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
317
- if @follow_inodes
318
- hash[target_info.ino] = target_info
319
- else
320
- hash[target_info.path] = target_info
330
+ # Even we just checked for existence, there is a race condition here as
331
+ # of which stat() might fail with ENOENT. See #3224.
332
+ begin
333
+ target_info = TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
334
+ if @follow_inodes
335
+ hash[target_info.ino] = target_info
336
+ else
337
+ hash[target_info.path] = target_info
338
+ end
339
+ rescue Errno::ENOENT, Errno::EACCES => e
340
+ $log.warn "expand_paths: stat() for #{path} failed with #{e.class.name}. Skip file."
321
341
  end
322
342
  }
323
343
  hash
@@ -367,8 +387,6 @@ module Fluent::Plugin
367
387
  tw.register_watcher(tt)
368
388
  end
369
389
 
370
- tw.on_notify
371
-
372
390
  tw.watchers.each do |watcher|
373
391
  event_loop_attach(watcher)
374
392
  end
@@ -380,34 +398,48 @@ module Fluent::Plugin
380
398
  event_loop_detach(watcher)
381
399
  end
382
400
 
383
- tw.detach
401
+ tw.detach(@shutdown_start_time)
384
402
  tw.close
385
403
  end
386
404
  raise e
387
405
  end
388
406
 
389
- def start_watchers(targets_info)
390
- targets_info.each_value { |target_info|
391
- pe = nil
392
- if @pf
393
- pe = @pf[target_info]
394
- if @read_from_head && pe.read_inode.zero?
395
- begin
396
- pe.update(Fluent::FileWrapper.stat(target_info.path).ino, 0)
397
- rescue Errno::ENOENT
398
- $log.warn "#{target_info.path} not found. Continuing without tailing it."
399
- end
407
+ def construct_watcher(target_info)
408
+ pe = nil
409
+ if @pf
410
+ pe = @pf[target_info]
411
+ if @read_from_head && pe.read_inode.zero?
412
+ begin
413
+ pe.update(Fluent::FileWrapper.stat(target_info.path).ino, 0)
414
+ rescue Errno::ENOENT, Errno::EACCES
415
+ $log.warn "stat() for #{target_info.path} failed. Continuing without tailing it."
400
416
  end
401
417
  end
418
+ end
402
419
 
403
- begin
404
- tw = setup_watcher(target_info, pe)
405
- rescue WatcherSetupError => e
406
- log.warn "Skip #{target_info.path} because unexpected setup error happens: #{e}"
407
- next
408
- end
420
+ begin
421
+ tw = setup_watcher(target_info, pe)
422
+ rescue WatcherSetupError => e
423
+ log.warn "Skip #{target_info.path} because unexpected setup error happens: #{e}"
424
+ return
425
+ end
426
+
427
+ begin
409
428
  target_info = TargetInfo.new(target_info.path, Fluent::FileWrapper.stat(target_info.path).ino)
410
429
  @tails[target_info] = tw
430
+ tw.on_notify
431
+ rescue Errno::ENOENT, Errno::EACCES => e
432
+ $log.warn "stat() for #{target_info.path} failed with #{e.class.name}. Drop tail watcher for now."
433
+ # explicitly detach and unwatch watcher `tw`.
434
+ tw.unwatched = true
435
+ detach_watcher(tw, target_info.ino, false)
436
+ end
437
+ end
438
+
439
+ def start_watchers(targets_info)
440
+ targets_info.each_value {|target_info|
441
+ construct_watcher(target_info)
442
+ break if before_shutdown?
411
443
  }
412
444
  end
413
445
 
@@ -460,9 +492,11 @@ module Fluent::Plugin
460
492
 
461
493
  if new_position_entry.read_inode == 0
462
494
  @tails[new_target_info] = setup_watcher(new_target_info, new_position_entry)
495
+ @tails[new_target_info].on_notify
463
496
  end
464
497
  else
465
498
  @tails[new_target_info] = setup_watcher(new_target_info, pe)
499
+ @tails[new_target_info].on_notify
466
500
  end
467
501
  detach_watcher_after_rotate_wait(rotated_tw, pe.read_inode) if rotated_tw
468
502
  end
@@ -475,7 +509,7 @@ module Fluent::Plugin
475
509
  tw.watchers.each do |watcher|
476
510
  event_loop_detach(watcher)
477
511
  end
478
- tw.detach
512
+ tw.detach(@shutdown_start_time)
479
513
 
480
514
  tw.close if close_io
481
515
 
@@ -619,6 +653,7 @@ module Fluent::Plugin
619
653
  path: path,
620
654
  log: log,
621
655
  read_lines_limit: @read_lines_limit,
656
+ read_bytes_limit_per_second: @read_bytes_limit_per_second,
622
657
  open_on_every_update: @open_on_every_update,
623
658
  from_encoding: @from_encoding,
624
659
  encoding: @encoding,
@@ -686,8 +721,11 @@ module Fluent::Plugin
686
721
  @watchers << watcher
687
722
  end
688
723
 
689
- def detach
690
- @io_handler.on_notify if @io_handler
724
+ def detach(shutdown_start_time = nil)
725
+ if @io_handler
726
+ @io_handler.ready_to_shutdown(shutdown_start_time)
727
+ @io_handler.on_notify
728
+ end
691
729
  @line_buffer_timer_flusher&.close(self)
692
730
  end
693
731
 
@@ -701,7 +739,7 @@ module Fluent::Plugin
701
739
  def on_notify
702
740
  begin
703
741
  stat = Fluent::FileWrapper.stat(@path)
704
- rescue Errno::ENOENT
742
+ rescue Errno::ENOENT, Errno::EACCES
705
743
  # moved or deleted
706
744
  stat = nil
707
745
  end
@@ -767,16 +805,22 @@ module Fluent::Plugin
767
805
  end
768
806
 
769
807
  if watcher_needs_update
770
- # No need to update a watcher if stat is nil (file not present), because moving to inodes will create
771
- # new watcher, and old watcher will be closed by stop_watcher in refresh_watchers method
772
- if stat
773
- target_info = TargetInfo.new(@path, stat.ino)
774
- if @follow_inodes
775
- # don't want to swap state because we need latest read offset in pos file even after rotate_wait
808
+ if @follow_inodes
809
+ # No need to update a watcher if stat is nil (file not present), because moving to inodes will create
810
+ # new watcher, and old watcher will be closed by stop_watcher in refresh_watchers method
811
+ # don't want to swap state because we need latest read offset in pos file even after rotate_wait
812
+ if stat
813
+ target_info = TargetInfo.new(@path, stat)
776
814
  @update_watcher.call(target_info, @pe)
777
- else
778
- @update_watcher.call(target_info, swap_state(@pe))
779
815
  end
816
+ else
817
+ # Permit to handle if stat is nil (file not present).
818
+ # If a file is mv-ed and a new file is created during
819
+ # calling `#refresh_watchers`s, and `#refresh_watchers` won't run `#start_watchers`
820
+ # and `#stop_watchers()` for the path because `target_paths_hash`
821
+ # always contains the path.
822
+ target_info = TargetInfo.new(@path, stat ? stat.ino : nil)
823
+ @update_watcher.call(target_info, swap_state(@pe))
780
824
  end
781
825
  else
782
826
  @log.info "detected rotation of #{@path}"
@@ -856,10 +900,16 @@ module Fluent::Plugin
856
900
  end
857
901
 
858
902
  class IOHandler
859
- def initialize(watcher, path:, read_lines_limit:, log:, open_on_every_update:, from_encoding: nil, encoding: nil, &receive_lines)
903
+ BYTES_TO_READ = 8192
904
+ SHUTDOWN_TIMEOUT = 5
905
+
906
+ attr_accessor :shutdown_timeout
907
+
908
+ def initialize(watcher, path:, read_lines_limit:, read_bytes_limit_per_second:, log:, open_on_every_update:, from_encoding: nil, encoding: nil, &receive_lines)
860
909
  @watcher = watcher
861
910
  @path = path
862
911
  @read_lines_limit = read_lines_limit
912
+ @read_bytes_limit_per_second = read_bytes_limit_per_second
863
913
  @receive_lines = receive_lines
864
914
  @open_on_every_update = open_on_every_update
865
915
  @fifo = FIFO.new(from_encoding || Encoding::ASCII_8BIT, encoding || Encoding::ASCII_8BIT)
@@ -868,6 +918,11 @@ module Fluent::Plugin
868
918
  @io = nil
869
919
  @notify_mutex = Mutex.new
870
920
  @log = log
921
+ @start_reading_time = nil
922
+ @number_bytes_read = 0
923
+ @shutdown_start_time = nil
924
+ @shutdown_timeout = SHUTDOWN_TIMEOUT
925
+ @shutdown_mutex = Mutex.new
871
926
 
872
927
  @log.info "following tail of #{@path}"
873
928
  end
@@ -876,6 +931,13 @@ module Fluent::Plugin
876
931
  @notify_mutex.synchronize { handle_notify }
877
932
  end
878
933
 
934
+ def ready_to_shutdown(shutdown_start_time = nil)
935
+ @shutdown_mutex.synchronize {
936
+ @shutdown_start_time =
937
+ shutdown_start_time || Fluent::Clock.now
938
+ }
939
+ end
940
+
879
941
  def close
880
942
  if @io && !@io.closed?
881
943
  @io.close
@@ -889,7 +951,35 @@ module Fluent::Plugin
889
951
 
890
952
  private
891
953
 
954
+ def limit_bytes_per_second_reached?
955
+ return false if @read_bytes_limit_per_second < 0 # not enabled by conf
956
+ return false if @number_bytes_read < @read_bytes_limit_per_second
957
+
958
+ @start_reading_time ||= Fluent::Clock.now
959
+ time_spent_reading = Fluent::Clock.now - @start_reading_time
960
+ @log.debug("time_spent_reading: #{time_spent_reading} #{ @watcher.path}")
961
+
962
+ if time_spent_reading < 1
963
+ true
964
+ else
965
+ @start_reading_time = nil
966
+ @number_bytes_read = 0
967
+ false
968
+ end
969
+ end
970
+
971
+ def should_shutdown_now?
972
+ # Ensure to read all remaining lines, but abort immediately if it
973
+ # seems to take too long time.
974
+ @shutdown_mutex.synchronize {
975
+ return false if @shutdown_start_time.nil?
976
+ return Fluent::Clock.now - @shutdown_start_time > @shutdown_timeout
977
+ }
978
+ end
979
+
892
980
  def handle_notify
981
+ return if limit_bytes_per_second_reached?
982
+
893
983
  with_io do |io|
894
984
  begin
895
985
  read_more = false
@@ -897,8 +987,18 @@ module Fluent::Plugin
897
987
  if !io.nil? && @lines.empty?
898
988
  begin
899
989
  while true
900
- @fifo << io.readpartial(8192, @iobuf)
990
+ @start_reading_time ||= Fluent::Clock.now
991
+ data = io.readpartial(BYTES_TO_READ, @iobuf)
992
+ @number_bytes_read += data.bytesize
993
+ @fifo << data
901
994
  @fifo.read_lines(@lines)
995
+
996
+ @log.debug("reading file: #{@path}")
997
+ if limit_bytes_per_second_reached? || should_shutdown_now?
998
+ # Just get out from tailing loop.
999
+ read_more = false
1000
+ break
1001
+ end
902
1002
  if @lines.size >= @read_lines_limit
903
1003
  # not to use too much memory in case the file is very large
904
1004
  read_more = true
@@ -928,7 +1028,7 @@ module Fluent::Plugin
928
1028
  rescue RangeError
929
1029
  io.close if io
930
1030
  raise WatcherSetupError, "seek error with #{@path}: file position = #{@watcher.pe.read_pos.to_s(16)}, reading bytesize = #{@fifo.bytesize.to_s(16)}"
931
- rescue Errno::ENOENT
1031
+ rescue Errno::ENOENT, Errno::EACCES
932
1032
  nil
933
1033
  end
934
1034
 
@@ -248,6 +248,20 @@ module Fluent::Plugin
248
248
  end
249
249
  end
250
250
 
251
- TargetInfo = Struct.new(:path, :ino)
251
+ TargetInfo = Struct.new(:path, :ino) do
252
+ def ==(other)
253
+ return false unless other.is_a?(TargetInfo)
254
+ self.path == other.path
255
+ end
256
+
257
+ def hash
258
+ self.path.hash
259
+ end
260
+
261
+ def eql?(other)
262
+ return false unless other.is_a?(TargetInfo)
263
+ self.path == other.path
264
+ end
265
+ end
252
266
  end
253
267
  end
@@ -27,20 +27,28 @@ module Fluent::Plugin
27
27
  desc 'Pass different record to each `store` plugin by specified method'
28
28
  config_param :copy_mode, :enum, list: [:no_copy, :shallow, :deep, :marshal], default: :no_copy
29
29
 
30
- attr_reader :ignore_errors
30
+ attr_reader :ignore_errors, :ignore_if_prev_successes
31
31
 
32
32
  def initialize
33
33
  super
34
34
  @ignore_errors = []
35
+ @ignore_if_prev_successes = []
35
36
  end
36
37
 
37
38
  def configure(conf)
38
39
  super
39
40
 
40
41
  @copy_proc = gen_copy_proc
41
- @stores.each { |store|
42
- @ignore_errors << (store.arg == 'ignore_error')
42
+ @stores.each_with_index { |store, i|
43
+ if i == 0 && store.arg.include?('ignore_if_prev_success')
44
+ raise Fluent::ConfigError, "ignore_if_prev_success must specify 2nd or later <store> directives"
45
+ end
46
+ @ignore_errors << (store.arg.include?('ignore_error'))
47
+ @ignore_if_prev_successes << (store.arg.include?('ignore_if_prev_success'))
43
48
  }
49
+ if @ignore_errors.uniq.size == 1 && @ignore_errors.include?(true) && @ignore_if_prev_successes.include?(false)
50
+ log.warn "ignore_errors are specified in all <store>, but ignore_if_prev_success is not specified. Is this intended?"
51
+ end
44
52
  end
45
53
 
46
54
  def multi_workers_ready?
@@ -55,10 +63,15 @@ module Fluent::Plugin
55
63
  }
56
64
  es = m
57
65
  end
58
-
66
+ success = Array.new(outputs.size)
59
67
  outputs.each_with_index do |output, i|
60
68
  begin
61
- output.emit_events(tag, @copy_proc ? @copy_proc.call(es) : es)
69
+ if i > 0 && success[i - 1] && @ignore_if_prev_successes[i]
70
+ log.debug "ignore copy because prev_success in #{output.plugin_id}", index: i
71
+ else
72
+ output.emit_events(tag, @copy_proc ? @copy_proc.call(es) : es)
73
+ success[i] = true
74
+ end
62
75
  rescue => e
63
76
  if @ignore_errors[i]
64
77
  log.error "ignore emit error in #{output.plugin_id}", error: e