fluentd 1.11.3-x86-mingw32 → 1.12.2-x86-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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/.deepsource.toml +13 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  4. data/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. data/.github/workflows/linux-test.yaml +36 -0
  6. data/.github/workflows/macos-test.yaml +30 -0
  7. data/.github/workflows/stale-actions.yml +22 -0
  8. data/.github/workflows/windows-test.yaml +30 -0
  9. data/CHANGELOG.md +138 -0
  10. data/MAINTAINERS.md +5 -2
  11. data/README.md +2 -2
  12. data/bin/fluent-cap-ctl +7 -0
  13. data/bin/fluent-ctl +7 -0
  14. data/fluentd.gemspec +4 -3
  15. data/lib/fluent/capability.rb +87 -0
  16. data/lib/fluent/command/bundler_injection.rb +1 -1
  17. data/lib/fluent/command/ca_generate.rb +6 -3
  18. data/lib/fluent/command/cap_ctl.rb +174 -0
  19. data/lib/fluent/command/cat.rb +0 -1
  20. data/lib/fluent/command/ctl.rb +177 -0
  21. data/lib/fluent/command/fluentd.rb +4 -0
  22. data/lib/fluent/command/plugin_config_formatter.rb +18 -2
  23. data/lib/fluent/compat/parser.rb +2 -2
  24. data/lib/fluent/config/section.rb +2 -2
  25. data/lib/fluent/config/types.rb +2 -2
  26. data/lib/fluent/env.rb +4 -0
  27. data/lib/fluent/event.rb +3 -13
  28. data/lib/fluent/load.rb +0 -1
  29. data/lib/fluent/plugin.rb +5 -0
  30. data/lib/fluent/plugin/buffer.rb +2 -21
  31. data/lib/fluent/plugin/formatter.rb +24 -0
  32. data/lib/fluent/plugin/formatter_csv.rb +1 -1
  33. data/lib/fluent/plugin/formatter_hash.rb +3 -1
  34. data/lib/fluent/plugin/formatter_json.rb +3 -1
  35. data/lib/fluent/plugin/formatter_ltsv.rb +7 -5
  36. data/lib/fluent/plugin/formatter_out_file.rb +6 -4
  37. data/lib/fluent/plugin/formatter_single_value.rb +4 -2
  38. data/lib/fluent/plugin/formatter_tsv.rb +4 -2
  39. data/lib/fluent/plugin/in_http.rb +24 -3
  40. data/lib/fluent/plugin/in_monitor_agent.rb +1 -1
  41. data/lib/fluent/plugin/in_tail.rb +128 -41
  42. data/lib/fluent/plugin/in_tail/position_file.rb +39 -14
  43. data/lib/fluent/plugin/in_tcp.rb +1 -0
  44. data/lib/fluent/plugin/out_copy.rb +18 -5
  45. data/lib/fluent/plugin/out_exec_filter.rb +3 -3
  46. data/lib/fluent/plugin/out_forward.rb +61 -28
  47. data/lib/fluent/plugin/out_http.rb +29 -4
  48. data/lib/fluent/plugin/output.rb +14 -6
  49. data/lib/fluent/plugin/storage_local.rb +3 -3
  50. data/lib/fluent/plugin_helper/http_server/compat/server.rb +1 -1
  51. data/lib/fluent/plugin_helper/inject.rb +4 -1
  52. data/lib/fluent/plugin_helper/retry_state.rb +4 -0
  53. data/lib/fluent/supervisor.rb +153 -48
  54. data/lib/fluent/system_config.rb +2 -1
  55. data/lib/fluent/time.rb +58 -1
  56. data/lib/fluent/version.rb +1 -1
  57. data/lib/fluent/winsvc.rb +22 -4
  58. data/templates/plugin_config_formatter/param.md-table.erb +10 -0
  59. data/test/command/test_binlog_reader.rb +22 -6
  60. data/test/command/test_cap_ctl.rb +100 -0
  61. data/test/command/test_ctl.rb +57 -0
  62. data/test/command/test_fluentd.rb +38 -0
  63. data/test/command/test_plugin_config_formatter.rb +124 -2
  64. data/test/config/test_configurable.rb +1 -1
  65. data/test/plugin/in_tail/test_position_file.rb +46 -26
  66. data/test/plugin/out_forward/test_connection_manager.rb +6 -0
  67. data/test/plugin/test_filter_stdout.rb +6 -1
  68. data/test/plugin/test_formatter_hash.rb +6 -3
  69. data/test/plugin/test_formatter_json.rb +14 -4
  70. data/test/plugin/test_formatter_ltsv.rb +13 -5
  71. data/test/plugin/test_formatter_out_file.rb +35 -14
  72. data/test/plugin/test_formatter_single_value.rb +12 -6
  73. data/test/plugin/test_formatter_tsv.rb +12 -4
  74. data/test/plugin/test_in_exec.rb +1 -1
  75. data/test/plugin/test_in_http.rb +25 -0
  76. data/test/plugin/test_in_tail.rb +470 -32
  77. data/test/plugin/test_out_copy.rb +87 -0
  78. data/test/plugin/test_out_file.rb +23 -18
  79. data/test/plugin/test_out_forward.rb +74 -0
  80. data/test/plugin/test_out_http.rb +20 -1
  81. data/test/plugin/test_output.rb +12 -0
  82. data/test/plugin/test_parser_syslog.rb +2 -2
  83. data/test/plugin/test_sd_file.rb +1 -1
  84. data/test/plugin_helper/test_child_process.rb +5 -2
  85. data/test/plugin_helper/test_compat_parameters.rb +7 -2
  86. data/test/plugin_helper/test_http_server_helper.rb +3 -1
  87. data/test/plugin_helper/test_inject.rb +42 -0
  88. data/test/plugin_helper/test_server.rb +18 -5
  89. data/test/test_capability.rb +74 -0
  90. data/test/test_event.rb +16 -0
  91. data/test/test_formatter.rb +64 -10
  92. data/test/test_output.rb +6 -1
  93. data/test/test_supervisor.rb +150 -1
  94. data/test/test_time_parser.rb +109 -0
  95. metadata +61 -29
  96. data/.travis.yml +0 -57
  97. data/appveyor.yml +0 -28
@@ -37,13 +37,17 @@ class TSVFormatterTest < ::Test::Unit::TestCase
37
37
  assert_equal false, d.instance.add_newline
38
38
  end
39
39
 
40
- def test_format
40
+ data("newline (LF)" => ["lf", "\n"],
41
+ "newline (CRLF)" => ["crlf", "\r\n"])
42
+ def test_format(data)
43
+ newline_conf, newline = data
41
44
  d = create_driver(
42
45
  'keys' => 'message,greeting',
46
+ 'newline' => newline_conf
43
47
  )
44
48
  formatted = d.instance.format(tag, @time, record)
45
49
 
46
- assert_equal("awesome\thello\n", formatted)
50
+ assert_equal("awesome\thello#{newline}", formatted)
47
51
  end
48
52
 
49
53
  def test_format_without_newline
@@ -56,13 +60,17 @@ class TSVFormatterTest < ::Test::Unit::TestCase
56
60
  assert_equal("awesome\thello", formatted)
57
61
  end
58
62
 
59
- def test_format_with_customized_delimiters
63
+ data("newline (LF)" => ["lf", "\n"],
64
+ "newline (CRLF)" => ["crlf", "\r\n"])
65
+ def test_format_with_customized_delimiters(data)
66
+ newline_conf, newline = data
60
67
  d = create_driver(
61
68
  'keys' => 'message,greeting',
62
69
  'delimiter' => ',',
70
+ 'newline' => newline_conf,
63
71
  )
64
72
  formatted = d.instance.format(tag, @time, record)
65
73
 
66
- assert_equal("awesome,hello\n", formatted)
74
+ assert_equal("awesome,hello#{newline}", formatted)
67
75
  end
68
76
  end
@@ -255,7 +255,7 @@ EOC
255
255
  assert{ d.events.length > 0 }
256
256
  d.events.each do |event|
257
257
  assert_equal 'test', event[0]
258
- assert_match /LoadError/, event[2]['message']
258
+ assert_match(/LoadError/, event[2]['message'])
259
259
  end
260
260
  end
261
261
  end
@@ -46,6 +46,7 @@ class HttpInputTest < Test::Unit::TestCase
46
46
  assert_equal 10*1024*1024, d.instance.body_size_limit
47
47
  assert_equal 5, d.instance.keepalive_timeout
48
48
  assert_equal false, d.instance.add_http_headers
49
+ assert_equal false, d.instance.add_query_params
49
50
  end
50
51
 
51
52
  def test_time
@@ -907,6 +908,30 @@ class HttpInputTest < Test::Unit::TestCase
907
908
  assert_equal ["403", "403"], res_codes
908
909
  end
909
910
 
911
+ def test_add_query_params
912
+ d = create_driver(CONFIG + "add_query_params true")
913
+ assert_equal true, d.instance.add_query_params
914
+
915
+ time = event_time("2011-01-02 13:14:15 UTC")
916
+ time_i = time.to_i
917
+ events = [
918
+ ["tag1", time, {"a"=>1, "QUERY_A"=>"b"}],
919
+ ["tag2", time, {"a"=>2, "QUERY_A"=>"b"}],
920
+ ]
921
+ res_codes = []
922
+ res_bodies = []
923
+
924
+ d.run do
925
+ events.each do |tag, _t, record|
926
+ res = post("/#{tag}?a=b", {"json"=>record.to_json, "time"=>time_i.to_s})
927
+ res_codes << res.code
928
+ end
929
+ end
930
+ assert_equal ["200", "200"], res_codes
931
+ assert_equal [], res_bodies
932
+ assert_equal events, d.events
933
+ end
934
+
910
935
  $test_in_http_connection_object_ids = []
911
936
  $test_in_http_content_types = []
912
937
  $test_in_http_content_types_flag = false
@@ -6,6 +6,8 @@ require 'fluent/system_config'
6
6
  require 'net/http'
7
7
  require 'flexmock/test_unit'
8
8
  require 'timecop'
9
+ require 'tmpdir'
10
+ require 'securerandom'
9
11
 
10
12
  class TailInputTest < Test::Unit::TestCase
11
13
  include FlexMock::TestCase
@@ -17,29 +19,98 @@ class TailInputTest < Test::Unit::TestCase
17
19
 
18
20
  def teardown
19
21
  super
22
+ cleanup_directory(TMP_DIR)
20
23
  Fluent::Engine.stop
21
24
  end
22
25
 
23
26
  def cleanup_directory(path)
24
- FileUtils.rm_rf(path, secure: true)
25
- if File.exist?(path)
26
- FileUtils.remove_entry_secure(path, true)
27
+ unless Dir.exist?(path)
28
+ FileUtils.mkdir_p(path)
29
+ return
30
+ end
31
+
32
+ if Fluent.windows?
33
+ Dir.glob("*", base: path).each do |name|
34
+ begin
35
+ cleanup_file(File.join(path, name))
36
+ rescue
37
+ # expect test driver block release already owned file handle.
38
+ end
39
+ end
40
+ else
41
+ begin
42
+ FileUtils.rm_f(path, secure:true)
43
+ rescue ArgumentError
44
+ FileUtils.rm_f(path) # For Ruby 2.6 or before.
45
+ end
46
+ if File.exist?(path)
47
+ FileUtils.remove_entry_secure(path, true)
48
+ end
27
49
  end
28
50
  FileUtils.mkdir_p(path)
29
51
  end
30
52
 
53
+ def cleanup_file(path)
54
+ if Fluent.windows?
55
+ # On Windows, when the file or directory is removed and created
56
+ # frequently, there is a case that creating file or directory will
57
+ # fail. This situation is caused by pending file or directory
58
+ # deletion which is mentioned on win32 API document [1]
59
+ # As a workaround, execute rename and remove method.
60
+ #
61
+ # [1] https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#files
62
+ #
63
+ file = File.join(Dir.tmpdir, SecureRandom.hex(10))
64
+ begin
65
+ FileUtils.mv(path, file)
66
+ FileUtils.rm_rf(file, secure: true)
67
+ rescue ArgumentError
68
+ FileUtils.rm_rf(file) # For Ruby 2.6 or before.
69
+ end
70
+ if File.exist?(file)
71
+ # ensure files are closed for Windows, on which deleted files
72
+ # are still visible from filesystem
73
+ GC.start(full_mark: true, immediate_mark: true, immediate_sweep: true)
74
+ FileUtils.remove_entry_secure(file, true)
75
+ end
76
+ else
77
+ begin
78
+ FileUtils.rm_f(path, secure: true)
79
+ rescue ArgumentError
80
+ FileUtils.rm_f(path) # For Ruby 2.6 or before.
81
+ end
82
+ if File.exist?(path)
83
+ FileUtils.remove_entry_secure(path, true)
84
+ end
85
+ end
86
+ end
87
+
88
+ def create_target_info(path)
89
+ Fluent::Plugin::TailInput::TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
90
+ end
91
+
31
92
  TMP_DIR = File.dirname(__FILE__) + "/../tmp/tail#{ENV['TEST_ENV_NUMBER']}"
32
93
 
33
94
  CONFIG = config_element("ROOT", "", {
34
95
  "path" => "#{TMP_DIR}/tail.txt",
35
96
  "tag" => "t1",
36
- "rotate_wait" => "2s"
97
+ "rotate_wait" => "2s",
98
+ "refresh_interval" => "1s"
37
99
  })
38
100
  COMMON_CONFIG = CONFIG + config_element("", "", { "pos_file" => "#{TMP_DIR}/tail.pos" })
39
101
  CONFIG_READ_FROM_HEAD = config_element("", "", { "read_from_head" => true })
40
102
  CONFIG_ENABLE_WATCH_TIMER = config_element("", "", { "enable_watch_timer" => false })
41
103
  CONFIG_DISABLE_STAT_WATCHER = config_element("", "", { "enable_stat_watcher" => false })
42
104
  CONFIG_OPEN_ON_EVERY_UPDATE = config_element("", "", { "open_on_every_update" => true })
105
+ COMMON_FOLLOW_INODE_CONFIG = config_element("ROOT", "", {
106
+ "path" => "#{TMP_DIR}/tail.txt*",
107
+ "pos_file" => "#{TMP_DIR}/tail.pos",
108
+ "tag" => "t1",
109
+ "refresh_interval" => "1s",
110
+ "read_from_head" => "true",
111
+ "format" => "none",
112
+ "follow_inodes" => "true"
113
+ })
43
114
  SINGLE_LINE_CONFIG = config_element("", "", { "format" => "/(?<message>.*)/" })
44
115
  PARSE_SINGLE_LINE_CONFIG = config_element("", "", {}, [config_element("parse", "", { "@type" => "/(?<message>.*)/" })])
45
116
  MULTILINE_CONFIG = config_element(
@@ -86,6 +157,9 @@ class TailInputTest < Test::Unit::TestCase
86
157
  assert_equal "#{TMP_DIR}/tail.pos", d.instance.pos_file
87
158
  assert_equal 1000, d.instance.read_lines_limit
88
159
  assert_equal false, d.instance.ignore_repeated_permission_error
160
+ assert_nothing_raised do
161
+ d.instance.have_read_capability?
162
+ end
89
163
  end
90
164
 
91
165
  data("empty" => config_element,
@@ -115,6 +189,12 @@ class TailInputTest < Test::Unit::TestCase
115
189
  end
116
190
  end
117
191
 
192
+ test "follow_inodes w/o pos file" do
193
+ assert_raise(Fluent::ConfigError) do
194
+ create_driver(CONFIG + config_element('', '', {'follow_inodes' => 'true'}))
195
+ end
196
+ end
197
+
118
198
  test "both enable_watch_timer and enable_stat_watcher are false" do
119
199
  assert_raise(Fluent::ConfigError) do
120
200
  create_driver(CONFIG_ENABLE_WATCH_TIMER + CONFIG_DISABLE_STAT_WATCHER + PARSE_SINGLE_LINE_CONFIG)
@@ -229,7 +309,7 @@ class TailInputTest < Test::Unit::TestCase
229
309
  d = create_driver(config)
230
310
  msg = 'test' * 2000 # in_tail reads 8192 bytes at once.
231
311
 
232
- d.run(expect_emits: num_events, timeout: 1) do
312
+ d.run(expect_emits: num_events, timeout: 2) do
233
313
  File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
234
314
  f.puts msg
235
315
  f.puts msg
@@ -999,6 +1079,7 @@ class TailInputTest < Test::Unit::TestCase
999
1079
  # * path test
1000
1080
  # TODO: Clean up tests
1001
1081
  EX_ROTATE_WAIT = 0
1082
+ EX_FOLLOW_INODES = false
1002
1083
 
1003
1084
  EX_CONFIG = config_element("", "", {
1004
1085
  "tag" => "tail",
@@ -1008,38 +1089,43 @@ class TailInputTest < Test::Unit::TestCase
1008
1089
  "read_from_head" => true,
1009
1090
  "refresh_interval" => 30,
1010
1091
  "rotate_wait" => "#{EX_ROTATE_WAIT}s",
1092
+ "follow_inodes" => "#{EX_FOLLOW_INODES}",
1011
1093
  })
1012
- EX_PATHS = [
1013
- 'test/plugin/data/2010/01/20100102-030405.log',
1014
- 'test/plugin/data/log/foo/bar.log',
1015
- 'test/plugin/data/log/test.log'
1016
- ]
1017
-
1018
1094
  def test_expand_paths
1095
+ ex_paths = [
1096
+ create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
1097
+ create_target_info('test/plugin/data/log/foo/bar.log'),
1098
+ create_target_info('test/plugin/data/log/test.log')
1099
+ ]
1019
1100
  plugin = create_driver(EX_CONFIG, false).instance
1020
1101
  flexstub(Time) do |timeclass|
1021
1102
  timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 2, 3, 4, 5))
1022
- assert_equal EX_PATHS, plugin.expand_paths.sort
1103
+ assert_equal ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1023
1104
  end
1024
1105
 
1025
1106
  # Test exclusion
1026
- exclude_config = EX_CONFIG + config_element("", "", { "exclude_path" => %Q(["#{EX_PATHS.last}"]) })
1107
+ exclude_config = EX_CONFIG + config_element("", "", { "exclude_path" => %Q(["#{ex_paths.last.path}"]) })
1027
1108
  plugin = create_driver(exclude_config, false).instance
1028
- assert_equal EX_PATHS - [EX_PATHS.last], plugin.expand_paths.sort
1109
+ assert_equal ex_paths - [ex_paths.last], plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1029
1110
  end
1030
1111
 
1031
1112
  def test_expand_paths_with_duplicate_configuration
1032
1113
  expanded_paths = [
1033
- 'test/plugin/data/log/foo/bar.log',
1034
- 'test/plugin/data/log/test.log'
1114
+ create_target_info('test/plugin/data/log/foo/bar.log'),
1115
+ create_target_info('test/plugin/data/log/test.log')
1035
1116
  ]
1036
1117
  duplicate_config = EX_CONFIG.dup
1037
1118
  duplicate_config["path"]="test/plugin/data/log/**/*.log, test/plugin/data/log/**/*.log"
1038
1119
  plugin = create_driver(EX_CONFIG, false).instance
1039
- assert_equal expanded_paths, plugin.expand_paths.sort
1120
+ assert_equal expanded_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1040
1121
  end
1041
1122
 
1042
1123
  def test_expand_paths_with_timezone
1124
+ ex_paths = [
1125
+ create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
1126
+ create_target_info('test/plugin/data/log/foo/bar.log'),
1127
+ create_target_info('test/plugin/data/log/test.log')
1128
+ ]
1043
1129
  ['Asia/Taipei', '+08'].each do |tz_type|
1044
1130
  taipei_config = EX_CONFIG + config_element("", "", {"path_timezone" => tz_type})
1045
1131
  plugin = create_driver(taipei_config, false).instance
@@ -1053,8 +1139,8 @@ class TailInputTest < Test::Unit::TestCase
1053
1139
  # env : 2010-01-01 19:04:05 (UTC), tail path : 2010-01-02 03:04:05 (Asia/Taipei)
1054
1140
  timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 1, 19, 4, 5))
1055
1141
 
1056
- assert_equal EX_PATHS, plugin.expand_paths.sort
1057
- assert_equal EX_PATHS - [EX_PATHS.first], exclude_plugin.expand_paths.sort
1142
+ assert_equal ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1143
+ assert_equal ex_paths - [ex_paths.first], exclude_plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1058
1144
  end
1059
1145
  end
1060
1146
  end
@@ -1062,10 +1148,10 @@ class TailInputTest < Test::Unit::TestCase
1062
1148
 
1063
1149
  def test_log_file_without_extension
1064
1150
  expected_files = [
1065
- 'test/plugin/data/log/bar',
1066
- 'test/plugin/data/log/foo/bar.log',
1067
- 'test/plugin/data/log/foo/bar2',
1068
- 'test/plugin/data/log/test.log'
1151
+ create_target_info('test/plugin/data/log/bar'),
1152
+ create_target_info('test/plugin/data/log/foo/bar.log'),
1153
+ create_target_info('test/plugin/data/log/foo/bar2'),
1154
+ create_target_info('test/plugin/data/log/test.log')
1069
1155
  ]
1070
1156
 
1071
1157
  config = config_element("", "", {
@@ -1076,7 +1162,7 @@ class TailInputTest < Test::Unit::TestCase
1076
1162
  })
1077
1163
 
1078
1164
  plugin = create_driver(config, false).instance
1079
- assert_equal expected_files, plugin.expand_paths.sort
1165
+ assert_equal expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1080
1166
  end
1081
1167
 
1082
1168
  def test_unwatched_files_should_be_removed
@@ -1112,6 +1198,49 @@ class TailInputTest < Test::Unit::TestCase
1112
1198
  end
1113
1199
  end
1114
1200
 
1201
+ sub_test_case "path w/ Linux capability" do
1202
+ def capability_enabled?
1203
+ if Fluent.linux?
1204
+ begin
1205
+ require 'capng'
1206
+ true
1207
+ rescue LoadError
1208
+ false
1209
+ end
1210
+ else
1211
+ false
1212
+ end
1213
+ end
1214
+
1215
+ setup do
1216
+ omit "This environment is not enabled Linux capability handling feature" unless capability_enabled?
1217
+
1218
+ @capng = CapNG.new(:current_process)
1219
+ flexstub(Fluent::Capability) do |klass|
1220
+ klass.should_receive(:new).with(:current_process).and_return(@capng)
1221
+ end
1222
+ end
1223
+
1224
+ data("dac_read_search" => [:dac_read_search, true, 1],
1225
+ "dac_override" => [:dac_override, true, 1],
1226
+ "chown" => [:chown, false, 0],
1227
+ )
1228
+ test "with partially elevated privileges" do |data|
1229
+ cap, result, readable_paths = data
1230
+ @capng.update(:add, :effective, cap)
1231
+
1232
+ d = create_driver(
1233
+ config_element("ROOT", "", {
1234
+ "path" => "/var/log/ker*.log", # Use /var/log/kern.log
1235
+ "tag" => "t1",
1236
+ "rotate_wait" => "2s"
1237
+ }) + PARSE_SINGLE_LINE_CONFIG, false)
1238
+
1239
+ assert_equal readable_paths, d.instance.expand_paths.length
1240
+ assert_equal result, d.instance.have_read_capability?
1241
+ end
1242
+ end
1243
+
1115
1244
  def test_pos_file_dir_creation
1116
1245
  config = config_element("", "", {
1117
1246
  "tag" => "tail",
@@ -1159,25 +1288,35 @@ class TailInputTest < Test::Unit::TestCase
1159
1288
  end
1160
1289
 
1161
1290
  def test_z_refresh_watchers
1291
+ ex_paths = [
1292
+ create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
1293
+ create_target_info('test/plugin/data/log/foo/bar.log'),
1294
+ create_target_info('test/plugin/data/log/test.log'),
1295
+ ]
1162
1296
  plugin = create_driver(EX_CONFIG, false).instance
1163
1297
  sio = StringIO.new
1164
1298
  plugin.instance_eval do
1165
- @pf = Fluent::Plugin::TailInput::PositionFile.load(sio, logger: $log)
1299
+ @pf = Fluent::Plugin::TailInput::PositionFile.load(sio, EX_FOLLOW_INODES, {}, logger: $log)
1166
1300
  @loop = Coolio::Loop.new
1167
1301
  end
1168
1302
 
1169
1303
  Timecop.freeze(2010, 1, 2, 3, 4, 5) do
1170
- EX_PATHS.each do |path|
1171
- mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(path, anything, anything, true, anything, nil, anything).once
1304
+ ex_paths.each do |target_info|
1305
+ mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything).once
1172
1306
  end
1173
1307
 
1174
1308
  plugin.refresh_watchers
1175
1309
  end
1176
1310
 
1177
- mock.proxy(plugin).detach_watcher_after_rotate_wait(plugin.instance_variable_get(:@tails)['test/plugin/data/2010/01/20100102-030405.log'])
1311
+ path = 'test/plugin/data/2010/01/20100102-030405.log'
1312
+ target_info = Fluent::Plugin::TailInput::TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
1313
+ mock.proxy(plugin).detach_watcher_after_rotate_wait(plugin.instance_variable_get(:@tails)[target_info], target_info.ino)
1178
1314
 
1179
1315
  Timecop.freeze(2010, 1, 2, 3, 4, 6) do
1180
- mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new('test/plugin/data/2010/01/20100102-030406.log', anything, anything, true, anything, nil, anything).once
1316
+ path = "test/plugin/data/2010/01/20100102-030406.log"
1317
+ inode = Fluent::FileWrapper.stat(path).ino
1318
+ target_info = Fluent::Plugin::TailInput::TargetInfo.new(path, inode)
1319
+ mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything).once
1181
1320
  plugin.refresh_watchers
1182
1321
 
1183
1322
  flexstub(Fluent::Plugin::TailInput::TailWatcher) do |watcherclass|
@@ -1323,6 +1462,305 @@ class TailInputTest < Test::Unit::TestCase
1323
1462
  end
1324
1463
  end
1325
1464
 
1465
+ sub_test_case 'inode_processing' do
1466
+ def test_should_delete_file_pos_entry_for_non_existing_file_with_follow_inodes
1467
+ config = COMMON_FOLLOW_INODE_CONFIG
1468
+
1469
+ path = "#{TMP_DIR}/tail.txt"
1470
+ ino = 1
1471
+ pos = 1234
1472
+ File.open("#{TMP_DIR}/tail.pos", "wb") {|f|
1473
+ f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
1474
+ }
1475
+
1476
+ d = create_driver(config, false)
1477
+ d.run
1478
+
1479
+ pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
1480
+ pos_file.pos = 0
1481
+
1482
+ assert_raise(EOFError) do
1483
+ pos_file.readline
1484
+ end
1485
+ end
1486
+
1487
+ def test_should_write_latest_offset_after_rotate_wait
1488
+ config = COMMON_FOLLOW_INODE_CONFIG
1489
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1490
+ f.puts "test1"
1491
+ f.puts "test2"
1492
+ }
1493
+
1494
+ d = create_driver(config, false)
1495
+ d.run(expect_emits: 2, shutdown: false) do
1496
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1497
+ FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt" + "1")
1498
+ sleep 1
1499
+ File.open("#{TMP_DIR}/tail.txt" + "1", "ab") {|f| f.puts "test4\n"}
1500
+ end
1501
+
1502
+ pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
1503
+ pos_file.pos = 0
1504
+ line_parts = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
1505
+ waiting(5) {
1506
+ while line_parts[2].to_i(16) != 24
1507
+ sleep(0.1)
1508
+ pos_file.pos = 0
1509
+ line_parts = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
1510
+ end
1511
+ }
1512
+ assert_equal(24, line_parts[2].to_i(16))
1513
+ d.instance_shutdown
1514
+ end
1515
+
1516
+ def test_should_keep_and_update_existing_file_pos_entry_for_deleted_file_when_new_file_with_same_name_created
1517
+ config = config_element("", "", {"format" => "none"})
1518
+
1519
+ path = "#{TMP_DIR}/tail.txt"
1520
+ ino = 1
1521
+ pos = 1234
1522
+ File.open("#{TMP_DIR}/tail.pos", "wb") {|f|
1523
+ f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
1524
+ }
1525
+
1526
+ d = create_driver(config)
1527
+ d.run(shutdown: false)
1528
+
1529
+ pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
1530
+ pos_file.pos = 0
1531
+
1532
+ path_pos_ino = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
1533
+ assert_equal(path, path_pos_ino[1])
1534
+ assert_equal(pos, path_pos_ino[2].to_i(16))
1535
+ assert_equal(ino, path_pos_ino[3].to_i(16))
1536
+
1537
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1538
+ f.puts "test1"
1539
+ f.puts "test2"
1540
+ }
1541
+ Timecop.travel(Time.now + 10) do
1542
+ sleep 5
1543
+ pos_file.pos = 0
1544
+ tuple = create_target_info("#{TMP_DIR}/tail.txt")
1545
+ path_pos_ino = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
1546
+ assert_equal(tuple.path, path_pos_ino[1])
1547
+ assert_equal(12, path_pos_ino[2].to_i(16))
1548
+ assert_equal(tuple.ino, path_pos_ino[3].to_i(16))
1549
+ end
1550
+ d.instance_shutdown
1551
+ end
1552
+
1553
+ def test_should_mark_file_unwatched_after_limit_recently_modified_and_rotate_wait
1554
+ config = config_element("ROOT", "", {
1555
+ "path" => "#{TMP_DIR}/tail.txt*",
1556
+ "pos_file" => "#{TMP_DIR}/tail.pos",
1557
+ "tag" => "t1",
1558
+ "rotate_wait" => "1s",
1559
+ "refresh_interval" => "1s",
1560
+ "limit_recently_modified" => "1s",
1561
+ "read_from_head" => "true",
1562
+ "format" => "none",
1563
+ "follow_inodes" => "true",
1564
+ })
1565
+
1566
+ d = create_driver(config, false)
1567
+
1568
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1569
+ f.puts "test1"
1570
+ f.puts "test2"
1571
+ }
1572
+ target_info = create_target_info("#{TMP_DIR}/tail.txt")
1573
+
1574
+ d.run(expect_emits: 1, shutdown: false) do
1575
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1576
+ end
1577
+
1578
+
1579
+ Timecop.travel(Time.now + 10) do
1580
+ waiting(5) {
1581
+ # @pos will be reset as 0 when UNWATCHED_POSITION is specified.
1582
+ sleep 0.1 until d.instance.instance_variable_get(:@pf)[target_info].read_pos == 0
1583
+ }
1584
+ end
1585
+
1586
+ assert_equal(0, d.instance.instance_variable_get(:@pf)[target_info].read_pos)
1587
+
1588
+ d.instance_shutdown
1589
+ end
1590
+
1591
+ def test_should_read_from_head_on_file_renaming_with_star_in_pattern
1592
+ config = config_element("ROOT", "", {
1593
+ "path" => "#{TMP_DIR}/tail.txt*",
1594
+ "pos_file" => "#{TMP_DIR}/tail.pos",
1595
+ "tag" => "t1",
1596
+ "rotate_wait" => "10s",
1597
+ "refresh_interval" => "1s",
1598
+ "limit_recently_modified" => "60s",
1599
+ "read_from_head" => "true",
1600
+ "format" => "none",
1601
+ "follow_inodes" => "true"
1602
+ })
1603
+
1604
+ d = create_driver(config, false)
1605
+
1606
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1607
+ f.puts "test1"
1608
+ f.puts "test2"
1609
+ }
1610
+
1611
+ d.run(expect_emits: 2, shutdown: false) do
1612
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1613
+ FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt1")
1614
+ end
1615
+
1616
+ events = d.events
1617
+ assert_equal(3, events.length)
1618
+ d.instance_shutdown
1619
+ end
1620
+
1621
+ def test_should_not_read_from_head_on_rotation_when_watching_inodes
1622
+ config = COMMON_FOLLOW_INODE_CONFIG
1623
+
1624
+ d = create_driver(config, false)
1625
+
1626
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1627
+ f.puts "test1"
1628
+ f.puts "test2"
1629
+ }
1630
+
1631
+ d.run(expect_emits: 1, shutdown: false) do
1632
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1633
+ end
1634
+
1635
+ FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt1")
1636
+ Timecop.travel(Time.now + 10) do
1637
+ sleep 2
1638
+ events = d.events
1639
+ assert_equal(3, events.length)
1640
+ end
1641
+
1642
+ d.instance_shutdown
1643
+ end
1644
+
1645
+ def test_should_mark_file_unwatched_if_same_name_file_created_with_different_inode
1646
+ config = COMMON_FOLLOW_INODE_CONFIG
1647
+
1648
+ d = create_driver(config, false)
1649
+
1650
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1651
+ f.puts "test1"
1652
+ f.puts "test2"
1653
+ }
1654
+ target_info = create_target_info("#{TMP_DIR}/tail.txt")
1655
+
1656
+ d.run(expect_emits: 2, shutdown: false) do
1657
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1658
+ cleanup_file("#{TMP_DIR}/tail.txt")
1659
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f| f.puts "test4\n"}
1660
+ end
1661
+
1662
+ new_target_info = create_target_info("#{TMP_DIR}/tail.txt")
1663
+
1664
+ pos_file = d.instance.instance_variable_get(:@pf)
1665
+
1666
+ waiting(10) {
1667
+ # @pos will be reset as 0 when UNWATCHED_POSITION is specified.
1668
+ sleep 0.1 until pos_file[target_info].read_pos == 0
1669
+ }
1670
+ new_position = pos_file[new_target_info].read_pos
1671
+ assert_equal(6, new_position)
1672
+
1673
+ d.instance_shutdown
1674
+ end
1675
+
1676
+ def test_should_close_watcher_after_rotate_wait
1677
+ now = Time.now
1678
+ config = COMMON_FOLLOW_INODE_CONFIG + config_element('', '', {"rotate_wait" => "1s", "limit_recently_modified" => "1s"})
1679
+
1680
+ d = create_driver(config, false)
1681
+
1682
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1683
+ f.puts "test1"
1684
+ f.puts "test2"
1685
+ }
1686
+ target_info = create_target_info("#{TMP_DIR}/tail.txt")
1687
+ mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, true, anything, nil, anything).once
1688
+ d.run(shutdown: false)
1689
+ assert d.instance.instance_variable_get(:@tails)[target_info]
1690
+
1691
+ Timecop.travel(now + 10) do
1692
+ d.instance.instance_eval do
1693
+ sleep 0.1 until @tails[target_info] == nil
1694
+ end
1695
+ assert_nil d.instance.instance_variable_get(:@tails)[target_info]
1696
+ end
1697
+ d.instance_shutdown
1698
+ end
1699
+
1700
+ def test_should_create_new_watcher_for_new_file_with_same_name
1701
+ now = Time.now
1702
+ config = COMMON_FOLLOW_INODE_CONFIG + config_element('', '', {"limit_recently_modified" => "2s"})
1703
+
1704
+ d = create_driver(config, false)
1705
+
1706
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1707
+ f.puts "test1"
1708
+ f.puts "test2"
1709
+ }
1710
+ path_ino = create_target_info("#{TMP_DIR}/tail.txt")
1711
+
1712
+ d.run(expect_emits: 1, shutdown: false) do
1713
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1714
+ end
1715
+
1716
+ cleanup_file("#{TMP_DIR}/tail.txt")
1717
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1718
+ f.puts "test3"
1719
+ f.puts "test4"
1720
+ }
1721
+ new_path_ino = create_target_info("#{TMP_DIR}/tail.txt")
1722
+
1723
+ Timecop.travel(now + 10) do
1724
+ sleep 3
1725
+ d.instance.instance_eval do
1726
+ @tails[path_ino] == nil
1727
+ @tails[new_path_ino] != nil
1728
+ end
1729
+ end
1730
+
1731
+ events = d.events
1732
+
1733
+ assert_equal(5, events.length)
1734
+
1735
+ d.instance_shutdown
1736
+ end
1737
+
1738
+ def test_truncate_file_with_follow_inodes
1739
+ config = COMMON_FOLLOW_INODE_CONFIG
1740
+
1741
+ d = create_driver(config, false)
1742
+
1743
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1744
+ f.puts "test1"
1745
+ f.puts "test2"
1746
+ }
1747
+
1748
+ d.run(expect_emits: 3, shutdown: false) do
1749
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1750
+ sleep 2
1751
+ File.open("#{TMP_DIR}/tail.txt", "w+b") {|f| f.puts "test4\n"}
1752
+ end
1753
+
1754
+ events = d.events
1755
+ assert_equal(4, events.length)
1756
+ assert_equal({"message" => "test1"}, events[0][2])
1757
+ assert_equal({"message" => "test2"}, events[1][2])
1758
+ assert_equal({"message" => "test3"}, events[2][2])
1759
+ assert_equal({"message" => "test4"}, events[3][2])
1760
+ d.instance_shutdown
1761
+ end
1762
+ end
1763
+
1326
1764
  sub_test_case "tail_path" do
1327
1765
  def test_tail_path_with_singleline
1328
1766
  File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
@@ -1452,13 +1890,13 @@ class TailInputTest < Test::Unit::TestCase
1452
1890
  })
1453
1891
 
1454
1892
  expected_files = [
1455
- "#{TMP_DIR}/tail_watch1.txt",
1456
- "#{TMP_DIR}/tail_watch2.txt"
1893
+ create_target_info("#{TMP_DIR}/tail_watch1.txt"),
1894
+ create_target_info("#{TMP_DIR}/tail_watch2.txt")
1457
1895
  ]
1458
1896
 
1459
1897
  Timecop.freeze(now) do
1460
1898
  plugin = create_driver(config, false).instance
1461
- assert_equal expected_files, plugin.expand_paths.sort
1899
+ assert_equal expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1462
1900
  end
1463
1901
  end
1464
1902