fluentd 1.16.1-x64-mingw32 → 1.16.3-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linux-test.yaml +2 -2
  3. data/.github/workflows/macos-test.yaml +2 -2
  4. data/.github/workflows/windows-test.yaml +2 -2
  5. data/CHANGELOG.md +50 -0
  6. data/Rakefile +1 -1
  7. data/fluentd.gemspec +1 -1
  8. data/lib/fluent/command/ctl.rb +2 -2
  9. data/lib/fluent/command/plugin_config_formatter.rb +1 -1
  10. data/lib/fluent/config/dsl.rb +1 -1
  11. data/lib/fluent/config/v1_parser.rb +2 -2
  12. data/lib/fluent/counter/server.rb +1 -1
  13. data/lib/fluent/counter/validator.rb +3 -3
  14. data/lib/fluent/engine.rb +1 -1
  15. data/lib/fluent/event.rb +6 -2
  16. data/lib/fluent/log.rb +9 -0
  17. data/lib/fluent/match.rb +1 -1
  18. data/lib/fluent/msgpack_factory.rb +6 -1
  19. data/lib/fluent/plugin/base.rb +1 -1
  20. data/lib/fluent/plugin/buffer.rb +1 -1
  21. data/lib/fluent/plugin/filter_record_transformer.rb +1 -1
  22. data/lib/fluent/plugin/in_forward.rb +1 -1
  23. data/lib/fluent/plugin/in_http.rb +8 -8
  24. data/lib/fluent/plugin/in_sample.rb +1 -1
  25. data/lib/fluent/plugin/in_tail/position_file.rb +32 -18
  26. data/lib/fluent/plugin/in_tail.rb +81 -25
  27. data/lib/fluent/plugin/out_exec_filter.rb +2 -2
  28. data/lib/fluent/plugin/output.rb +1 -1
  29. data/lib/fluent/plugin/parser_json.rb +1 -1
  30. data/lib/fluent/plugin_helper/event_loop.rb +2 -2
  31. data/lib/fluent/plugin_helper/record_accessor.rb +1 -1
  32. data/lib/fluent/plugin_helper/thread.rb +3 -3
  33. data/lib/fluent/plugin_id.rb +1 -1
  34. data/lib/fluent/supervisor.rb +1 -1
  35. data/lib/fluent/system_config.rb +1 -1
  36. data/lib/fluent/version.rb +1 -1
  37. data/test/config/test_system_config.rb +2 -2
  38. data/test/plugin/in_tail/test_position_file.rb +31 -1
  39. data/test/plugin/test_base.rb +1 -1
  40. data/test/plugin/test_buffer_chunk.rb +11 -0
  41. data/test/plugin/test_in_forward.rb +9 -9
  42. data/test/plugin/test_in_tail.rb +484 -0
  43. data/test/plugin/test_in_unix.rb +2 -2
  44. data/test/plugin/test_multi_output.rb +1 -1
  45. data/test/plugin/test_out_exec_filter.rb +2 -2
  46. data/test/plugin/test_out_file.rb +2 -2
  47. data/test/plugin/test_output.rb +12 -12
  48. data/test/plugin/test_output_as_buffered.rb +44 -44
  49. data/test/plugin/test_output_as_buffered_retries.rb +1 -1
  50. data/test/plugin/test_output_as_buffered_secondary.rb +2 -2
  51. data/test/plugin_helper/test_child_process.rb +2 -2
  52. data/test/plugin_helper/test_server.rb +1 -1
  53. data/test/test_log.rb +38 -1
  54. data/test/test_msgpack_factory.rb +32 -0
  55. data/test/test_supervisor.rb +13 -0
  56. metadata +5 -5
@@ -2638,4 +2638,488 @@ class TailInputTest < Test::Unit::TestCase
2638
2638
  end
2639
2639
  end
2640
2640
  end
2641
+
2642
+ sub_test_case "Update watchers for rotation with follow_inodes" do
2643
+ def test_updateTW_before_refreshTW_and_detach_before_refreshTW
2644
+ config = config_element(
2645
+ "ROOT",
2646
+ "",
2647
+ {
2648
+ "path" => "#{@tmp_dir}/tail.txt*",
2649
+ "pos_file" => "#{@tmp_dir}/tail.pos",
2650
+ "tag" => "t1",
2651
+ "format" => "none",
2652
+ "read_from_head" => "true",
2653
+ "follow_inodes" => "true",
2654
+ # In order to detach the old watcher quickly.
2655
+ "rotate_wait" => "1s",
2656
+ # In order to reproduce the same condition stably, ensure that `refresh_watchers` is not
2657
+ # called by a timer.
2658
+ "refresh_interval" => "1h",
2659
+ # stat_watcher often calls `TailWatcher::on_notify` faster than creating a new log file,
2660
+ # so disable it in order to reproduce the same condition stably.
2661
+ "enable_stat_watcher" => "false",
2662
+ }
2663
+ )
2664
+ d = create_driver(config, false)
2665
+
2666
+ tail_watchers = []
2667
+ stub.proxy(d.instance).setup_watcher do |tw|
2668
+ tail_watchers.append(tw)
2669
+ tw
2670
+ end
2671
+
2672
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file1 log1"}
2673
+
2674
+ d.run(expect_records: 4, timeout: 10) do
2675
+ # Rotate (If the timing is bad, `TailWatcher::on_notify` might be called between mv and new-file-creation)
2676
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file1 log2"}
2677
+ FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt" + "1")
2678
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file2 log1"}
2679
+
2680
+ # `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` updates the TailWatcher:
2681
+ # TailWatcher(path: "tail.txt", inode: inode_0) => TailWatcher(path: "tail.txt", inode: inode_1)
2682
+ # The old TailWathcer is detached here since `rotate_wait` is just `1s`.
2683
+ sleep 3
2684
+
2685
+ # This reproduces the following situation:
2686
+ # Rotation => update_watcher => refresh_watchers
2687
+ # This adds a new TailWatcher: TailWatcher(path: "tail.txt1", inode: inode_0)
2688
+ d.instance.refresh_watchers
2689
+
2690
+ # Append to the new current log file.
2691
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file2 log2"}
2692
+ end
2693
+
2694
+ inode_0 = tail_watchers[0].ino
2695
+ inode_1 = tail_watchers[1].ino
2696
+ record_values = d.events.collect { |event| event[2]["message"] }.sort
2697
+ position_entries = []
2698
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
2699
+ f.readlines(chomp: true).each do |line|
2700
+ values = line.split("\t")
2701
+ position_entries.append([values[0], values[1], values[2].to_i(16)])
2702
+ end
2703
+ end
2704
+
2705
+ assert_equal(
2706
+ {
2707
+ record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2"],
2708
+ tail_watcher_paths: ["#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt1"],
2709
+ tail_watcher_inodes: [inode_0, inode_1, inode_0],
2710
+ tail_watcher_io_handler_opened_statuses: [false, false, false],
2711
+ position_entries: [
2712
+ ["#{@tmp_dir}/tail.txt", "0000000000000016", inode_0],
2713
+ ["#{@tmp_dir}/tail.txt", "0000000000000016", inode_1],
2714
+ ],
2715
+ },
2716
+ {
2717
+ record_values: record_values,
2718
+ tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
2719
+ tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
2720
+ tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
2721
+ position_entries: position_entries
2722
+ },
2723
+ )
2724
+ end
2725
+
2726
+ def test_updateTW_before_refreshTW_and_detach_after_refreshTW
2727
+ config = config_element(
2728
+ "ROOT",
2729
+ "",
2730
+ {
2731
+ "path" => "#{@tmp_dir}/tail.txt*",
2732
+ "pos_file" => "#{@tmp_dir}/tail.pos",
2733
+ "tag" => "t1",
2734
+ "format" => "none",
2735
+ "read_from_head" => "true",
2736
+ "follow_inodes" => "true",
2737
+ # In order to detach the old watcher after refresh_watchers.
2738
+ "rotate_wait" => "4s",
2739
+ # In order to reproduce the same condition stably, ensure that `refresh_watchers` is not
2740
+ # called by a timer.
2741
+ "refresh_interval" => "1h",
2742
+ # stat_watcher often calls `TailWatcher::on_notify` faster than creating a new log file,
2743
+ # so disable it in order to reproduce the same condition stably.
2744
+ "enable_stat_watcher" => "false",
2745
+ }
2746
+ )
2747
+ d = create_driver(config, false)
2748
+
2749
+ tail_watchers = []
2750
+ stub.proxy(d.instance).setup_watcher do |tw|
2751
+ tail_watchers.append(tw)
2752
+ tw
2753
+ end
2754
+
2755
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file1 log1"}
2756
+
2757
+ d.run(expect_records: 4, timeout: 10) do
2758
+ # Rotate (If the timing is bad, `TailWatcher::on_notify` might be called between mv and new-file-creation)
2759
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file1 log2"}
2760
+ FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt" + "1")
2761
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file2 log1"}
2762
+
2763
+ # `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` updates the TailWatcher:
2764
+ # TailWatcher(path: "tail.txt", inode: inode_0) => TailWatcher(path: "tail.txt", inode: inode_1)
2765
+ sleep 2
2766
+
2767
+ # This reproduces the following situation:
2768
+ # Rotation => update_watcher => refresh_watchers
2769
+ # This adds a new TailWatcher: TailWatcher(path: "tail.txt1", inode: inode_0)
2770
+ d.instance.refresh_watchers
2771
+
2772
+ # The old TailWathcer is detached here since `rotate_wait` is `4s`.
2773
+ sleep 3
2774
+
2775
+ # Append to the new current log file.
2776
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file2 log2"}
2777
+ end
2778
+
2779
+ inode_0 = tail_watchers[0].ino
2780
+ inode_1 = tail_watchers[1].ino
2781
+ record_values = d.events.collect { |event| event[2]["message"] }.sort
2782
+ position_entries = []
2783
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
2784
+ f.readlines(chomp: true).each do |line|
2785
+ values = line.split("\t")
2786
+ position_entries.append([values[0], values[1], values[2].to_i(16)])
2787
+ end
2788
+ end
2789
+
2790
+ assert_equal(
2791
+ {
2792
+ record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2"],
2793
+ tail_watcher_paths: ["#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt1"],
2794
+ tail_watcher_inodes: [inode_0, inode_1, inode_0],
2795
+ tail_watcher_io_handler_opened_statuses: [false, false, false],
2796
+ position_entries: [
2797
+ # The recorded path is old, but it is no problem. The path is not used when using follow_inodes.
2798
+ ["#{@tmp_dir}/tail.txt", "0000000000000016", inode_0],
2799
+ ["#{@tmp_dir}/tail.txt", "0000000000000016", inode_1],
2800
+ ],
2801
+ },
2802
+ {
2803
+ record_values: record_values,
2804
+ tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
2805
+ tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
2806
+ tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
2807
+ position_entries: position_entries
2808
+ },
2809
+ )
2810
+ end
2811
+
2812
+ # The scenario where in_tail wrongly detaches TailWatcher.
2813
+ # This is reported in https://github.com/fluent/fluentd/issues/4190.
2814
+ def test_updateTW_after_refreshTW
2815
+ config = config_element(
2816
+ "ROOT",
2817
+ "",
2818
+ {
2819
+ "path" => "#{@tmp_dir}/tail.txt*",
2820
+ "pos_file" => "#{@tmp_dir}/tail.pos",
2821
+ "tag" => "t1",
2822
+ "format" => "none",
2823
+ "read_from_head" => "true",
2824
+ "follow_inodes" => "true",
2825
+ # In order to detach the old watcher quickly.
2826
+ "rotate_wait" => "1s",
2827
+ # In order to reproduce the same condition stably, ensure that `refresh_watchers` is not
2828
+ # called by a timer.
2829
+ "refresh_interval" => "1h",
2830
+ # stat_watcher often calls `TailWatcher::on_notify` faster than creating a new log file,
2831
+ # so disable it in order to reproduce the same condition stably.
2832
+ "enable_stat_watcher" => "false",
2833
+ }
2834
+ )
2835
+ d = create_driver(config, false)
2836
+
2837
+ tail_watchers = []
2838
+ stub.proxy(d.instance).setup_watcher do |tw|
2839
+ tail_watchers.append(tw)
2840
+ tw
2841
+ end
2842
+
2843
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file1 log1"}
2844
+
2845
+ d.run(expect_records: 4, timeout: 10) do
2846
+ # Rotate (If the timing is bad, `TailWatcher::on_notify` might be called between mv and new-file-creation)
2847
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file1 log2"}
2848
+ FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt" + "1")
2849
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file2 log1"}
2850
+
2851
+ # This reproduces the following situation:
2852
+ # Rotation => refresh_watchers => update_watcher
2853
+ # This add a new TailWatcher: TailWatcher(path: "tail.txt", inode: inode_1)
2854
+ # This overwrites `@tails["tail.txt"]`
2855
+ d.instance.refresh_watchers
2856
+
2857
+ # `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` trys to update the TailWatcher:
2858
+ # TailWatcher(path: "tail.txt", inode: inode_0) => TailWatcher(path: "tail.txt", inode: inode_1)
2859
+ # However, it is already added in `refresh_watcher`, so `update_watcher` doesn't create the new TailWatcher.
2860
+ # The old TailWathcer is detached here since `rotate_wait` is just `1s`.
2861
+ sleep 3
2862
+
2863
+ # This adds a new TailWatcher: TailWatcher(path: "tail.txt1", inode: inode_0)
2864
+ d.instance.refresh_watchers
2865
+
2866
+ # Append to the new current log file.
2867
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file2 log2"}
2868
+ end
2869
+
2870
+ inode_0 = tail_watchers[0].ino
2871
+ inode_1 = tail_watchers[1].ino
2872
+ record_values = d.events.collect { |event| event[2]["message"] }.sort
2873
+ position_entries = []
2874
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
2875
+ f.readlines(chomp: true).each do |line|
2876
+ values = line.split("\t")
2877
+ position_entries.append([values[0], values[1], values[2].to_i(16)])
2878
+ end
2879
+ end
2880
+
2881
+ assert_equal(
2882
+ {
2883
+ record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2"],
2884
+ tail_watcher_paths: ["#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt1"],
2885
+ tail_watcher_inodes: [inode_0, inode_1, inode_0],
2886
+ tail_watcher_io_handler_opened_statuses: [false, false, false],
2887
+ position_entries: [
2888
+ # The recorded path is old, but it is no problem. The path is not used when using follow_inodes.
2889
+ ["#{@tmp_dir}/tail.txt", "0000000000000016", inode_0],
2890
+ ["#{@tmp_dir}/tail.txt", "0000000000000016", inode_1],
2891
+ ],
2892
+ },
2893
+ {
2894
+ record_values: record_values,
2895
+ tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
2896
+ tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
2897
+ tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
2898
+ position_entries: position_entries
2899
+ },
2900
+ )
2901
+ end
2902
+
2903
+ def test_path_resurrection
2904
+ config = config_element(
2905
+ "ROOT",
2906
+ "",
2907
+ {
2908
+ "path" => "#{@tmp_dir}/tail.txt*",
2909
+ "pos_file" => "#{@tmp_dir}/tail.pos",
2910
+ "tag" => "t1",
2911
+ "format" => "none",
2912
+ "read_from_head" => "true",
2913
+ "follow_inodes" => "true",
2914
+ # In order to reproduce the same condition stably, ensure that `refresh_watchers` is not
2915
+ # called by a timer.
2916
+ "refresh_interval" => "1h",
2917
+ # https://github.com/fluent/fluentd/pull/4237#issuecomment-1633358632
2918
+ # Because of this problem, log duplication can occur during `rotate_wait`.
2919
+ # Need to set `rotate_wait 0` for a workaround.
2920
+ "rotate_wait" => "0s",
2921
+ }
2922
+ )
2923
+ d = create_driver(config, false)
2924
+
2925
+ tail_watchers = []
2926
+ stub.proxy(d.instance).setup_watcher do |tw|
2927
+ tail_watchers.append(tw)
2928
+ tw
2929
+ end
2930
+
2931
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file1 log1"}
2932
+
2933
+ d.run(expect_records: 5, timeout: 10) do
2934
+ # Rotate
2935
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file1 log2"}
2936
+ FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt" + "1")
2937
+ # TailWatcher(path: "tail.txt", inode: inode_0) detects `tail.txt` disappeared.
2938
+ # Call `update_watcher` to stop and discard self.
2939
+ # If not discarding, then it will be a orphan and cause leak and log duplication.
2940
+ #
2941
+ # This reproduces the case where the notify to TailWatcher comes before the new file for the path
2942
+ # is created during rotation.
2943
+ # (stat_watcher notifies faster than a new file is created)
2944
+ # Overall, this is a rotation operation, but from the TailWatcher, it appears as if the file
2945
+ # was resurrected once it disappeared.
2946
+ sleep 2 # On Windows and macOS, StatWatcher doesn't work, so need enough interval for TimeTrigger.
2947
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "file2 log1"}
2948
+
2949
+ # Add new TailWatchers
2950
+ # tail.txt: TailWatcher(path: "tail.txt", inode: inode_1)
2951
+ # tail.txt: TailWatcher(path: "tail.txt1", inode: inode_0)
2952
+ # NOTE: If not discarding the first TailWatcher on notify, this makes it a orphan because
2953
+ # this overwrites the `@tails[tail.txt]` by adding TailWatcher(path: "tail.txt", inode: inode_1)
2954
+ d.instance.refresh_watchers
2955
+
2956
+ # This does nothing.
2957
+ # NOTE: If not discarding the first TailWatcher on notify, this add
2958
+ # tail.txt1: TailWatcher(path: "tail.txt1", inode: inode_0)
2959
+ # because the previous refresh_watcher overwrites `@tails[tail.txt]` and the inode_0 is lost.
2960
+ # This would cause log duplication.
2961
+ d.instance.refresh_watchers
2962
+
2963
+ # Append to the old file
2964
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt1", "ab") {|f| f.puts "file1 log3"}
2965
+
2966
+ # Append to the new current log file.
2967
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "file2 log2"}
2968
+ end
2969
+
2970
+ inode_0 = Fluent::FileWrapper.stat("#{@tmp_dir}/tail.txt1").ino
2971
+ inode_1 = Fluent::FileWrapper.stat("#{@tmp_dir}/tail.txt").ino
2972
+ record_values = d.events.collect { |event| event[2]["message"] }.sort
2973
+ position_entries = []
2974
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
2975
+ f.readlines(chomp: true).each do |line|
2976
+ values = line.split("\t")
2977
+ position_entries.append([values[0], values[1], values[2].to_i(16)])
2978
+ end
2979
+ end
2980
+
2981
+ assert_equal(
2982
+ {
2983
+ record_values: ["file1 log1", "file1 log2", "file1 log3", "file2 log1", "file2 log2"],
2984
+ tail_watcher_set: Set[
2985
+ {
2986
+ path: "#{@tmp_dir}/tail.txt",
2987
+ inode: inode_0,
2988
+ io_handler_opened_status: false,
2989
+ },
2990
+ {
2991
+ path: "#{@tmp_dir}/tail.txt",
2992
+ inode: inode_1,
2993
+ io_handler_opened_status: false,
2994
+ },
2995
+ {
2996
+ path: "#{@tmp_dir}/tail.txt1",
2997
+ inode: inode_0,
2998
+ io_handler_opened_status: false,
2999
+ },
3000
+ ],
3001
+ position_entries: [
3002
+ ["#{@tmp_dir}/tail.txt", "0000000000000021", inode_0],
3003
+ ["#{@tmp_dir}/tail.txt", "0000000000000016", inode_1],
3004
+ ],
3005
+ },
3006
+ {
3007
+ record_values: record_values,
3008
+ tail_watcher_set: Set.new(tail_watchers.collect { |tw|
3009
+ {
3010
+ path: tw.path,
3011
+ inode: tw.ino,
3012
+ io_handler_opened_status: tw.instance_variable_get(:@io_handler)&.opened? || false,
3013
+ }
3014
+ }),
3015
+ position_entries: position_entries,
3016
+ },
3017
+ )
3018
+ end
3019
+ end
3020
+
3021
+ sub_test_case "Update watchers for rotation without follow_inodes" do
3022
+ # The scenario where in_tail wrongly unwatches the PositionEntry.
3023
+ # This is reported in https://github.com/fluent/fluentd/issues/3614.
3024
+ def test_refreshTW_during_rotation
3025
+ config = config_element(
3026
+ "ROOT",
3027
+ "",
3028
+ {
3029
+ "path" => "#{@tmp_dir}/tail.txt0",
3030
+ "pos_file" => "#{@tmp_dir}/tail.pos",
3031
+ "tag" => "t1",
3032
+ "format" => "none",
3033
+ "read_from_head" => "true",
3034
+ # In order to detach the old watcher quickly.
3035
+ "rotate_wait" => "3s",
3036
+ # In order to reproduce the same condition stably, ensure that `refresh_watchers` is not
3037
+ # called by a timer.
3038
+ "refresh_interval" => "1h",
3039
+ # stat_watcher often calls `TailWatcher::on_notify` faster than creating a new log file,
3040
+ # so disable it in order to reproduce the same condition stably.
3041
+ "enable_stat_watcher" => "false",
3042
+ }
3043
+ )
3044
+ d = create_driver(config, false)
3045
+
3046
+ tail_watchers = []
3047
+ stub.proxy(d.instance).setup_watcher do |tw|
3048
+ tail_watchers.append(tw)
3049
+ tw
3050
+ end
3051
+
3052
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file1 log1"}
3053
+
3054
+ d.run(expect_records: 6, timeout: 15) do
3055
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file1 log2"}
3056
+ FileUtils.move("#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt" + "1")
3057
+
3058
+ # This reproduces the following situation:
3059
+ # `refresh_watchers` is called during the rotation process and it detects the current file being lost.
3060
+ # Then it stops and unwatches the TailWatcher.
3061
+ d.instance.refresh_watchers
3062
+
3063
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file2 log1"}
3064
+
3065
+ # `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` trys to add the new TailWatcher.
3066
+ # After `rotate_wait` interval, the PositionEntry is unwatched.
3067
+ # HOWEVER, the new TailWatcher is still using that PositionEntry, so this breaks the PositionFile!!
3068
+ # That PositionEntry is removed from `PositionFile::map`, but it is still working and remaining in the real pos file.
3069
+ sleep 5
3070
+
3071
+ # Append to the new current log file.
3072
+ # The PositionEntry is updated although it does not exist in `PositionFile::map`.
3073
+ # `PositionFile::map`: empty
3074
+ # Real pos file: `.../tail.txt 0000000000000016 (inode)`
3075
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file2 log2"}
3076
+
3077
+ # Rotate again
3078
+ [1, 0].each do |i|
3079
+ FileUtils.move("#{@tmp_dir}/tail.txt#{i}", "#{@tmp_dir}/tail.txt#{i + 1}")
3080
+ end
3081
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file3 log1"}
3082
+
3083
+ # `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` trys to update the TailWatcher.
3084
+ sleep 3
3085
+
3086
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file3 log2"}
3087
+
3088
+ # Wait `rotate_wait` for file2 to make sure to close all IO handlers
3089
+ sleep 3
3090
+ end
3091
+
3092
+ inode_0 = tail_watchers[0]&.ino
3093
+ inode_1 = tail_watchers[1]&.ino
3094
+ inode_2 = tail_watchers[2]&.ino
3095
+ record_values = d.events.collect { |event| event[2]["message"] }.sort
3096
+ position_entries = []
3097
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
3098
+ f.readlines(chomp: true).each do |line|
3099
+ values = line.split("\t")
3100
+ position_entries.append([values[0], values[1], values[2].to_i(16)])
3101
+ end
3102
+ end
3103
+
3104
+ assert_equal(
3105
+ {
3106
+ record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2", "file3 log1", "file3 log2"],
3107
+ tail_watcher_paths: ["#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0"],
3108
+ tail_watcher_inodes: [inode_0, inode_1, inode_2],
3109
+ tail_watcher_io_handler_opened_statuses: [false, false, false],
3110
+ position_entries: [
3111
+ # The recorded path is old, but it is no problem. The path is not used when using follow_inodes.
3112
+ ["#{@tmp_dir}/tail.txt0", "0000000000000016", inode_2],
3113
+ ],
3114
+ },
3115
+ {
3116
+ record_values: record_values,
3117
+ tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
3118
+ tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
3119
+ tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
3120
+ position_entries: position_entries
3121
+ },
3122
+ )
3123
+ end
3124
+ end
2641
3125
  end
@@ -174,8 +174,8 @@ class UnixInputTest < Test::Unit::TestCase
174
174
  assert_equal 0, @d.events.size
175
175
 
176
176
  logs = @d.instance.log.logs
177
- assert_equal 1, logs.select { |line|
177
+ assert_equal 1, logs.count { |line|
178
178
  line =~ / \[warn\]: incoming data is broken: msg=#{data.inspect}/
179
- }.size, "should not accept broken chunk"
179
+ }, "should not accept broken chunk"
180
180
  end
181
181
  end unless Fluent.windows?
@@ -150,7 +150,7 @@ class MultiOutputTest < Test::Unit::TestCase
150
150
  log_size_for_metrics_plugin_helper = 4
151
151
  expected_warn_log_size = log_size_for_multi_output_itself + log_size_for_metrics_plugin_helper
152
152
  logs = @i.log.out.logs
153
- assert{ logs.select{|log| log.include?('[warn]') && log.include?("'type' is deprecated parameter name. use '@type' instead.") }.size == expected_warn_log_size }
153
+ assert{ logs.count{|log| log.include?('[warn]') && log.include?("'type' is deprecated parameter name. use '@type' instead.") } == expected_warn_log_size }
154
154
  end
155
155
 
156
156
  test '#emit_events calls #process always' do
@@ -597,8 +597,8 @@ class ExecFilterOutputTest < Test::Unit::TestCase
597
597
  # the number of pids should be same with number of child processes
598
598
  assert_equal 2, pid_list.size
599
599
  logs = d.instance.log.out.logs
600
- assert_equal 2, logs.select { |l| l.include?('child process exits with error code') }.size
601
- assert_equal 2, logs.select { |l| l.include?('respawning child process') }.size
600
+ assert_equal 2, logs.count { |l| l.include?('child process exits with error code') }
601
+ assert_equal 2, logs.count { |l| l.include?('respawning child process') }
602
602
 
603
603
  ensure
604
604
  d.run(start: false, shutdown: true)
@@ -264,8 +264,8 @@ class FileOutputTest < Test::Unit::TestCase
264
264
  assert !(Dir.exist?("#{TMP_DIR}/my.data/a"))
265
265
  assert !(Dir.exist?("#{TMP_DIR}/your.data/a"))
266
266
  buffer_files = Dir.entries("#{TMP_DIR}/buf_full").reject{|e| e =~ /^\.+$/ }
267
- assert_equal 2, buffer_files.select{|n| n.end_with?('.meta') }.size
268
- assert_equal 2, buffer_files.select{|n| !n.end_with?('.meta') }.size
267
+ assert_equal 2, buffer_files.count{|n| n.end_with?('.meta') }
268
+ assert_equal 2, buffer_files.count{|n| !n.end_with?('.meta') }
269
269
 
270
270
  m1 = d.instance.metadata('my.data', t1, {"type" => "a"})
271
271
  m2 = d.instance.metadata('your.data', t3, {"type" => "a"})
@@ -447,25 +447,25 @@ class OutputTest < Test::Unit::TestCase
447
447
  @i.configure(config_element('ROOT', '', {}, [config_element('buffer', '')]))
448
448
  validators = @i.placeholder_validators(:path, "/my/path/${tag}/${username}/file.%Y%m%d_%H%M.log")
449
449
  assert_equal 3, validators.size
450
- assert_equal 1, validators.select(&:time?).size
451
- assert_equal 1, validators.select(&:tag?).size
452
- assert_equal 1, validators.select(&:keys?).size
450
+ assert_equal 1, validators.count(&:time?)
451
+ assert_equal 1, validators.count(&:tag?)
452
+ assert_equal 1, validators.count(&:keys?)
453
453
  end
454
454
 
455
455
  test 'returns validators for time, tag and keys when a plugin is configured with these keys even if a template does not have placeholders' do
456
456
  @i.configure(config_element('ROOT', '', {}, [config_element('buffer', 'time,tag,username', {'timekey' => 60})]))
457
457
  validators = @i.placeholder_validators(:path, "/my/path/file.log")
458
458
  assert_equal 3, validators.size
459
- assert_equal 1, validators.select(&:time?).size
460
- assert_equal 1, validators.select(&:tag?).size
461
- assert_equal 1, validators.select(&:keys?).size
459
+ assert_equal 1, validators.count(&:time?)
460
+ assert_equal 1, validators.count(&:tag?)
461
+ assert_equal 1, validators.count(&:keys?)
462
462
  end
463
463
 
464
464
  test 'returns a validator for time if a template has timestamp placeholders' do
465
465
  @i.configure(config_element('ROOT', '', {}, [config_element('buffer', '')]))
466
466
  validators = @i.placeholder_validators(:path, "/my/path/file.%Y-%m-%d.log")
467
467
  assert_equal 1, validators.size
468
- assert_equal 1, validators.select(&:time?).size
468
+ assert_equal 1, validators.count(&:time?)
469
469
  assert_raise Fluent::ConfigError.new("Parameter 'path: /my/path/file.%Y-%m-%d.log' has timestamp placeholders, but chunk key 'time' is not configured") do
470
470
  validators.first.validate!
471
471
  end
@@ -475,7 +475,7 @@ class OutputTest < Test::Unit::TestCase
475
475
  @i.configure(config_element('ROOT', '', {}, [config_element('buffer', 'time', {'timekey' => '30'})]))
476
476
  validators = @i.placeholder_validators(:path, "/my/path/to/file.log")
477
477
  assert_equal 1, validators.size
478
- assert_equal 1, validators.select(&:time?).size
478
+ assert_equal 1, validators.count(&:time?)
479
479
  assert_raise Fluent::ConfigError.new("Parameter 'path: /my/path/to/file.log' doesn't have timestamp placeholders for timekey 30") do
480
480
  validators.first.validate!
481
481
  end
@@ -485,7 +485,7 @@ class OutputTest < Test::Unit::TestCase
485
485
  @i.configure(config_element('ROOT', '', {}, [config_element('buffer', '')]))
486
486
  validators = @i.placeholder_validators(:path, "/my/path/${tag}/file.log")
487
487
  assert_equal 1, validators.size
488
- assert_equal 1, validators.select(&:tag?).size
488
+ assert_equal 1, validators.count(&:tag?)
489
489
  assert_raise Fluent::ConfigError.new("Parameter 'path: /my/path/${tag}/file.log' has tag placeholders, but chunk key 'tag' is not configured") do
490
490
  validators.first.validate!
491
491
  end
@@ -495,7 +495,7 @@ class OutputTest < Test::Unit::TestCase
495
495
  @i.configure(config_element('ROOT', '', {}, [config_element('buffer', 'tag')]))
496
496
  validators = @i.placeholder_validators(:path, "/my/path/file.log")
497
497
  assert_equal 1, validators.size
498
- assert_equal 1, validators.select(&:tag?).size
498
+ assert_equal 1, validators.count(&:tag?)
499
499
  assert_raise Fluent::ConfigError.new("Parameter 'path: /my/path/file.log' doesn't have tag placeholder") do
500
500
  validators.first.validate!
501
501
  end
@@ -505,7 +505,7 @@ class OutputTest < Test::Unit::TestCase
505
505
  @i.configure(config_element('ROOT', '', {}, [config_element('buffer', '')]))
506
506
  validators = @i.placeholder_validators(:path, "/my/path/${username}/file.${group}.log")
507
507
  assert_equal 1, validators.size
508
- assert_equal 1, validators.select(&:keys?).size
508
+ assert_equal 1, validators.count(&:keys?)
509
509
  assert_raise Fluent::ConfigError.new("Parameter 'path: /my/path/${username}/file.${group}.log' has placeholders, but chunk keys doesn't have keys group,username") do
510
510
  validators.first.validate!
511
511
  end
@@ -515,7 +515,7 @@ class OutputTest < Test::Unit::TestCase
515
515
  @i.configure(config_element('ROOT', '', {}, [config_element('buffer', 'username,group')]))
516
516
  validators = @i.placeholder_validators(:path, "/my/path/file.log")
517
517
  assert_equal 1, validators.size
518
- assert_equal 1, validators.select(&:keys?).size
518
+ assert_equal 1, validators.count(&:keys?)
519
519
  assert_raise Fluent::ConfigError.new("Parameter 'path: /my/path/file.log' doesn't have enough placeholders for keys group,username") do
520
520
  validators.first.validate!
521
521
  end