fluentd 1.16.0-x64-mingw-ucrt → 1.16.2-x64-mingw-ucrt
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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.yaml +1 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yaml +1 -0
- data/.github/workflows/stale-actions.yml +24 -0
- data/CHANGELOG.md +74 -0
- data/CONTRIBUTING.md +1 -1
- data/MAINTAINERS.md +3 -3
- data/SECURITY.md +5 -9
- data/fluentd.gemspec +1 -1
- data/lib/fluent/command/ctl.rb +2 -2
- data/lib/fluent/command/plugin_config_formatter.rb +1 -1
- data/lib/fluent/config/dsl.rb +1 -1
- data/lib/fluent/config/v1_parser.rb +2 -2
- data/lib/fluent/counter/server.rb +1 -1
- data/lib/fluent/counter/validator.rb +3 -3
- data/lib/fluent/engine.rb +1 -1
- data/lib/fluent/event.rb +8 -4
- data/lib/fluent/log.rb +9 -0
- data/lib/fluent/match.rb +1 -1
- data/lib/fluent/msgpack_factory.rb +6 -1
- data/lib/fluent/plugin/base.rb +1 -1
- data/lib/fluent/plugin/filter_record_transformer.rb +1 -1
- data/lib/fluent/plugin/in_forward.rb +1 -1
- data/lib/fluent/plugin/in_http.rb +8 -8
- data/lib/fluent/plugin/in_sample.rb +1 -1
- data/lib/fluent/plugin/in_tail/position_file.rb +32 -18
- data/lib/fluent/plugin/in_tail.rb +58 -24
- data/lib/fluent/plugin/in_tcp.rb +43 -0
- data/lib/fluent/plugin/out_exec_filter.rb +2 -2
- data/lib/fluent/plugin/output.rb +2 -2
- data/lib/fluent/plugin/parser_json.rb +1 -1
- data/lib/fluent/plugin_helper/event_loop.rb +2 -2
- data/lib/fluent/plugin_helper/record_accessor.rb +1 -1
- data/lib/fluent/plugin_helper/server.rb +8 -0
- data/lib/fluent/plugin_helper/thread.rb +3 -3
- data/lib/fluent/plugin_id.rb +1 -1
- data/lib/fluent/supervisor.rb +1 -1
- data/lib/fluent/version.rb +1 -1
- data/templates/new_gem/test/helper.rb.erb +0 -1
- data/test/plugin/in_tail/test_position_file.rb +31 -1
- data/test/plugin/test_base.rb +1 -1
- data/test/plugin/test_buffer_chunk.rb +11 -0
- data/test/plugin/test_in_forward.rb +9 -9
- data/test/plugin/test_in_tail.rb +379 -0
- data/test/plugin/test_in_tcp.rb +74 -4
- data/test/plugin/test_in_udp.rb +28 -0
- data/test/plugin/test_in_unix.rb +2 -2
- data/test/plugin/test_multi_output.rb +1 -1
- data/test/plugin/test_out_exec_filter.rb +2 -2
- data/test/plugin/test_out_file.rb +2 -2
- data/test/plugin/test_output.rb +12 -12
- data/test/plugin/test_output_as_buffered.rb +44 -44
- data/test/plugin/test_output_as_buffered_compress.rb +32 -18
- data/test/plugin/test_output_as_buffered_retries.rb +1 -1
- data/test/plugin/test_output_as_buffered_secondary.rb +2 -2
- data/test/plugin_helper/test_child_process.rb +2 -2
- data/test/plugin_helper/test_server.rb +50 -1
- data/test/test_log.rb +38 -1
- data/test/test_msgpack_factory.rb +32 -0
- data/test/test_supervisor.rb +13 -0
- metadata +5 -4
data/test/plugin/test_in_tail.rb
CHANGED
@@ -2638,4 +2638,383 @@ 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
|
2641
3020
|
end
|
data/test/plugin/test_in_tcp.rb
CHANGED
@@ -218,16 +218,14 @@ class TcpInputTest < Test::Unit::TestCase
|
|
218
218
|
</client>
|
219
219
|
</security>
|
220
220
|
!)
|
221
|
-
d.run(
|
221
|
+
d.run(expect_records: 1, timeout: 2) do
|
222
222
|
create_tcp_socket('127.0.0.1', @port) do |sock|
|
223
223
|
sock.send("hello\n", 0)
|
224
224
|
end
|
225
225
|
end
|
226
226
|
|
227
|
-
assert_equal 1, d.
|
227
|
+
assert_equal 1, d.logs.count { |l| l =~ /anonymous client/ }
|
228
228
|
assert_equal 0, d.events.size
|
229
|
-
ensure
|
230
|
-
d.instance_shutdown if d&.instance
|
231
229
|
end
|
232
230
|
end
|
233
231
|
|
@@ -255,4 +253,76 @@ class TcpInputTest < Test::Unit::TestCase
|
|
255
253
|
assert_equal 'hello', event[2]['msg']
|
256
254
|
end
|
257
255
|
end
|
256
|
+
|
257
|
+
sub_test_case "message_length_limit" do
|
258
|
+
data("batch_emit", { extract: "" }, keep: true)
|
259
|
+
data("single_emit", { extract: "<extract>\ntag_key tag\n</extract>\n" }, keep: true)
|
260
|
+
test "drop records exceeding limit" do |data|
|
261
|
+
message_length_limit = 10
|
262
|
+
d = create_driver(base_config + %!
|
263
|
+
message_length_limit #{message_length_limit}
|
264
|
+
<parse>
|
265
|
+
@type none
|
266
|
+
</parse>
|
267
|
+
#{data[:extract]}
|
268
|
+
!)
|
269
|
+
d.run(expect_records: 2, timeout: 10) do
|
270
|
+
create_tcp_socket('127.0.0.1', @port) do |sock|
|
271
|
+
sock.send("a" * message_length_limit + "\n", 0)
|
272
|
+
sock.send("b" * (message_length_limit + 1) + "\n", 0)
|
273
|
+
sock.send("c" * (message_length_limit - 1) + "\n", 0)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
expected_records = [
|
278
|
+
"a" * message_length_limit,
|
279
|
+
"c" * (message_length_limit - 1)
|
280
|
+
]
|
281
|
+
actual_records = d.events.collect do |event|
|
282
|
+
event[2]["message"]
|
283
|
+
end
|
284
|
+
|
285
|
+
assert_equal expected_records, actual_records
|
286
|
+
end
|
287
|
+
|
288
|
+
test "clear buffer and discard the subsequent data until the next delimiter" do |data|
|
289
|
+
message_length_limit = 12
|
290
|
+
d = create_driver(base_config + %!
|
291
|
+
message_length_limit #{message_length_limit}
|
292
|
+
delimiter ";"
|
293
|
+
<parse>
|
294
|
+
@type json
|
295
|
+
</parse>
|
296
|
+
#{data[:extract]}
|
297
|
+
!)
|
298
|
+
d.run(expect_records: 1, timeout: 10) do
|
299
|
+
create_tcp_socket('127.0.0.1', @port) do |sock|
|
300
|
+
sock.send('{"message":', 0)
|
301
|
+
sock.send('"hello', 0)
|
302
|
+
sleep 1 # To make the server read data and clear the buffer here.
|
303
|
+
sock.send('world!"};', 0) # This subsequent data must be discarded so that a parsing failure doesn't occur.
|
304
|
+
sock.send('{"k":"v"};', 0) # This will succeed to parse.
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
logs = d.logs.collect do |log|
|
309
|
+
log.gsub(/\A\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [-+]\d{4} /, "")
|
310
|
+
end
|
311
|
+
actual_records = d.events.collect do |event|
|
312
|
+
event[2]
|
313
|
+
end
|
314
|
+
|
315
|
+
assert_equal(
|
316
|
+
{
|
317
|
+
# Asserting that '[warn]: pattern not matched message="world!\"}"' warning does not occur.
|
318
|
+
logs: ['[info]: The buffer size exceeds \'message_length_limit\', cleared: limit=12 size=17 head="{\"message\":\"hello"' + "\n"],
|
319
|
+
records: [{"k" => "v"}],
|
320
|
+
},
|
321
|
+
{
|
322
|
+
logs: logs[1..],
|
323
|
+
records: actual_records,
|
324
|
+
}
|
325
|
+
)
|
326
|
+
end
|
327
|
+
end
|
258
328
|
end
|
data/test/plugin/test_in_udp.rb
CHANGED
@@ -265,4 +265,32 @@ class UdpInputTest < Test::Unit::TestCase
|
|
265
265
|
end
|
266
266
|
end
|
267
267
|
end
|
268
|
+
|
269
|
+
test 'message_length_limit' do
|
270
|
+
message_length_limit = 32
|
271
|
+
d = create_driver(base_config + %!
|
272
|
+
format none
|
273
|
+
message_length_limit #{message_length_limit}
|
274
|
+
!)
|
275
|
+
d.run(expect_records: 3) do
|
276
|
+
create_udp_socket('127.0.0.1', @port) do |u|
|
277
|
+
3.times do |i|
|
278
|
+
u.send("#{i}" * 40 + "\n", 0)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
if Fluent.windows?
|
284
|
+
expected_records = []
|
285
|
+
else
|
286
|
+
expected_records = 3.times.collect do |i|
|
287
|
+
"#{i}" * message_length_limit
|
288
|
+
end
|
289
|
+
end
|
290
|
+
actual_records = d.events.collect do |event|
|
291
|
+
event[2]["message"]
|
292
|
+
end
|
293
|
+
|
294
|
+
assert_equal expected_records, actual_records
|
295
|
+
end
|
268
296
|
end
|
data/test/plugin/test_in_unix.rb
CHANGED
@@ -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.
|
177
|
+
assert_equal 1, logs.count { |line|
|
178
178
|
line =~ / \[warn\]: incoming data is broken: msg=#{data.inspect}/
|
179
|
-
}
|
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.
|
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.
|
601
|
-
assert_equal 2, logs.
|
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.
|
268
|
-
assert_equal 2, buffer_files.
|
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"})
|
data/test/plugin/test_output.rb
CHANGED
@@ -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.
|
451
|
-
assert_equal 1, validators.
|
452
|
-
assert_equal 1, validators.
|
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.
|
460
|
-
assert_equal 1, validators.
|
461
|
-
assert_equal 1, validators.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|