fluentd 1.13.3 → 1.16.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/{bug_report.yaml → bug_report.yml} +2 -0
  3. data/.github/ISSUE_TEMPLATE/config.yml +2 -2
  4. data/.github/ISSUE_TEMPLATE/{feature_request.yaml → feature_request.yml} +1 -0
  5. data/.github/workflows/stale-actions.yml +11 -9
  6. data/.github/workflows/test.yml +32 -0
  7. data/CHANGELOG.md +490 -10
  8. data/CONTRIBUTING.md +2 -2
  9. data/MAINTAINERS.md +7 -5
  10. data/README.md +3 -23
  11. data/Rakefile +1 -1
  12. data/SECURITY.md +14 -0
  13. data/fluentd.gemspec +7 -8
  14. data/lib/fluent/command/cat.rb +13 -3
  15. data/lib/fluent/command/ctl.rb +6 -3
  16. data/lib/fluent/command/fluentd.rb +73 -65
  17. data/lib/fluent/command/plugin_config_formatter.rb +1 -1
  18. data/lib/fluent/compat/output.rb +9 -6
  19. data/lib/fluent/config/dsl.rb +1 -1
  20. data/lib/fluent/config/error.rb +12 -0
  21. data/lib/fluent/config/literal_parser.rb +2 -2
  22. data/lib/fluent/config/parser.rb +1 -1
  23. data/lib/fluent/config/v1_parser.rb +3 -3
  24. data/lib/fluent/config/yaml_parser/fluent_value.rb +47 -0
  25. data/lib/fluent/config/yaml_parser/loader.rb +108 -0
  26. data/lib/fluent/config/yaml_parser/parser.rb +166 -0
  27. data/lib/fluent/config/yaml_parser/section_builder.rb +107 -0
  28. data/lib/fluent/config/yaml_parser.rb +56 -0
  29. data/lib/fluent/config.rb +14 -1
  30. data/lib/fluent/counter/server.rb +1 -1
  31. data/lib/fluent/counter/validator.rb +3 -3
  32. data/lib/fluent/daemon.rb +2 -4
  33. data/lib/fluent/engine.rb +1 -1
  34. data/lib/fluent/env.rb +4 -0
  35. data/lib/fluent/error.rb +3 -0
  36. data/lib/fluent/event.rb +8 -4
  37. data/lib/fluent/event_router.rb +47 -2
  38. data/lib/fluent/file_wrapper.rb +137 -0
  39. data/lib/fluent/log/console_adapter.rb +66 -0
  40. data/lib/fluent/log.rb +44 -5
  41. data/lib/fluent/match.rb +1 -1
  42. data/lib/fluent/msgpack_factory.rb +6 -1
  43. data/lib/fluent/oj_options.rb +1 -2
  44. data/lib/fluent/plugin/bare_output.rb +49 -8
  45. data/lib/fluent/plugin/base.rb +26 -9
  46. data/lib/fluent/plugin/buf_file.rb +34 -5
  47. data/lib/fluent/plugin/buf_file_single.rb +32 -3
  48. data/lib/fluent/plugin/buffer/file_chunk.rb +1 -1
  49. data/lib/fluent/plugin/buffer.rb +216 -70
  50. data/lib/fluent/plugin/filter.rb +35 -1
  51. data/lib/fluent/plugin/filter_record_transformer.rb +1 -1
  52. data/lib/fluent/plugin/in_forward.rb +2 -2
  53. data/lib/fluent/plugin/in_http.rb +39 -10
  54. data/lib/fluent/plugin/in_monitor_agent.rb +4 -2
  55. data/lib/fluent/plugin/in_sample.rb +1 -1
  56. data/lib/fluent/plugin/in_syslog.rb +13 -1
  57. data/lib/fluent/plugin/in_tail/group_watch.rb +204 -0
  58. data/lib/fluent/plugin/in_tail/position_file.rb +33 -33
  59. data/lib/fluent/plugin/in_tail.rb +216 -84
  60. data/lib/fluent/plugin/in_tcp.rb +47 -2
  61. data/lib/fluent/plugin/input.rb +39 -1
  62. data/lib/fluent/plugin/metrics.rb +119 -0
  63. data/lib/fluent/plugin/metrics_local.rb +96 -0
  64. data/lib/fluent/plugin/multi_output.rb +43 -6
  65. data/lib/fluent/plugin/out_copy.rb +1 -1
  66. data/lib/fluent/plugin/out_exec_filter.rb +2 -2
  67. data/lib/fluent/plugin/out_file.rb +20 -2
  68. data/lib/fluent/plugin/out_forward/ack_handler.rb +19 -4
  69. data/lib/fluent/plugin/out_forward/socket_cache.rb +2 -0
  70. data/lib/fluent/plugin/out_forward.rb +17 -9
  71. data/lib/fluent/plugin/out_secondary_file.rb +39 -22
  72. data/lib/fluent/plugin/output.rb +167 -78
  73. data/lib/fluent/plugin/parser.rb +3 -4
  74. data/lib/fluent/plugin/parser_apache2.rb +1 -1
  75. data/lib/fluent/plugin/parser_json.rb +1 -1
  76. data/lib/fluent/plugin/parser_syslog.rb +1 -1
  77. data/lib/fluent/plugin/storage_local.rb +3 -5
  78. data/lib/fluent/plugin.rb +10 -1
  79. data/lib/fluent/plugin_helper/child_process.rb +3 -0
  80. data/lib/fluent/plugin_helper/event_emitter.rb +8 -1
  81. data/lib/fluent/plugin_helper/event_loop.rb +2 -2
  82. data/lib/fluent/plugin_helper/http_server/server.rb +2 -1
  83. data/lib/fluent/plugin_helper/metrics.rb +129 -0
  84. data/lib/fluent/plugin_helper/record_accessor.rb +1 -1
  85. data/lib/fluent/plugin_helper/retry_state.rb +14 -4
  86. data/lib/fluent/plugin_helper/server.rb +35 -6
  87. data/lib/fluent/plugin_helper/service_discovery.rb +2 -2
  88. data/lib/fluent/plugin_helper/socket.rb +13 -2
  89. data/lib/fluent/plugin_helper/thread.rb +3 -3
  90. data/lib/fluent/plugin_helper.rb +1 -0
  91. data/lib/fluent/plugin_id.rb +3 -2
  92. data/lib/fluent/registry.rb +2 -1
  93. data/lib/fluent/root_agent.rb +6 -0
  94. data/lib/fluent/rpc.rb +4 -3
  95. data/lib/fluent/supervisor.rb +283 -259
  96. data/lib/fluent/system_config.rb +13 -3
  97. data/lib/fluent/test/driver/base.rb +11 -5
  98. data/lib/fluent/test/driver/filter.rb +4 -0
  99. data/lib/fluent/test/startup_shutdown.rb +6 -8
  100. data/lib/fluent/time.rb +21 -20
  101. data/lib/fluent/version.rb +1 -1
  102. data/lib/fluent/win32api.rb +38 -0
  103. data/lib/fluent/winsvc.rb +5 -8
  104. data/templates/new_gem/test/helper.rb.erb +0 -1
  105. data/test/command/test_cat.rb +31 -2
  106. data/test/command/test_ctl.rb +1 -2
  107. data/test/command/test_fluentd.rb +209 -24
  108. data/test/command/test_plugin_config_formatter.rb +0 -1
  109. data/test/compat/test_parser.rb +6 -6
  110. data/test/config/test_system_config.rb +13 -11
  111. data/test/config/test_types.rb +1 -1
  112. data/test/log/test_console_adapter.rb +110 -0
  113. data/test/plugin/in_tail/test_io_handler.rb +26 -8
  114. data/test/plugin/in_tail/test_position_file.rb +48 -59
  115. data/test/plugin/out_forward/test_ack_handler.rb +39 -0
  116. data/test/plugin/out_forward/test_socket_cache.rb +26 -1
  117. data/test/plugin/test_bare_output.rb +14 -1
  118. data/test/plugin/test_base.rb +133 -1
  119. data/test/plugin/test_buf_file.rb +62 -23
  120. data/test/plugin/test_buf_file_single.rb +65 -0
  121. data/test/plugin/test_buffer.rb +267 -3
  122. data/test/plugin/test_buffer_chunk.rb +11 -0
  123. data/test/plugin/test_filter.rb +12 -1
  124. data/test/plugin/test_filter_parser.rb +1 -1
  125. data/test/plugin/test_filter_stdout.rb +2 -2
  126. data/test/plugin/test_in_forward.rb +9 -11
  127. data/test/plugin/test_in_http.rb +65 -3
  128. data/test/plugin/test_in_monitor_agent.rb +216 -11
  129. data/test/plugin/test_in_object_space.rb +9 -3
  130. data/test/plugin/test_in_syslog.rb +35 -0
  131. data/test/plugin/test_in_tail.rb +1393 -385
  132. data/test/plugin/test_in_tcp.rb +87 -2
  133. data/test/plugin/test_in_udp.rb +28 -0
  134. data/test/plugin/test_in_unix.rb +2 -2
  135. data/test/plugin/test_input.rb +12 -1
  136. data/test/plugin/test_metrics.rb +294 -0
  137. data/test/plugin/test_metrics_local.rb +96 -0
  138. data/test/plugin/test_multi_output.rb +25 -1
  139. data/test/plugin/test_out_exec.rb +6 -4
  140. data/test/plugin/test_out_exec_filter.rb +6 -2
  141. data/test/plugin/test_out_file.rb +34 -17
  142. data/test/plugin/test_out_forward.rb +78 -77
  143. data/test/plugin/test_out_http.rb +1 -0
  144. data/test/plugin/test_out_stdout.rb +2 -2
  145. data/test/plugin/test_output.rb +297 -12
  146. data/test/plugin/test_output_as_buffered.rb +44 -44
  147. data/test/plugin/test_output_as_buffered_compress.rb +32 -18
  148. data/test/plugin/test_output_as_buffered_retries.rb +54 -7
  149. data/test/plugin/test_output_as_buffered_secondary.rb +4 -4
  150. data/test/plugin/test_parser_regexp.rb +1 -6
  151. data/test/plugin/test_parser_syslog.rb +1 -1
  152. data/test/plugin_helper/test_cert_option.rb +1 -1
  153. data/test/plugin_helper/test_child_process.rb +38 -16
  154. data/test/plugin_helper/test_event_emitter.rb +29 -0
  155. data/test/plugin_helper/test_http_server_helper.rb +1 -1
  156. data/test/plugin_helper/test_metrics.rb +137 -0
  157. data/test/plugin_helper/test_retry_state.rb +602 -38
  158. data/test/plugin_helper/test_server.rb +78 -6
  159. data/test/plugin_helper/test_timer.rb +2 -2
  160. data/test/test_config.rb +191 -24
  161. data/test/test_event_router.rb +17 -0
  162. data/test/test_file_wrapper.rb +53 -0
  163. data/test/test_formatter.rb +24 -21
  164. data/test/test_log.rb +122 -40
  165. data/test/test_msgpack_factory.rb +32 -0
  166. data/test/test_plugin_classes.rb +102 -0
  167. data/test/test_root_agent.rb +30 -1
  168. data/test/test_supervisor.rb +477 -257
  169. data/test/test_time_parser.rb +22 -0
  170. metadata +55 -34
  171. data/.drone.yml +0 -35
  172. data/.github/workflows/issue-auto-closer.yml +0 -12
  173. data/.github/workflows/linux-test.yaml +0 -36
  174. data/.github/workflows/macos-test.yaml +0 -30
  175. data/.github/workflows/windows-test.yaml +0 -46
  176. data/.gitlab-ci.yml +0 -103
  177. data/lib/fluent/plugin/file_wrapper.rb +0 -187
  178. data/test/plugin/test_file_wrapper.rb +0 -126
  179. data/test/test_logger_initializer.rb +0 -46
@@ -3,6 +3,7 @@ require 'fluent/test/driver/input'
3
3
  require 'fluent/plugin/in_tail'
4
4
  require 'fluent/plugin/buffer'
5
5
  require 'fluent/system_config'
6
+ require 'fluent/file_wrapper'
6
7
  require 'net/http'
7
8
  require 'flexmock/test_unit'
8
9
  require 'timecop'
@@ -12,15 +13,21 @@ require 'securerandom'
12
13
  class TailInputTest < Test::Unit::TestCase
13
14
  include FlexMock::TestCase
14
15
 
16
+ def tmp_dir
17
+ File.join(File.dirname(__FILE__), "..", "tmp", "tail#{ENV['TEST_ENV_NUMBER']}", SecureRandom.hex(10))
18
+ end
19
+
15
20
  def setup
16
21
  Fluent::Test.setup
17
- cleanup_directory(TMP_DIR)
22
+ @tmp_dir = tmp_dir
23
+ cleanup_directory(@tmp_dir)
18
24
  end
19
25
 
20
26
  def teardown
21
27
  super
22
- cleanup_directory(TMP_DIR)
28
+ cleanup_directory(@tmp_dir)
23
29
  Fluent::Engine.stop
30
+ Timecop.return
24
31
  end
25
32
 
26
33
  def cleanup_directory(path)
@@ -29,89 +36,48 @@ class TailInputTest < Test::Unit::TestCase
29
36
  return
30
37
  end
31
38
 
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
49
- end
50
- FileUtils.mkdir_p(path)
39
+ FileUtils.remove_entry_secure(path, true)
51
40
  end
52
41
 
53
42
  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
43
+ FileUtils.remove_entry_secure(path, true)
86
44
  end
87
45
 
88
46
  def create_target_info(path)
89
47
  Fluent::Plugin::TailInput::TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
90
48
  end
91
49
 
92
- TMP_DIR = File.dirname(__FILE__) + "/../tmp/tail#{ENV['TEST_ENV_NUMBER']}"
93
-
94
- CONFIG = config_element("ROOT", "", {
95
- "path" => "#{TMP_DIR}/tail.txt",
50
+ ROOT_CONFIG = config_element("ROOT", "", {
96
51
  "tag" => "t1",
97
52
  "rotate_wait" => "2s",
98
53
  "refresh_interval" => "1s"
99
- })
100
- COMMON_CONFIG = CONFIG + config_element("", "", { "pos_file" => "#{TMP_DIR}/tail.pos" })
54
+ })
55
+
56
+ def base_config
57
+ ROOT_CONFIG + config_element("", "", { "path" => "#{@tmp_dir}/tail.txt" })
58
+ end
59
+
60
+ def common_config
61
+ base_config + config_element("", "", { "pos_file" => "#{@tmp_dir}/tail.pos" })
62
+ end
63
+
64
+ def common_follow_inode_config
65
+ config_element("ROOT", "", {
66
+ "path" => "#{@tmp_dir}/tail.txt*",
67
+ "pos_file" => "#{@tmp_dir}/tail.pos",
68
+ "tag" => "t1",
69
+ "refresh_interval" => "1s",
70
+ "read_from_head" => "true",
71
+ "format" => "none",
72
+ "rotate_wait" => "1s",
73
+ "follow_inodes" => "true"
74
+ })
75
+ end
76
+
101
77
  CONFIG_READ_FROM_HEAD = config_element("", "", { "read_from_head" => true })
102
- CONFIG_ENABLE_WATCH_TIMER = config_element("", "", { "enable_watch_timer" => false })
78
+ CONFIG_DISABLE_WATCH_TIMER = config_element("", "", { "enable_watch_timer" => false })
103
79
  CONFIG_DISABLE_STAT_WATCHER = config_element("", "", { "enable_stat_watcher" => false })
104
80
  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
- "rotate_wait" => "1s",
113
- "follow_inodes" => "true"
114
- })
115
81
  SINGLE_LINE_CONFIG = config_element("", "", { "format" => "/(?<message>.*)/" })
116
82
  PARSE_SINGLE_LINE_CONFIG = config_element("", "", {}, [config_element("parse", "", { "@type" => "/(?<message>.*)/" })])
117
83
  MULTILINE_CONFIG = config_element(
@@ -144,21 +110,67 @@ class TailInputTest < Test::Unit::TestCase
144
110
  })
145
111
  ])
146
112
 
113
+ EX_ROTATE_WAIT = 0
114
+ EX_FOLLOW_INODES = false
115
+
116
+ def ex_config
117
+ config_element("", "", {
118
+ "tag" => "tail",
119
+ "path" => "test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log",
120
+ "format" => "none",
121
+ "pos_file" => "#{@tmp_dir}/tail.pos",
122
+ "read_from_head" => true,
123
+ "refresh_interval" => 30,
124
+ "rotate_wait" => "#{EX_ROTATE_WAIT}s",
125
+ "follow_inodes" => "#{EX_FOLLOW_INODES}",
126
+ })
127
+ end
128
+
129
+ def tailing_group_pattern
130
+ "/#{@tmp_dir}\/(?<podname>[a-z0-9]([-a-z0-9]*[a-z0-9])?(\/[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)_(?<namespace>[^_]+)_(?<container>.+)-(?<docker_id>[a-z0-9]{6})\.log$/"
131
+ end
132
+
133
+ DEBUG_LOG_LEVEL = config_element("", "", {
134
+ "@log_level" => "debug"
135
+ })
136
+
137
+ def create_group_directive(pattern, rate_period, *rules)
138
+ config_element("", "", {}, [
139
+ config_element("group", "", {
140
+ "pattern" => pattern,
141
+ "rate_period" => rate_period
142
+ }, rules)
143
+ ])
144
+ end
145
+
146
+ def create_rule_directive(match_named_captures, limit)
147
+ params = {
148
+ "limit" => limit,
149
+ "match" => match_named_captures,
150
+ }
151
+
152
+ config_element("rule", "", params)
153
+ end
154
+
155
+ def create_path_element(path)
156
+ config_element("source", "", { "path" => "#{@tmp_dir}/#{path}" })
157
+ end
158
+
147
159
  def create_driver(conf = SINGLE_LINE_CONFIG, use_common_conf = true)
148
- config = use_common_conf ? COMMON_CONFIG + conf : conf
160
+ config = use_common_conf ? common_config + conf : conf
149
161
  Fluent::Test::Driver::Input.new(Fluent::Plugin::TailInput).configure(config)
150
162
  end
151
163
 
152
164
  sub_test_case "configure" do
153
165
  test "plain single line" do
154
166
  d = create_driver
155
- assert_equal ["#{TMP_DIR}/tail.txt"], d.instance.paths
156
- assert_equal "t1", d.instance.tag
157
- assert_equal 2, d.instance.rotate_wait
158
- assert_equal "#{TMP_DIR}/tail.pos", d.instance.pos_file
159
- assert_equal 1000, d.instance.read_lines_limit
160
- assert_equal -1, d.instance.read_bytes_limit_per_second
161
- assert_equal false, d.instance.ignore_repeated_permission_error
167
+ assert_equal(["#{@tmp_dir}/tail.txt"], d.instance.paths)
168
+ assert_equal("t1", d.instance.tag)
169
+ assert_equal(2, d.instance.rotate_wait)
170
+ assert_equal("#{@tmp_dir}/tail.pos", d.instance.pos_file)
171
+ assert_equal(1000, d.instance.read_lines_limit)
172
+ assert_equal(-1, d.instance.read_bytes_limit_per_second)
173
+ assert_equal(false, d.instance.ignore_repeated_permission_error)
162
174
  assert_nothing_raised do
163
175
  d.instance.have_read_capability?
164
176
  end
@@ -175,13 +187,13 @@ class TailInputTest < Test::Unit::TestCase
175
187
  test "multi paths with path_delimiter" do
176
188
  c = config_element("ROOT", "", { "path" => "tail.txt|test2|tmp,dev", "tag" => "t1", "path_delimiter" => "|" })
177
189
  d = create_driver(c + PARSE_SINGLE_LINE_CONFIG, false)
178
- assert_equal ["tail.txt", "test2", "tmp,dev"], d.instance.paths
190
+ assert_equal(["tail.txt", "test2", "tmp,dev"], d.instance.paths)
179
191
  end
180
192
 
181
193
  test "multi paths with same path configured twice" do
182
194
  c = config_element("ROOT", "", { "path" => "test1.txt,test2.txt,test1.txt", "tag" => "t1", "path_delimiter" => "," })
183
195
  d = create_driver(c + PARSE_SINGLE_LINE_CONFIG, false)
184
- assert_equal ["test2.txt","test1.txt"].sort, d.instance.paths.sort
196
+ assert_equal(["test2.txt","test1.txt"].sort, d.instance.paths.sort)
185
197
  end
186
198
 
187
199
  test "multi paths with invaid path_delimiter" do
@@ -193,13 +205,13 @@ class TailInputTest < Test::Unit::TestCase
193
205
 
194
206
  test "follow_inodes w/o pos file" do
195
207
  assert_raise(Fluent::ConfigError) do
196
- create_driver(CONFIG + config_element('', '', {'follow_inodes' => 'true'}))
208
+ create_driver(base_config + config_element('', '', {'follow_inodes' => 'true'}))
197
209
  end
198
210
  end
199
211
 
200
212
  sub_test_case "log throttling per file" do
201
213
  test "w/o watcher timer is invalid" do
202
- conf = CONFIG_ENABLE_WATCH_TIMER + config_element("ROOT", "", {"read_bytes_limit_per_second" => "8k"})
214
+ conf = CONFIG_DISABLE_WATCH_TIMER + config_element("ROOT", "", {"read_bytes_limit_per_second" => "8k"})
203
215
  assert_raise(Fluent::ConfigError) do
204
216
  create_driver(conf)
205
217
  end
@@ -215,7 +227,7 @@ class TailInputTest < Test::Unit::TestCase
215
227
 
216
228
  test "both enable_watch_timer and enable_stat_watcher are false" do
217
229
  assert_raise(Fluent::ConfigError) do
218
- create_driver(CONFIG_ENABLE_WATCH_TIMER + CONFIG_DISABLE_STAT_WATCHER + PARSE_SINGLE_LINE_CONFIG)
230
+ create_driver(CONFIG_DISABLE_WATCH_TIMER + CONFIG_DISABLE_STAT_WATCHER + PARSE_SINGLE_LINE_CONFIG)
219
231
  end
220
232
  end
221
233
 
@@ -223,7 +235,7 @@ class TailInputTest < Test::Unit::TestCase
223
235
  test "valid" do
224
236
  conf = SINGLE_LINE_CONFIG + config_element("", "", { "encoding" => "utf-8" })
225
237
  d = create_driver(conf)
226
- assert_equal Encoding::UTF_8, d.instance.encoding
238
+ assert_equal(Encoding::UTF_8, d.instance.encoding)
227
239
  end
228
240
 
229
241
  test "invalid" do
@@ -263,12 +275,171 @@ class TailInputTest < Test::Unit::TestCase
263
275
  end
264
276
  end
265
277
 
278
+ sub_test_case "configure group" do
279
+ test "<rule> required" do
280
+ conf = create_group_directive('.', '1m') + SINGLE_LINE_CONFIG
281
+ assert_raise(Fluent::ConfigError) do
282
+ create_driver(conf)
283
+ end
284
+ end
285
+
286
+ test "valid configuration" do
287
+ rule1 = create_rule_directive({
288
+ "namespace"=> "/namespace-a/",
289
+ "podname"=> "/podname-[b|c]/"
290
+ }, 100)
291
+ rule2 = create_rule_directive({
292
+ "namespace"=> "/namespace-[d|e]/",
293
+ "podname"=> "/podname-f/",
294
+ }, 50)
295
+ rule3 = create_rule_directive({
296
+ "podname"=> "/podname-g/",
297
+ }, -1)
298
+ rule4 = create_rule_directive({
299
+ "namespace"=> "/namespace-h/",
300
+ }, 0)
301
+
302
+ conf = create_group_directive(tailing_group_pattern, '1m', rule1, rule2, rule3, rule4) + SINGLE_LINE_CONFIG
303
+ assert_nothing_raised do
304
+ create_driver(conf)
305
+ end
306
+ end
307
+
308
+ test "limit should be greater than DEFAULT_LIMIT (-1)" do
309
+ rule1 = create_rule_directive({
310
+ "namespace"=> "/namespace-a/",
311
+ "podname"=> "/podname-[b|c]/",
312
+ }, -100)
313
+ rule2 = create_rule_directive({
314
+ "namespace"=> "/namespace-[d|e]/",
315
+ "podname"=> "/podname-f/",
316
+ }, 50)
317
+ conf = create_group_directive(tailing_group_pattern, '1m', rule1, rule2) + SINGLE_LINE_CONFIG
318
+ assert_raise(RuntimeError) do
319
+ create_driver(conf)
320
+ end
321
+ end
322
+ end
323
+
324
+ sub_test_case "group rules line limit resolution" do
325
+ test "valid" do
326
+ rule1 = create_rule_directive({
327
+ "namespace"=> "/namespace-a/",
328
+ "podname"=> "/podname-[b|c]/",
329
+ }, 50)
330
+ rule2 = create_rule_directive({
331
+ "podname"=> "/podname-[b|c]/",
332
+ }, 400)
333
+ rule3 = create_rule_directive({
334
+ "namespace"=> "/namespace-a/",
335
+ }, 100)
336
+
337
+ conf = create_group_directive(tailing_group_pattern, '1m', rule3, rule1, rule2) + SINGLE_LINE_CONFIG
338
+ assert_nothing_raised do
339
+ d = create_driver(conf)
340
+ instance = d.instance
341
+
342
+ metadata = {
343
+ "namespace"=> "namespace-a",
344
+ "podname"=> "podname-b",
345
+ }
346
+ assert_equal(50, instance.find_group(metadata).limit)
347
+
348
+ metadata = {
349
+ "namespace" => "namespace-a",
350
+ "podname" => "podname-c",
351
+ }
352
+ assert_equal(50, instance.find_group(metadata).limit)
353
+
354
+ metadata = {
355
+ "namespace" => "namespace-a",
356
+ "podname" => "podname-d",
357
+ }
358
+ assert_equal(100, instance.find_group(metadata).limit)
359
+
360
+ metadata = {
361
+ "namespace" => "namespace-f",
362
+ "podname" => "podname-b",
363
+ }
364
+ assert_equal(400, instance.find_group(metadata).limit)
365
+
366
+ metadata = {
367
+ "podname" => "podname-c",
368
+ }
369
+ assert_equal(400, instance.find_group(metadata).limit)
370
+
371
+ assert_equal(-1, instance.find_group({}).limit)
372
+ end
373
+ end
374
+ end
375
+
376
+ sub_test_case "files should be placed in groups" do
377
+ test "invalid regex pattern places files in default group" do
378
+ rule1 = create_rule_directive({}, 100) ## limits default groups
379
+ conf = ROOT_CONFIG + DEBUG_LOG_LEVEL + create_group_directive(tailing_group_pattern, '1m', rule1) + create_path_element("test*.txt") + SINGLE_LINE_CONFIG
380
+
381
+ d = create_driver(conf, false)
382
+ Fluent::FileWrapper.open("#{@tmp_dir}/test1.txt", 'w')
383
+ Fluent::FileWrapper.open("#{@tmp_dir}/test2.txt", 'w')
384
+ Fluent::FileWrapper.open("#{@tmp_dir}/test3.txt", 'w')
385
+
386
+ d.run do
387
+ ## checking default group_watcher's paths
388
+ instance = d.instance
389
+ key = instance.default_group_key
390
+
391
+ assert_equal(3, instance.log.logs.count{|a| a.match?("Cannot find group from metadata, Adding file in the default group\n")})
392
+ assert_equal(3, instance.group_watchers[key].size)
393
+ assert_true(instance.group_watchers[key].include? File.join(@tmp_dir, 'test1.txt'))
394
+ assert_true(instance.group_watchers[key].include? File.join(@tmp_dir, 'test2.txt'))
395
+ assert_true(instance.group_watchers[key].include? File.join(@tmp_dir, 'test3.txt'))
396
+ end
397
+ end
398
+
399
+ test "valid regex pattern places file in their respective groups" do
400
+ rule1 = create_rule_directive({
401
+ "namespace"=> "/test-namespace1/",
402
+ "podname"=> "/test-podname1/",
403
+ }, 100)
404
+ rule2 = create_rule_directive({
405
+ "namespace"=> "/test-namespace1/",
406
+ }, 200)
407
+ rule3 = create_rule_directive({
408
+ "podname"=> "/test-podname2/",
409
+ }, 300)
410
+ rule4 = create_rule_directive({}, 400)
411
+
412
+ path_element = create_path_element("test-podname*.log")
413
+
414
+ conf = ROOT_CONFIG + create_group_directive(tailing_group_pattern, '1m', rule4, rule3, rule2, rule1) + path_element + SINGLE_LINE_CONFIG
415
+ d = create_driver(conf, false)
416
+
417
+ file1 = File.join(@tmp_dir, "test-podname1_test-namespace1_test-container-15fabq.log")
418
+ file2 = File.join(@tmp_dir, "test-podname3_test-namespace1_test-container-15fabq.log")
419
+ file3 = File.join(@tmp_dir, "test-podname2_test-namespace2_test-container-15fabq.log")
420
+ file4 = File.join(@tmp_dir, "test-podname4_test-namespace3_test-container-15fabq.log")
421
+
422
+ d.run do
423
+ Fluent::FileWrapper.open(file1, 'w')
424
+ Fluent::FileWrapper.open(file2, 'w')
425
+ Fluent::FileWrapper.open(file3, 'w')
426
+ Fluent::FileWrapper.open(file4, 'w')
427
+
428
+ instance = d.instance
429
+ assert_equal(100, instance.find_group_from_metadata(file1).limit)
430
+ assert_equal(200, instance.find_group_from_metadata(file2).limit)
431
+ assert_equal(300, instance.find_group_from_metadata(file3).limit)
432
+ assert_equal(400, instance.find_group_from_metadata(file4).limit)
433
+ end
434
+ end
435
+ end
436
+
266
437
  sub_test_case "singleline" do
267
438
  data(flat: SINGLE_LINE_CONFIG,
268
439
  parse: PARSE_SINGLE_LINE_CONFIG)
269
440
  def test_emit(data)
270
441
  config = data
271
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
442
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
272
443
  f.puts "test1"
273
444
  f.puts "test2"
274
445
  }
@@ -276,7 +447,7 @@ class TailInputTest < Test::Unit::TestCase
276
447
  d = create_driver(config)
277
448
 
278
449
  d.run(expect_emits: 1) do
279
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
450
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
280
451
  f.puts "test3\ntest4"
281
452
  }
282
453
  end
@@ -292,11 +463,11 @@ class TailInputTest < Test::Unit::TestCase
292
463
 
293
464
  def test_emit_with_emit_unmatched_lines_true
294
465
  config = config_element("", "", { "format" => "/^(?<message>test.*)/", "emit_unmatched_lines" => true })
295
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
466
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
296
467
 
297
468
  d = create_driver(config)
298
469
  d.run(expect_emits: 1) do
299
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
470
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
300
471
  f.puts "test line 1"
301
472
  f.puts "test line 2"
302
473
  f.puts "bad line 1"
@@ -328,7 +499,7 @@ class TailInputTest < Test::Unit::TestCase
328
499
  msg = 'test' * 2000 # in_tail reads 8192 bytes at once.
329
500
 
330
501
  d.run(expect_emits: num_events, timeout: 2) do
331
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
502
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
332
503
  f.puts msg
333
504
  f.puts msg
334
505
  }
@@ -343,7 +514,7 @@ class TailInputTest < Test::Unit::TestCase
343
514
 
344
515
  sub_test_case "log throttling per file" do
345
516
  teardown do
346
- cleanup_file("#{TMP_DIR}/tail.txt")
517
+ cleanup_file("#{@tmp_dir}/tail.txt")
347
518
  end
348
519
 
349
520
  sub_test_case "reads_bytes_per_second w/o throttled" do
@@ -374,7 +545,7 @@ class TailInputTest < Test::Unit::TestCase
374
545
 
375
546
  d = create_driver(config)
376
547
  d.run(expect_emits: 2) do
377
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
548
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
378
549
  100.times do
379
550
  f.puts msg
380
551
  end
@@ -397,7 +568,7 @@ class TailInputTest < Test::Unit::TestCase
397
568
  start_time = Fluent::Clock.now
398
569
  d = create_driver(config)
399
570
  d.run(expect_emits: 2) do
400
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
571
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
401
572
  8000.times do
402
573
  f.puts msg
403
574
  end
@@ -436,7 +607,7 @@ class TailInputTest < Test::Unit::TestCase
436
607
  io_handler
437
608
  end
438
609
 
439
- File.open("#{TMP_DIR}/tail.txt", "ab") do |f|
610
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") do |f|
440
611
  100.times do
441
612
  f.puts msg
442
613
  end
@@ -446,7 +617,7 @@ class TailInputTest < Test::Unit::TestCase
446
617
  d.run do
447
618
  start_time = Fluent::Clock.now
448
619
  while Fluent::Clock.now - start_time < 0.8 do
449
- File.open("#{TMP_DIR}/tail.txt", "ab") do |f|
620
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") do |f|
450
621
  f.puts msg
451
622
  f.flush
452
623
  end
@@ -464,7 +635,7 @@ class TailInputTest < Test::Unit::TestCase
464
635
  num_lines = 1024 * 3
465
636
  msg = "08bytes"
466
637
 
467
- File.open("#{TMP_DIR}/tail.txt", "wb") do |f|
638
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") do |f|
468
639
  f.write("#{msg}\n" * num_lines)
469
640
  end
470
641
 
@@ -481,8 +652,8 @@ class TailInputTest < Test::Unit::TestCase
481
652
  d.run(timeout: 10) do
482
653
  while d.events.size < num_lines do
483
654
  if d.events.size > 0 && !rotated
484
- cleanup_file("#{TMP_DIR}/tail.txt")
485
- FileUtils.touch("#{TMP_DIR}/tail.txt")
655
+ cleanup_file("#{@tmp_dir}/tail.txt")
656
+ FileUtils.touch("#{@tmp_dir}/tail.txt")
486
657
  rotated = true
487
658
  end
488
659
  sleep 0.3
@@ -500,7 +671,7 @@ class TailInputTest < Test::Unit::TestCase
500
671
  num_lines = 1024 * 2
501
672
  msg = "08bytes"
502
673
 
503
- File.open("#{TMP_DIR}/tail.txt", "wb") do |f|
674
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") do |f|
504
675
  f.write("#{msg}\n" * num_lines)
505
676
  end
506
677
 
@@ -527,8 +698,8 @@ class TailInputTest < Test::Unit::TestCase
527
698
  d.run(timeout: 10) do
528
699
  until detached do
529
700
  if d.events.size > 0 && !rotated
530
- cleanup_file("#{TMP_DIR}/tail.txt")
531
- FileUtils.touch("#{TMP_DIR}/tail.txt")
701
+ cleanup_file("#{@tmp_dir}/tail.txt")
702
+ FileUtils.touch("#{@tmp_dir}/tail.txt")
532
703
  rotated = true
533
704
  end
534
705
  sleep 0.3
@@ -548,7 +719,7 @@ class TailInputTest < Test::Unit::TestCase
548
719
  parse: CONFIG_READ_FROM_HEAD + PARSE_SINGLE_LINE_CONFIG)
549
720
  def test_emit_with_read_from_head(data)
550
721
  config = data
551
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
722
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
552
723
  f.puts "test1"
553
724
  f.puts "test2"
554
725
  }
@@ -556,7 +727,7 @@ class TailInputTest < Test::Unit::TestCase
556
727
  d = create_driver(config)
557
728
 
558
729
  d.run(expect_emits: 2) do
559
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
730
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
560
731
  f.puts "test3"
561
732
  f.puts "test4"
562
733
  }
@@ -570,11 +741,11 @@ class TailInputTest < Test::Unit::TestCase
570
741
  assert_equal({"message" => "test4"}, events[3][2])
571
742
  end
572
743
 
573
- data(flat: CONFIG_ENABLE_WATCH_TIMER + SINGLE_LINE_CONFIG,
574
- parse: CONFIG_ENABLE_WATCH_TIMER + PARSE_SINGLE_LINE_CONFIG)
575
- def test_emit_with_enable_watch_timer(data)
744
+ data(flat: CONFIG_DISABLE_WATCH_TIMER + SINGLE_LINE_CONFIG,
745
+ parse: CONFIG_DISABLE_WATCH_TIMER + PARSE_SINGLE_LINE_CONFIG)
746
+ def test_emit_without_watch_timer(data)
576
747
  config = data
577
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
748
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
578
749
  f.puts "test1"
579
750
  f.puts "test2"
580
751
  }
@@ -582,7 +753,7 @@ class TailInputTest < Test::Unit::TestCase
582
753
  d = create_driver(config)
583
754
 
584
755
  d.run(expect_emits: 1) do
585
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
756
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
586
757
  f.puts "test3"
587
758
  f.puts "test4"
588
759
  }
@@ -596,11 +767,43 @@ class TailInputTest < Test::Unit::TestCase
596
767
  assert_equal({"message" => "test4"}, events[1][2])
597
768
  end
598
769
 
770
+ # https://github.com/fluent/fluentd/pull/3541#discussion_r740197711
771
+ def test_watch_wildcard_path_without_watch_timer
772
+ omit "need inotify" unless Fluent.linux?
773
+
774
+ config = config_element("ROOT", "", {
775
+ "path" => "#{@tmp_dir}/tail*.txt",
776
+ "tag" => "t1",
777
+ })
778
+ config = config + CONFIG_DISABLE_WATCH_TIMER + SINGLE_LINE_CONFIG
779
+
780
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
781
+ f.puts "test1"
782
+ f.puts "test2"
783
+ }
784
+
785
+ d = create_driver(config, false)
786
+
787
+ d.run(expect_emits: 1, timeout: 1) do
788
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
789
+ f.puts "test3"
790
+ f.puts "test4"
791
+ }
792
+ end
793
+
794
+ assert_equal(
795
+ [
796
+ {"message" => "test3"},
797
+ {"message" => "test4"},
798
+ ],
799
+ d.events.collect { |event| event[2] })
800
+ end
801
+
599
802
  data(flat: CONFIG_DISABLE_STAT_WATCHER + SINGLE_LINE_CONFIG,
600
803
  parse: CONFIG_DISABLE_STAT_WATCHER + PARSE_SINGLE_LINE_CONFIG)
601
804
  def test_emit_with_disable_stat_watcher(data)
602
805
  config = data
603
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
806
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
604
807
  f.puts "test1"
605
808
  f.puts "test2"
606
809
  }
@@ -608,7 +811,7 @@ class TailInputTest < Test::Unit::TestCase
608
811
  d = create_driver(config)
609
812
 
610
813
  d.run(expect_emits: 1) do
611
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
814
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
612
815
  f.puts "test3"
613
816
  f.puts "test4"
614
817
  }
@@ -619,6 +822,23 @@ class TailInputTest < Test::Unit::TestCase
619
822
  assert_equal({"message" => "test3"}, events[0][2])
620
823
  assert_equal({"message" => "test4"}, events[1][2])
621
824
  end
825
+
826
+ def test_always_read_from_head_on_detecting_a_new_file
827
+ d = create_driver(SINGLE_LINE_CONFIG)
828
+
829
+ d.run(expect_emits: 1, timeout: 3) do
830
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
831
+ f.puts "test1\ntest2\n"
832
+ }
833
+ end
834
+
835
+ assert_equal(
836
+ [
837
+ {"message" => "test1"},
838
+ {"message" => "test2"},
839
+ ],
840
+ d.events.collect { |event| event[2] })
841
+ end
622
842
  end
623
843
 
624
844
  class TestWithSystem < self
@@ -633,11 +853,14 @@ class TailInputTest < Test::Unit::TestCase
633
853
 
634
854
  def setup
635
855
  omit "NTFS doesn't support UNIX like permissions" if Fluent.windows?
856
+ super
636
857
  # Store default permission
637
858
  @default_permission = system_config.instance_variable_get(:@file_permission)
638
859
  end
639
860
 
640
861
  def teardown
862
+ return if Fluent.windows?
863
+ super
641
864
  # Restore default permission
642
865
  system_config.instance_variable_set(:@file_permission, @default_permission)
643
866
  end
@@ -651,7 +874,7 @@ class TailInputTest < Test::Unit::TestCase
651
874
  system_conf = parse_system(CONFIG_SYSTEM)
652
875
  sc = Fluent::SystemConfig.new(system_conf)
653
876
  Fluent::Engine.init(sc)
654
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
877
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
655
878
  f.puts "test1"
656
879
  f.puts "test2"
657
880
  }
@@ -659,7 +882,7 @@ class TailInputTest < Test::Unit::TestCase
659
882
  d = create_driver
660
883
 
661
884
  d.run(expect_emits: 1) do
662
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
885
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
663
886
  f.puts "test3"
664
887
  f.puts "test4"
665
888
  }
@@ -679,16 +902,18 @@ class TailInputTest < Test::Unit::TestCase
679
902
  end
680
903
 
681
904
  sub_test_case "rotate file" do
905
+ def create_driver(conf = SINGLE_LINE_CONFIG)
906
+ config = common_config + conf
907
+ Fluent::Test::Driver::Input.new(Fluent::Plugin::TailInput).configure(config)
908
+ end
909
+
682
910
  data(flat: SINGLE_LINE_CONFIG,
683
911
  parse: PARSE_SINGLE_LINE_CONFIG)
684
912
  def test_rotate_file(data)
685
913
  config = data
686
914
  events = sub_test_rotate_file(config, expect_emits: 2)
687
- assert_equal(4, events.length)
688
- assert_equal({"message" => "test3"}, events[0][2])
689
- assert_equal({"message" => "test4"}, events[1][2])
690
- assert_equal({"message" => "test5"}, events[2][2])
691
- assert_equal({"message" => "test6"}, events[3][2])
915
+ assert_equal(3.upto(6).collect { |i| {"message" => "test#{i}"} },
916
+ events.collect { |event| event[2] })
692
917
  end
693
918
 
694
919
  data(flat: CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG,
@@ -696,13 +921,8 @@ class TailInputTest < Test::Unit::TestCase
696
921
  def test_rotate_file_with_read_from_head(data)
697
922
  config = data
698
923
  events = sub_test_rotate_file(config, expect_records: 6)
699
- assert_equal(6, events.length)
700
- assert_equal({"message" => "test1"}, events[0][2])
701
- assert_equal({"message" => "test2"}, events[1][2])
702
- assert_equal({"message" => "test3"}, events[2][2])
703
- assert_equal({"message" => "test4"}, events[3][2])
704
- assert_equal({"message" => "test5"}, events[4][2])
705
- assert_equal({"message" => "test6"}, events[5][2])
924
+ assert_equal(1.upto(6).collect { |i| {"message" => "test#{i}"} },
925
+ events.collect { |event| event[2] })
706
926
  end
707
927
 
708
928
  data(flat: CONFIG_OPEN_ON_EVERY_UPDATE + CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG,
@@ -710,13 +930,8 @@ class TailInputTest < Test::Unit::TestCase
710
930
  def test_rotate_file_with_open_on_every_update(data)
711
931
  config = data
712
932
  events = sub_test_rotate_file(config, expect_records: 6)
713
- assert_equal(6, events.length)
714
- assert_equal({"message" => "test1"}, events[0][2])
715
- assert_equal({"message" => "test2"}, events[1][2])
716
- assert_equal({"message" => "test3"}, events[2][2])
717
- assert_equal({"message" => "test4"}, events[3][2])
718
- assert_equal({"message" => "test5"}, events[4][2])
719
- assert_equal({"message" => "test6"}, events[5][2])
933
+ assert_equal(1.upto(6).collect { |i| {"message" => "test#{i}"} },
934
+ events.collect { |event| event[2] })
720
935
  end
721
936
 
722
937
  data(flat: SINGLE_LINE_CONFIG,
@@ -724,26 +939,21 @@ class TailInputTest < Test::Unit::TestCase
724
939
  def test_rotate_file_with_write_old(data)
725
940
  config = data
726
941
  events = sub_test_rotate_file(config, expect_emits: 3) { |rotated_file|
727
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
942
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
728
943
  rotated_file.puts "test7"
729
944
  rotated_file.puts "test8"
730
945
  rotated_file.flush
731
946
 
732
947
  sleep 1
733
- File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
948
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
734
949
  f.puts "test5"
735
950
  f.puts "test6"
736
951
  }
737
952
  }
738
953
  # This test sometimes fails and it shows a potential bug of in_tail
739
954
  # https://github.com/fluent/fluentd/issues/1434
740
- assert_equal(6, events.length)
741
- assert_equal({"message" => "test3"}, events[0][2])
742
- assert_equal({"message" => "test4"}, events[1][2])
743
- assert_equal({"message" => "test7"}, events[2][2])
744
- assert_equal({"message" => "test8"}, events[3][2])
745
- assert_equal({"message" => "test5"}, events[4][2])
746
- assert_equal({"message" => "test6"}, events[5][2])
955
+ assert_equal([3, 4, 7, 8, 5, 6].collect { |i| {"message" => "test#{i}"} },
956
+ events.collect { |event| event[2] })
747
957
  end
748
958
 
749
959
  data(flat: SINGLE_LINE_CONFIG,
@@ -755,21 +965,19 @@ class TailInputTest < Test::Unit::TestCase
755
965
  rotated_file.puts "test8"
756
966
  rotated_file.flush
757
967
  }
758
- assert_equal(4, events.length)
759
- assert_equal({"message" => "test3"}, events[0][2])
760
- assert_equal({"message" => "test4"}, events[1][2])
761
- assert_equal({"message" => "test7"}, events[2][2])
762
- assert_equal({"message" => "test8"}, events[3][2])
968
+ assert_equal([3, 4, 7, 8].collect { |i| {"message" => "test#{i}"} },
969
+ events.collect { |event| event[2] })
763
970
  end
764
971
 
765
- def sub_test_rotate_file(config = nil, expect_emits: nil, expect_records: nil, timeout: nil)
766
- file = Fluent::FileWrapper.open("#{TMP_DIR}/tail.txt", "wb")
972
+ def sub_test_rotate_file(config = nil, expect_emits: nil, expect_records: nil, timeout: 5)
973
+ file = Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb")
767
974
  file.puts "test1"
768
975
  file.puts "test2"
769
976
  file.flush
770
977
 
771
978
  d = create_driver(config)
772
979
  d.run(expect_emits: expect_emits, expect_records: expect_records, timeout: timeout) do
980
+ sleep(0.1) while d.instance.instance_variable_get(:@startup)
773
981
  size = d.emit_count
774
982
  file.puts "test3"
775
983
  file.puts "test4"
@@ -779,18 +987,18 @@ class TailInputTest < Test::Unit::TestCase
779
987
 
780
988
  if Fluent.windows?
781
989
  file.close
782
- FileUtils.mv("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail2.txt", force: true)
783
- file = File.open("#{TMP_DIR}/tail.txt", "ab")
990
+ FileUtils.mv("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail2.txt", force: true)
991
+ file = Fluent::FileWrapper.open("#{@tmp_dir}/tail2.txt", "ab")
784
992
  else
785
- FileUtils.mv("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail2.txt")
993
+ FileUtils.mv("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail2.txt")
786
994
  end
787
995
  if block_given?
788
996
  yield file
789
997
  else
790
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
998
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
791
999
  sleep 1
792
1000
 
793
- File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1001
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
794
1002
  f.puts "test5"
795
1003
  f.puts "test6"
796
1004
  }
@@ -804,10 +1012,8 @@ class TailInputTest < Test::Unit::TestCase
804
1012
  end
805
1013
 
806
1014
  def test_truncate_file
807
- omit "Permission denied error happen on Windows. Need fix" if Fluent.windows?
808
-
809
1015
  config = SINGLE_LINE_CONFIG
810
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1016
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
811
1017
  f.puts "test1"
812
1018
  f.puts "test2"
813
1019
  f.flush
@@ -816,12 +1022,18 @@ class TailInputTest < Test::Unit::TestCase
816
1022
  d = create_driver(config)
817
1023
 
818
1024
  d.run(expect_emits: 2) do
819
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
1025
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
820
1026
  f.puts "test3\ntest4"
821
1027
  f.flush
822
1028
  }
823
1029
  waiting(2) { sleep 0.1 until d.events.length == 2 }
824
- File.truncate("#{TMP_DIR}/tail.txt", 6)
1030
+ if Fluent.windows?
1031
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f|
1032
+ f.puts("test1");
1033
+ }
1034
+ else
1035
+ File.truncate("#{@tmp_dir}/tail.txt", 6)
1036
+ end
825
1037
  end
826
1038
 
827
1039
  expected = {
@@ -840,10 +1052,8 @@ class TailInputTest < Test::Unit::TestCase
840
1052
  end
841
1053
 
842
1054
  def test_move_truncate_move_back
843
- omit "Permission denied error happen on Windows. Need fix" if Fluent.windows?
844
-
845
1055
  config = SINGLE_LINE_CONFIG
846
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1056
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
847
1057
  f.puts "test1"
848
1058
  f.puts "test2"
849
1059
  }
@@ -852,17 +1062,23 @@ class TailInputTest < Test::Unit::TestCase
852
1062
 
853
1063
  d.run(expect_emits: 1) do
854
1064
  if Fluent.windows?
855
- FileUtils.mv("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail2.txt", force: true)
1065
+ FileUtils.mv("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail2.txt", force: true)
856
1066
  else
857
- FileUtils.mv("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail2.txt")
1067
+ FileUtils.mv("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail2.txt")
858
1068
  end
859
1069
  sleep(1)
860
- File.truncate("#{TMP_DIR}/tail2.txt", 6)
1070
+ if Fluent.windows?
1071
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail2.txt", "wb") { |f|
1072
+ f.puts("test1");
1073
+ }
1074
+ else
1075
+ File.truncate("#{@tmp_dir}/tail2.txt", 6)
1076
+ end
861
1077
  sleep(1)
862
1078
  if Fluent.windows?
863
- FileUtils.mv("#{TMP_DIR}/tail2.txt", "#{TMP_DIR}/tail.txt", force: true)
1079
+ FileUtils.mv("#{@tmp_dir}/tail2.txt", "#{@tmp_dir}/tail.txt", force: true)
864
1080
  else
865
- FileUtils.mv("#{TMP_DIR}/tail2.txt", "#{TMP_DIR}/tail.txt")
1081
+ FileUtils.mv("#{@tmp_dir}/tail2.txt", "#{@tmp_dir}/tail.txt")
866
1082
  end
867
1083
  end
868
1084
 
@@ -874,17 +1090,17 @@ class TailInputTest < Test::Unit::TestCase
874
1090
  end
875
1091
 
876
1092
  def test_lf
877
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f| }
1093
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| }
878
1094
 
879
1095
  d = create_driver
880
1096
 
881
1097
  d.run(expect_emits: 1) do
882
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
1098
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
883
1099
  f.print "test3"
884
1100
  }
885
1101
  sleep 1
886
1102
 
887
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
1103
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
888
1104
  f.puts "test4"
889
1105
  }
890
1106
  end
@@ -895,12 +1111,12 @@ class TailInputTest < Test::Unit::TestCase
895
1111
  end
896
1112
 
897
1113
  def test_whitespace
898
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f| }
1114
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| }
899
1115
 
900
1116
  d = create_driver
901
1117
 
902
1118
  d.run(expect_emits: 1) do
903
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
1119
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
904
1120
  f.puts " " # 4 spaces
905
1121
  f.puts " 4 spaces"
906
1122
  f.puts "4 spaces "
@@ -931,7 +1147,7 @@ class TailInputTest < Test::Unit::TestCase
931
1147
  d = create_driver(CONFIG_READ_FROM_HEAD + encoding_config)
932
1148
 
933
1149
  d.run(expect_emits: 1) do
934
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1150
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
935
1151
  f.puts "test"
936
1152
  }
937
1153
  end
@@ -953,7 +1169,7 @@ class TailInputTest < Test::Unit::TestCase
953
1169
  utf8_message = cp932_message.encode(Encoding::UTF_8)
954
1170
 
955
1171
  d.run(expect_emits: 1) do
956
- File.open("#{TMP_DIR}/tail.txt", "w:cp932") {|f|
1172
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "w:cp932") {|f|
957
1173
  f.puts cp932_message
958
1174
  }
959
1175
  end
@@ -976,7 +1192,7 @@ class TailInputTest < Test::Unit::TestCase
976
1192
  utf8_message = utf16_message.encode(Encoding::UTF_8).strip
977
1193
 
978
1194
  d.run(expect_emits: 1) do
979
- File.open("#{TMP_DIR}/tail.txt", "w:utf-16le") { |f|
1195
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "w:utf-16le") { |f|
980
1196
  f.write utf16_message
981
1197
  }
982
1198
  end
@@ -997,7 +1213,7 @@ class TailInputTest < Test::Unit::TestCase
997
1213
  d = create_driver(conf)
998
1214
 
999
1215
  d.run(expect_emits: 1) do
1000
- File.open("#{TMP_DIR}/tail.txt", "w") { |f|
1216
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "w") { |f|
1001
1217
  f.write "te\x86st\n"
1002
1218
  }
1003
1219
  end
@@ -1012,11 +1228,11 @@ class TailInputTest < Test::Unit::TestCase
1012
1228
  parse: PARSE_MULTILINE_CONFIG)
1013
1229
  def test_multiline(data)
1014
1230
  config = data
1015
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1231
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
1016
1232
 
1017
1233
  d = create_driver(config)
1018
1234
  d.run(expect_emits: 1) do
1019
- File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1235
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
1020
1236
  f.puts "f test1"
1021
1237
  f.puts "s test2"
1022
1238
  f.puts "f test3"
@@ -1040,11 +1256,11 @@ class TailInputTest < Test::Unit::TestCase
1040
1256
  parse: PARSE_MULTILINE_CONFIG)
1041
1257
  def test_multiline_with_emit_unmatched_lines_true(data)
1042
1258
  config = data + config_element("", "", { "emit_unmatched_lines" => true })
1043
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1259
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
1044
1260
 
1045
1261
  d = create_driver(config)
1046
1262
  d.run(expect_emits: 1) do
1047
- File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1263
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
1048
1264
  f.puts "f test1"
1049
1265
  f.puts "s test2"
1050
1266
  f.puts "f test3"
@@ -1070,11 +1286,11 @@ class TailInputTest < Test::Unit::TestCase
1070
1286
  parse: PARSE_MULTILINE_CONFIG_WITH_NEWLINE)
1071
1287
  def test_multiline_with_emit_unmatched_lines2(data)
1072
1288
  config = data + config_element("", "", { "emit_unmatched_lines" => true })
1073
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1289
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
1074
1290
 
1075
1291
  d = create_driver(config)
1076
1292
  d.run(expect_emits: 0, timeout: 1) do
1077
- File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1293
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
1078
1294
  f.puts "s test0"
1079
1295
  f.puts "f test1"
1080
1296
  f.puts "f test2"
@@ -1096,15 +1312,15 @@ class TailInputTest < Test::Unit::TestCase
1096
1312
  data(flat: MULTILINE_CONFIG,
1097
1313
  parse: PARSE_MULTILINE_CONFIG)
1098
1314
  def test_multiline_with_flush_interval(data)
1099
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1315
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
1100
1316
 
1101
1317
  config = data + config_element("", "", { "multiline_flush_interval" => "2s" })
1102
1318
  d = create_driver(config)
1103
1319
 
1104
- assert_equal 2, d.instance.multiline_flush_interval
1320
+ assert_equal(2, d.instance.multiline_flush_interval)
1105
1321
 
1106
1322
  d.run(expect_emits: 1) do
1107
- File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1323
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
1108
1324
  f.puts "f test1"
1109
1325
  f.puts "s test2"
1110
1326
  f.puts "f test3"
@@ -1139,7 +1355,7 @@ class TailInputTest < Test::Unit::TestCase
1139
1355
  d = create_driver(config + encoding_config)
1140
1356
 
1141
1357
  d.run(expect_emits: 1) do
1142
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f|
1358
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f|
1143
1359
  f.puts "s test"
1144
1360
  }
1145
1361
  end
@@ -1162,7 +1378,7 @@ class TailInputTest < Test::Unit::TestCase
1162
1378
  cp932_message = "s \x82\xCD\x82\xEB\x81\x5B\x82\xED\x81\x5B\x82\xE9\x82\xC7".force_encoding(Encoding::CP932)
1163
1379
  utf8_message = "\x82\xCD\x82\xEB\x81\x5B\x82\xED\x81\x5B\x82\xE9\x82\xC7".encode(Encoding::UTF_8, Encoding::CP932)
1164
1380
  d.run(expect_emits: 1) do
1165
- File.open("#{TMP_DIR}/tail.txt", "w:cp932") { |f|
1381
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "w:cp932") { |f|
1166
1382
  f.puts cp932_message
1167
1383
  }
1168
1384
  end
@@ -1194,11 +1410,11 @@ class TailInputTest < Test::Unit::TestCase
1194
1410
  )
1195
1411
  def test_multiline_with_multiple_formats(data)
1196
1412
  config = data
1197
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1413
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
1198
1414
 
1199
1415
  d = create_driver(config)
1200
1416
  d.run(expect_emits: 1) do
1201
- File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1417
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
1202
1418
  f.puts "f test1"
1203
1419
  f.puts "s test2"
1204
1420
  f.puts "f test3"
@@ -1234,8 +1450,8 @@ class TailInputTest < Test::Unit::TestCase
1234
1450
  ])
1235
1451
  )
1236
1452
  def test_multilinelog_with_multiple_paths(data)
1237
- files = ["#{TMP_DIR}/tail1.txt", "#{TMP_DIR}/tail2.txt"]
1238
- files.each { |file| File.open(file, "wb") { |f| } }
1453
+ files = ["#{@tmp_dir}/tail1.txt", "#{@tmp_dir}/tail2.txt"]
1454
+ files.each { |file| Fluent::FileWrapper.open(file, "wb") { |f| } }
1239
1455
 
1240
1456
  config = data + config_element("", "", {
1241
1457
  "path" => "#{files[0]},#{files[1]}",
@@ -1244,7 +1460,7 @@ class TailInputTest < Test::Unit::TestCase
1244
1460
  d = create_driver(config, false)
1245
1461
  d.run(expect_emits: 2) do
1246
1462
  files.each do |file|
1247
- File.open(file, 'ab') { |f|
1463
+ Fluent::FileWrapper.open(file, 'ab') { |f|
1248
1464
  f.puts "f #{file} line should be ignored"
1249
1465
  f.puts "s test1"
1250
1466
  f.puts "f test2"
@@ -1279,12 +1495,12 @@ class TailInputTest < Test::Unit::TestCase
1279
1495
  ])
1280
1496
  )
1281
1497
  def test_multiline_without_firstline(data)
1282
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1498
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
1283
1499
 
1284
1500
  config = data
1285
1501
  d = create_driver(config)
1286
1502
  d.run(expect_emits: 1) do
1287
- File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1503
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
1288
1504
  f.puts "foo 1"
1289
1505
  f.puts "bar 1"
1290
1506
  f.puts "baz 1"
@@ -1304,35 +1520,22 @@ class TailInputTest < Test::Unit::TestCase
1304
1520
  sub_test_case "path" do
1305
1521
  # * path test
1306
1522
  # TODO: Clean up tests
1307
- EX_ROTATE_WAIT = 0
1308
- EX_FOLLOW_INODES = false
1309
-
1310
- EX_CONFIG = config_element("", "", {
1311
- "tag" => "tail",
1312
- "path" => "test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log",
1313
- "format" => "none",
1314
- "pos_file" => "#{TMP_DIR}/tail.pos",
1315
- "read_from_head" => true,
1316
- "refresh_interval" => 30,
1317
- "rotate_wait" => "#{EX_ROTATE_WAIT}s",
1318
- "follow_inodes" => "#{EX_FOLLOW_INODES}",
1319
- })
1320
1523
  def test_expand_paths
1321
1524
  ex_paths = [
1322
1525
  create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
1323
1526
  create_target_info('test/plugin/data/log/foo/bar.log'),
1324
1527
  create_target_info('test/plugin/data/log/test.log')
1325
1528
  ]
1326
- plugin = create_driver(EX_CONFIG, false).instance
1529
+ plugin = create_driver(ex_config, false).instance
1327
1530
  flexstub(Time) do |timeclass|
1328
1531
  timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 2, 3, 4, 5))
1329
- assert_equal ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1532
+ assert_equal(ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
1330
1533
  end
1331
1534
 
1332
1535
  # Test exclusion
1333
- exclude_config = EX_CONFIG + config_element("", "", { "exclude_path" => %Q(["#{ex_paths.last.path}"]) })
1536
+ exclude_config = ex_config + config_element("", "", { "exclude_path" => %Q(["#{ex_paths.last.path}"]) })
1334
1537
  plugin = create_driver(exclude_config, false).instance
1335
- assert_equal ex_paths - [ex_paths.last], plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1538
+ assert_equal(ex_paths - [ex_paths.last], plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
1336
1539
  end
1337
1540
 
1338
1541
  def test_expand_paths_with_duplicate_configuration
@@ -1340,10 +1543,10 @@ class TailInputTest < Test::Unit::TestCase
1340
1543
  create_target_info('test/plugin/data/log/foo/bar.log'),
1341
1544
  create_target_info('test/plugin/data/log/test.log')
1342
1545
  ]
1343
- duplicate_config = EX_CONFIG.dup
1546
+ duplicate_config = ex_config.dup
1344
1547
  duplicate_config["path"]="test/plugin/data/log/**/*.log, test/plugin/data/log/**/*.log"
1345
- plugin = create_driver(EX_CONFIG, false).instance
1346
- assert_equal expanded_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1548
+ plugin = create_driver(ex_config, false).instance
1549
+ assert_equal(expanded_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
1347
1550
  end
1348
1551
 
1349
1552
  def test_expand_paths_with_timezone
@@ -1353,7 +1556,7 @@ class TailInputTest < Test::Unit::TestCase
1353
1556
  create_target_info('test/plugin/data/log/test.log')
1354
1557
  ]
1355
1558
  ['Asia/Taipei', '+08'].each do |tz_type|
1356
- taipei_config = EX_CONFIG + config_element("", "", {"path_timezone" => tz_type})
1559
+ taipei_config = ex_config + config_element("", "", {"path_timezone" => tz_type})
1357
1560
  plugin = create_driver(taipei_config, false).instance
1358
1561
 
1359
1562
  # Test exclude
@@ -1365,8 +1568,8 @@ class TailInputTest < Test::Unit::TestCase
1365
1568
  # env : 2010-01-01 19:04:05 (UTC), tail path : 2010-01-02 03:04:05 (Asia/Taipei)
1366
1569
  timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 1, 19, 4, 5))
1367
1570
 
1368
- assert_equal ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1369
- assert_equal ex_paths - [ex_paths.first], exclude_plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1571
+ assert_equal(ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
1572
+ assert_equal(ex_paths - [ex_paths.first], exclude_plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
1370
1573
  end
1371
1574
  end
1372
1575
  end
@@ -1384,35 +1587,45 @@ class TailInputTest < Test::Unit::TestCase
1384
1587
  "tag" => "tail",
1385
1588
  "path" => "test/plugin/data/log/**/*",
1386
1589
  "format" => "none",
1387
- "pos_file" => "#{TMP_DIR}/tail.pos"
1590
+ "pos_file" => "#{@tmp_dir}/tail.pos"
1388
1591
  })
1389
1592
 
1390
1593
  plugin = create_driver(config, false).instance
1391
- assert_equal expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1594
+ assert_equal(expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
1392
1595
  end
1393
1596
 
1394
1597
  def test_unwatched_files_should_be_removed
1395
1598
  config = config_element("", "", {
1396
1599
  "tag" => "tail",
1397
- "path" => "#{TMP_DIR}/*.txt",
1600
+ "path" => "#{@tmp_dir}/*.txt",
1398
1601
  "format" => "none",
1399
- "pos_file" => "#{TMP_DIR}/tail.pos",
1602
+ "pos_file" => "#{@tmp_dir}/tail.pos",
1400
1603
  "read_from_head" => true,
1401
1604
  "refresh_interval" => 1,
1402
1605
  })
1403
1606
  d = create_driver(config, false)
1404
1607
  d.end_if { d.instance.instance_variable_get(:@tails).keys.size >= 1 }
1405
1608
  d.run(expect_emits: 1, shutdown: false) do
1406
- File.open("#{TMP_DIR}/tail.txt", "ab") { |f| f.puts "test3\n" }
1609
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f| f.puts "test3\n" }
1407
1610
  end
1408
1611
 
1409
- cleanup_directory(TMP_DIR)
1410
- waiting(20) { sleep 0.1 until Dir.glob("#{TMP_DIR}/*.txt").size == 0 } # Ensure file is deleted on Windows
1612
+ cleanup_file("#{@tmp_dir}/tail.txt")
1613
+ waiting(20) { sleep 0.1 until Dir.glob("#{@tmp_dir}/*.txt").size == 0 } # Ensure file is deleted on Windows
1411
1614
  waiting(5) { sleep 0.1 until d.instance.instance_variable_get(:@tails).keys.size <= 0 }
1412
1615
 
1413
- assert_equal 0, d.instance.instance_variable_get(:@tails).keys.size
1414
-
1415
- d.instance_shutdown
1616
+ assert_equal(
1617
+ {
1618
+ files: [],
1619
+ tails: []
1620
+ },
1621
+ {
1622
+ files: Dir.glob("#{@tmp_dir}/*.txt"),
1623
+ tails: d.instance.instance_variable_get(:@tails).keys
1624
+ }
1625
+ )
1626
+ ensure
1627
+ d.instance_shutdown if d && d.instance
1628
+ cleanup_directory(@tmp_dir)
1416
1629
  end
1417
1630
 
1418
1631
  def count_timer_object
@@ -1462,55 +1675,55 @@ class TailInputTest < Test::Unit::TestCase
1462
1675
  "rotate_wait" => "2s"
1463
1676
  }) + PARSE_SINGLE_LINE_CONFIG, false)
1464
1677
 
1465
- assert_equal readable_paths, d.instance.expand_paths.length
1466
- assert_equal result, d.instance.have_read_capability?
1678
+ assert_equal(readable_paths, d.instance.expand_paths.length)
1679
+ assert_equal(result, d.instance.have_read_capability?)
1467
1680
  end
1468
1681
  end
1469
1682
 
1470
1683
  def test_pos_file_dir_creation
1471
1684
  config = config_element("", "", {
1472
1685
  "tag" => "tail",
1473
- "path" => "#{TMP_DIR}/*.txt",
1686
+ "path" => "#{@tmp_dir}/*.txt",
1474
1687
  "format" => "none",
1475
- "pos_file" => "#{TMP_DIR}/pos/tail.pos",
1688
+ "pos_file" => "#{@tmp_dir}/pos/tail.pos",
1476
1689
  "read_from_head" => true,
1477
1690
  "refresh_interval" => 1
1478
1691
  })
1479
1692
 
1480
- assert_path_not_exist("#{TMP_DIR}/pos")
1693
+ assert_path_not_exist("#{@tmp_dir}/pos")
1481
1694
  d = create_driver(config, false)
1482
1695
  d.run
1483
- assert_path_exist("#{TMP_DIR}/pos")
1484
- assert_equal '755', File.stat("#{TMP_DIR}/pos").mode.to_s(8)[-3, 3]
1696
+ assert_path_exist("#{@tmp_dir}/pos")
1697
+ assert_equal('755', File.stat("#{@tmp_dir}/pos").mode.to_s(8)[-3, 3])
1485
1698
  ensure
1486
- cleanup_directory(TMP_DIR)
1699
+ cleanup_directory(@tmp_dir)
1487
1700
  end
1488
1701
 
1489
1702
  def test_pos_file_dir_creation_with_system_dir_permission
1490
1703
  config = config_element("", "", {
1491
1704
  "tag" => "tail",
1492
- "path" => "#{TMP_DIR}/*.txt",
1705
+ "path" => "#{@tmp_dir}/*.txt",
1493
1706
  "format" => "none",
1494
- "pos_file" => "#{TMP_DIR}/pos/tail.pos",
1707
+ "pos_file" => "#{@tmp_dir}/pos/tail.pos",
1495
1708
  "read_from_head" => true,
1496
1709
  "refresh_interval" => 1
1497
1710
  })
1498
1711
 
1499
- assert_path_not_exist("#{TMP_DIR}/pos")
1712
+ assert_path_not_exist("#{@tmp_dir}/pos")
1500
1713
 
1501
1714
  Fluent::SystemConfig.overwrite_system_config({ "dir_permission" => "744" }) do
1502
1715
  d = create_driver(config, false)
1503
1716
  d.run
1504
1717
  end
1505
1718
 
1506
- assert_path_exist("#{TMP_DIR}/pos")
1719
+ assert_path_exist("#{@tmp_dir}/pos")
1507
1720
  if Fluent.windows?
1508
- assert_equal '755', File.stat("#{TMP_DIR}/pos").mode.to_s(8)[-3, 3]
1721
+ assert_equal('755', File.stat("#{@tmp_dir}/pos").mode.to_s(8)[-3, 3])
1509
1722
  else
1510
- assert_equal '744', File.stat("#{TMP_DIR}/pos").mode.to_s(8)[-3, 3]
1723
+ assert_equal('744', File.stat("#{@tmp_dir}/pos").mode.to_s(8)[-3, 3])
1511
1724
  end
1512
1725
  ensure
1513
- cleanup_directory(TMP_DIR)
1726
+ cleanup_directory(@tmp_dir)
1514
1727
  end
1515
1728
 
1516
1729
  def test_z_refresh_watchers
@@ -1519,30 +1732,36 @@ class TailInputTest < Test::Unit::TestCase
1519
1732
  create_target_info('test/plugin/data/log/foo/bar.log'),
1520
1733
  create_target_info('test/plugin/data/log/test.log'),
1521
1734
  ]
1522
- plugin = create_driver(EX_CONFIG, false).instance
1735
+ plugin = create_driver(ex_config, false).instance
1523
1736
  sio = StringIO.new
1524
1737
  plugin.instance_eval do
1525
1738
  @pf = Fluent::Plugin::TailInput::PositionFile.load(sio, EX_FOLLOW_INODES, {}, logger: $log)
1526
1739
  @loop = Coolio::Loop.new
1740
+ opened_file_metrics = Fluent::Plugin::LocalMetrics.new
1741
+ opened_file_metrics.configure(config_element('metrics', '', {}))
1742
+ closed_file_metrics = Fluent::Plugin::LocalMetrics.new
1743
+ closed_file_metrics.configure(config_element('metrics', '', {}))
1744
+ rotated_file_metrics = Fluent::Plugin::LocalMetrics.new
1745
+ rotated_file_metrics.configure(config_element('metrics', '', {}))
1746
+ @metrics = Fluent::Plugin::TailInput::MetricsInfo.new(opened_file_metrics, closed_file_metrics, rotated_file_metrics)
1527
1747
  end
1528
1748
 
1529
1749
  Timecop.freeze(2010, 1, 2, 3, 4, 5) do
1530
1750
  ex_paths.each do |target_info|
1531
- mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything).once
1751
+ mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything, anything).once
1532
1752
  end
1533
1753
 
1534
1754
  plugin.refresh_watchers
1535
1755
  end
1536
1756
 
1537
1757
  path = 'test/plugin/data/2010/01/20100102-030405.log'
1538
- target_info = Fluent::Plugin::TailInput::TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
1539
- mock.proxy(plugin).detach_watcher_after_rotate_wait(plugin.instance_variable_get(:@tails)[target_info], target_info.ino)
1758
+ mock.proxy(plugin).detach_watcher_after_rotate_wait(plugin.instance_variable_get(:@tails)[path], Fluent::FileWrapper.stat(path).ino)
1540
1759
 
1541
1760
  Timecop.freeze(2010, 1, 2, 3, 4, 6) do
1542
1761
  path = "test/plugin/data/2010/01/20100102-030406.log"
1543
1762
  inode = Fluent::FileWrapper.stat(path).ino
1544
1763
  target_info = Fluent::Plugin::TailInput::TargetInfo.new(path, inode)
1545
- mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything).once
1764
+ mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything, anything).once
1546
1765
  plugin.refresh_watchers
1547
1766
 
1548
1767
  flexstub(Fluent::Plugin::TailInput::TailWatcher) do |watcherclass|
@@ -1556,9 +1775,9 @@ class TailInputTest < Test::Unit::TestCase
1556
1775
  test 'type of pos_file_compaction_interval is time' do
1557
1776
  tail = {
1558
1777
  "tag" => "tail",
1559
- "path" => "#{TMP_DIR}/*.txt",
1778
+ "path" => "#{@tmp_dir}/*.txt",
1560
1779
  "format" => "none",
1561
- "pos_file" => "#{TMP_DIR}/pos/tail.pos",
1780
+ "pos_file" => "#{@tmp_dir}/pos/tail.pos",
1562
1781
  "refresh_interval" => 1,
1563
1782
  "read_from_head" => true,
1564
1783
  'pos_file_compaction_interval' => '24h',
@@ -1575,7 +1794,7 @@ class TailInputTest < Test::Unit::TestCase
1575
1794
  DummyWatcher = Struct.new("DummyWatcher", :tag)
1576
1795
 
1577
1796
  def test_tag
1578
- d = create_driver(EX_CONFIG, false)
1797
+ d = create_driver(ex_config, false)
1579
1798
  d.run {}
1580
1799
  plugin = d.instance
1581
1800
  mock(plugin.router).emit_stream('tail', anything).once
@@ -1651,12 +1870,47 @@ class TailInputTest < Test::Unit::TestCase
1651
1870
  mock(plugin.router).emit_stream('pre.foo.bar.log.post', anything).once
1652
1871
  plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
1653
1872
  end
1873
+
1874
+ data(
1875
+ small: ["128", 128],
1876
+ KiB: ["1k", 1024]
1877
+ )
1878
+ test 'max_line_size' do |(label, size)|
1879
+ config = config_element("", "", {
1880
+ "tag" => "max_line_size",
1881
+ "path" => "#{@tmp_dir}/with_long_lines.txt",
1882
+ "format" => "none",
1883
+ "read_from_head" => true,
1884
+ "max_line_size" => label,
1885
+ "log_level" => "debug"
1886
+ })
1887
+ Fluent::FileWrapper.open("#{@tmp_dir}/with_long_lines.txt", "w+") do |f|
1888
+ f.puts "foo"
1889
+ f.puts "x" * size # 'x' * size + \n > @max_line_size
1890
+ f.puts "bar"
1891
+ end
1892
+ d = create_driver(config, false)
1893
+ timestamp = Time.parse("Mon Nov 29 11:22:33 UTC 2021")
1894
+ Timecop.freeze(timestamp)
1895
+ d.run(expect_records: 2)
1896
+ assert_equal([
1897
+ [{"message" => "foo"},{"message" => "bar"}],
1898
+ [
1899
+ "2021-11-29 11:22:33 +0000 [warn]: received line length is longer than #{size}\n",
1900
+ "2021-11-29 11:22:33 +0000 [debug]: skipped line: #{'x' * size}\n"
1901
+ ]
1902
+ ],
1903
+ [
1904
+ d.events.collect { |event| event.last },
1905
+ d.logs[-2..]
1906
+ ])
1907
+ end
1654
1908
  end
1655
1909
 
1656
1910
  # Ensure that no fatal exception is raised when a file is missing and that
1657
1911
  # files that do exist are still tailed as expected.
1658
1912
  def test_missing_file
1659
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1913
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
1660
1914
  f.puts "test1"
1661
1915
  f.puts "test2"
1662
1916
  }
@@ -1665,16 +1919,16 @@ class TailInputTest < Test::Unit::TestCase
1665
1919
  # since their interactions with the filesystem differ.
1666
1920
  config1 = config_element("", "", {
1667
1921
  "tag" => "t1",
1668
- "path" => "#{TMP_DIR}/non_existent_file.txt,#{TMP_DIR}/tail.txt",
1922
+ "path" => "#{@tmp_dir}/non_existent_file.txt,#{@tmp_dir}/tail.txt",
1669
1923
  "format" => "none",
1670
1924
  "rotate_wait" => "2s",
1671
- "pos_file" => "#{TMP_DIR}/tail.pos"
1925
+ "pos_file" => "#{@tmp_dir}/tail.pos"
1672
1926
  })
1673
1927
  config2 = config1 + config_element("", "", { "read_from_head" => true })
1674
1928
  [config1, config2].each do |config|
1675
1929
  d = create_driver(config, false)
1676
1930
  d.run(expect_emits: 1) do
1677
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
1931
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
1678
1932
  f.puts "test3"
1679
1933
  f.puts "test4"
1680
1934
  }
@@ -1690,19 +1944,19 @@ class TailInputTest < Test::Unit::TestCase
1690
1944
 
1691
1945
  sub_test_case 'inode_processing' do
1692
1946
  def test_should_delete_file_pos_entry_for_non_existing_file_with_follow_inodes
1693
- config = COMMON_FOLLOW_INODE_CONFIG
1947
+ config = common_follow_inode_config
1694
1948
 
1695
- path = "#{TMP_DIR}/tail.txt"
1949
+ path = "#{@tmp_dir}/tail.txt"
1696
1950
  ino = 1
1697
1951
  pos = 1234
1698
- File.open("#{TMP_DIR}/tail.pos", "wb") {|f|
1952
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "wb") {|f|
1699
1953
  f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
1700
1954
  }
1701
1955
 
1702
1956
  d = create_driver(config, false)
1703
1957
  d.run
1704
1958
 
1705
- pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
1959
+ pos_file = Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r")
1706
1960
  pos_file.pos = 0
1707
1961
 
1708
1962
  assert_raise(EOFError) do
@@ -1711,21 +1965,21 @@ class TailInputTest < Test::Unit::TestCase
1711
1965
  end
1712
1966
 
1713
1967
  def test_should_write_latest_offset_after_rotate_wait
1714
- config = COMMON_FOLLOW_INODE_CONFIG
1715
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1968
+ config = common_follow_inode_config
1969
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
1716
1970
  f.puts "test1"
1717
1971
  f.puts "test2"
1718
1972
  }
1719
1973
 
1720
1974
  d = create_driver(config, false)
1721
1975
  d.run(expect_emits: 2, shutdown: false) do
1722
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1723
- FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt" + "1")
1976
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
1977
+ FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt" + "1")
1724
1978
  sleep 1
1725
- File.open("#{TMP_DIR}/tail.txt" + "1", "ab") {|f| f.puts "test4\n"}
1979
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt" + "1", "ab") {|f| f.puts "test4\n"}
1726
1980
  end
1727
1981
 
1728
- pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
1982
+ pos_file = Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r")
1729
1983
  pos_file.pos = 0
1730
1984
  line_parts = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
1731
1985
  waiting(5) {
@@ -1739,47 +1993,28 @@ class TailInputTest < Test::Unit::TestCase
1739
1993
  d.instance_shutdown
1740
1994
  end
1741
1995
 
1742
- def test_should_keep_and_update_existing_file_pos_entry_for_deleted_file_when_new_file_with_same_name_created
1996
+ def test_should_remove_deleted_file
1743
1997
  config = config_element("", "", {"format" => "none"})
1744
1998
 
1745
- path = "#{TMP_DIR}/tail.txt"
1999
+ path = "#{@tmp_dir}/tail.txt"
1746
2000
  ino = 1
1747
2001
  pos = 1234
1748
- File.open("#{TMP_DIR}/tail.pos", "wb") {|f|
2002
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "wb") {|f|
1749
2003
  f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
1750
2004
  }
1751
2005
 
1752
2006
  d = create_driver(config)
1753
- d.run(shutdown: false)
1754
-
1755
- pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
1756
- pos_file.pos = 0
1757
-
1758
- path_pos_ino = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
1759
- assert_equal(path, path_pos_ino[1])
1760
- assert_equal(pos, path_pos_ino[2].to_i(16))
1761
- assert_equal(ino, path_pos_ino[3].to_i(16))
1762
-
1763
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1764
- f.puts "test1"
1765
- f.puts "test2"
1766
- }
1767
- Timecop.travel(Time.now + 10) do
1768
- sleep 5
2007
+ d.run do
2008
+ pos_file = Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r")
1769
2009
  pos_file.pos = 0
1770
- tuple = create_target_info("#{TMP_DIR}/tail.txt")
1771
- path_pos_ino = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
1772
- assert_equal(tuple.path, path_pos_ino[1])
1773
- assert_equal(12, path_pos_ino[2].to_i(16))
1774
- assert_equal(tuple.ino, path_pos_ino[3].to_i(16))
2010
+ assert_equal([], pos_file.readlines)
1775
2011
  end
1776
- d.instance_shutdown
1777
2012
  end
1778
2013
 
1779
2014
  def test_should_mark_file_unwatched_after_limit_recently_modified_and_rotate_wait
1780
2015
  config = config_element("ROOT", "", {
1781
- "path" => "#{TMP_DIR}/tail.txt*",
1782
- "pos_file" => "#{TMP_DIR}/tail.pos",
2016
+ "path" => "#{@tmp_dir}/tail.txt*",
2017
+ "pos_file" => "#{@tmp_dir}/tail.pos",
1783
2018
  "tag" => "t1",
1784
2019
  "rotate_wait" => "1s",
1785
2020
  "refresh_interval" => "1s",
@@ -1791,14 +2026,14 @@ class TailInputTest < Test::Unit::TestCase
1791
2026
 
1792
2027
  d = create_driver(config, false)
1793
2028
 
1794
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2029
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
1795
2030
  f.puts "test1"
1796
2031
  f.puts "test2"
1797
2032
  }
1798
- target_info = create_target_info("#{TMP_DIR}/tail.txt")
2033
+ target_info = create_target_info("#{@tmp_dir}/tail.txt")
1799
2034
 
1800
2035
  d.run(expect_emits: 1, shutdown: false) do
1801
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
2036
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
1802
2037
  end
1803
2038
 
1804
2039
 
@@ -1816,8 +2051,8 @@ class TailInputTest < Test::Unit::TestCase
1816
2051
 
1817
2052
  def test_should_read_from_head_on_file_renaming_with_star_in_pattern
1818
2053
  config = config_element("ROOT", "", {
1819
- "path" => "#{TMP_DIR}/tail.txt*",
1820
- "pos_file" => "#{TMP_DIR}/tail.pos",
2054
+ "path" => "#{@tmp_dir}/tail.txt*",
2055
+ "pos_file" => "#{@tmp_dir}/tail.pos",
1821
2056
  "tag" => "t1",
1822
2057
  "rotate_wait" => "10s",
1823
2058
  "refresh_interval" => "1s",
@@ -1829,14 +2064,14 @@ class TailInputTest < Test::Unit::TestCase
1829
2064
 
1830
2065
  d = create_driver(config, false)
1831
2066
 
1832
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2067
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
1833
2068
  f.puts "test1"
1834
2069
  f.puts "test2"
1835
2070
  }
1836
2071
 
1837
2072
  d.run(expect_emits: 2, shutdown: false) do
1838
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1839
- FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt1")
2073
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
2074
+ FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt1")
1840
2075
  end
1841
2076
 
1842
2077
  events = d.events
@@ -1845,20 +2080,20 @@ class TailInputTest < Test::Unit::TestCase
1845
2080
  end
1846
2081
 
1847
2082
  def test_should_not_read_from_head_on_rotation_when_watching_inodes
1848
- config = COMMON_FOLLOW_INODE_CONFIG
2083
+ config = common_follow_inode_config
1849
2084
 
1850
2085
  d = create_driver(config, false)
1851
2086
 
1852
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2087
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
1853
2088
  f.puts "test1"
1854
2089
  f.puts "test2"
1855
2090
  }
1856
2091
 
1857
2092
  d.run(expect_emits: 1, shutdown: false) do
1858
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
2093
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
1859
2094
  end
1860
2095
 
1861
- FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt1")
2096
+ FileUtils.move("#{@tmp_dir}/tail.txt", "#{@tmp_dir}/tail.txt1")
1862
2097
  Timecop.travel(Time.now + 10) do
1863
2098
  sleep 2
1864
2099
  events = d.events
@@ -1869,23 +2104,23 @@ class TailInputTest < Test::Unit::TestCase
1869
2104
  end
1870
2105
 
1871
2106
  def test_should_mark_file_unwatched_if_same_name_file_created_with_different_inode
1872
- config = COMMON_FOLLOW_INODE_CONFIG
2107
+ config = common_follow_inode_config
1873
2108
 
1874
2109
  d = create_driver(config, false)
1875
2110
 
1876
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2111
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
1877
2112
  f.puts "test1"
1878
2113
  f.puts "test2"
1879
2114
  }
1880
- target_info = create_target_info("#{TMP_DIR}/tail.txt")
2115
+ target_info = create_target_info("#{@tmp_dir}/tail.txt")
1881
2116
 
1882
2117
  d.run(expect_emits: 2, shutdown: false) do
1883
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1884
- cleanup_file("#{TMP_DIR}/tail.txt")
1885
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f| f.puts "test4\n"}
2118
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
2119
+ cleanup_file("#{@tmp_dir}/tail.txt")
2120
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "test4\n"}
1886
2121
  end
1887
2122
 
1888
- new_target_info = create_target_info("#{TMP_DIR}/tail.txt")
2123
+ new_target_info = create_target_info("#{@tmp_dir}/tail.txt")
1889
2124
 
1890
2125
  pos_file = d.instance.instance_variable_get(:@pf)
1891
2126
 
@@ -1901,56 +2136,65 @@ class TailInputTest < Test::Unit::TestCase
1901
2136
 
1902
2137
  def test_should_close_watcher_after_rotate_wait
1903
2138
  now = Time.now
1904
- config = COMMON_FOLLOW_INODE_CONFIG + config_element('', '', {"rotate_wait" => "1s", "limit_recently_modified" => "1s"})
2139
+ config = common_follow_inode_config + config_element('', '', {"rotate_wait" => "1s", "limit_recently_modified" => "1s"})
1905
2140
 
1906
2141
  d = create_driver(config, false)
2142
+ d.instance.instance_eval do
2143
+ opened_file_metrics = Fluent::Plugin::LocalMetrics.new
2144
+ opened_file_metrics.configure(config_element('metrics', '', {}))
2145
+ closed_file_metrics = Fluent::Plugin::LocalMetrics.new
2146
+ closed_file_metrics.configure(config_element('metrics', '', {}))
2147
+ rotated_file_metrics = Fluent::Plugin::LocalMetrics.new
2148
+ rotated_file_metrics.configure(config_element('metrics', '', {}))
2149
+ @metrics = Fluent::Plugin::TailInput::MetricsInfo.new(opened_file_metrics, closed_file_metrics, rotated_file_metrics)
2150
+ end
1907
2151
 
1908
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2152
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
1909
2153
  f.puts "test1"
1910
2154
  f.puts "test2"
1911
2155
  }
1912
- target_info = create_target_info("#{TMP_DIR}/tail.txt")
1913
- mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, true, anything, nil, anything).once
2156
+ target_info = create_target_info("#{@tmp_dir}/tail.txt")
2157
+ mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, true, anything, nil, anything, anything).once
1914
2158
  d.run(shutdown: false)
1915
- assert d.instance.instance_variable_get(:@tails)[target_info]
2159
+ assert d.instance.instance_variable_get(:@tails)[target_info.path]
1916
2160
 
1917
2161
  Timecop.travel(now + 10) do
1918
2162
  d.instance.instance_eval do
1919
- sleep 0.1 until @tails[target_info] == nil
2163
+ sleep 0.1 until @tails[target_info.path] == nil
1920
2164
  end
1921
- assert_nil d.instance.instance_variable_get(:@tails)[target_info]
2165
+ assert_nil d.instance.instance_variable_get(:@tails)[target_info.path]
1922
2166
  end
1923
2167
  d.instance_shutdown
1924
2168
  end
1925
2169
 
1926
2170
  def test_should_create_new_watcher_for_new_file_with_same_name
1927
2171
  now = Time.now
1928
- config = COMMON_FOLLOW_INODE_CONFIG + config_element('', '', {"limit_recently_modified" => "2s"})
2172
+ config = common_follow_inode_config + config_element('', '', {"limit_recently_modified" => "2s"})
1929
2173
 
1930
2174
  d = create_driver(config, false)
1931
2175
 
1932
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2176
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
1933
2177
  f.puts "test1"
1934
2178
  f.puts "test2"
1935
2179
  }
1936
- path_ino = create_target_info("#{TMP_DIR}/tail.txt")
2180
+ path_ino = create_target_info("#{@tmp_dir}/tail.txt")
1937
2181
 
1938
2182
  d.run(expect_emits: 1, shutdown: false) do
1939
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
2183
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
1940
2184
  end
1941
2185
 
1942
- cleanup_file("#{TMP_DIR}/tail.txt")
1943
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2186
+ cleanup_file("#{@tmp_dir}/tail.txt")
2187
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
1944
2188
  f.puts "test3"
1945
2189
  f.puts "test4"
1946
2190
  }
1947
- new_path_ino = create_target_info("#{TMP_DIR}/tail.txt")
2191
+ new_path_ino = create_target_info("#{@tmp_dir}/tail.txt")
1948
2192
 
1949
2193
  Timecop.travel(now + 10) do
1950
2194
  sleep 3
1951
2195
  d.instance.instance_eval do
1952
- @tails[path_ino] == nil
1953
- @tails[new_path_ino] != nil
2196
+ @tails[path_ino.path] == nil
2197
+ @tails[new_path_ino.path] != nil
1954
2198
  end
1955
2199
  end
1956
2200
 
@@ -1962,19 +2206,19 @@ class TailInputTest < Test::Unit::TestCase
1962
2206
  end
1963
2207
 
1964
2208
  def test_truncate_file_with_follow_inodes
1965
- config = COMMON_FOLLOW_INODE_CONFIG
2209
+ config = common_follow_inode_config
1966
2210
 
1967
2211
  d = create_driver(config, false)
1968
2212
 
1969
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2213
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
1970
2214
  f.puts "test1"
1971
2215
  f.puts "test2"
1972
2216
  }
1973
2217
 
1974
2218
  d.run(expect_emits: 3, shutdown: false) do
1975
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
2219
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f| f.puts "test3\n"}
1976
2220
  sleep 2
1977
- File.open("#{TMP_DIR}/tail.txt", "w+b") {|f| f.puts "test4\n"}
2221
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "w+b") {|f| f.puts "test4\n"}
1978
2222
  end
1979
2223
 
1980
2224
  events = d.events
@@ -1988,15 +2232,15 @@ class TailInputTest < Test::Unit::TestCase
1988
2232
 
1989
2233
  # issue #3464
1990
2234
  def test_should_replace_target_info
1991
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2235
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
1992
2236
  f.puts "test1\n"
1993
2237
  }
1994
- target_info = create_target_info("#{TMP_DIR}/tail.txt")
2238
+ target_info = create_target_info("#{@tmp_dir}/tail.txt")
1995
2239
  inodes = []
1996
2240
 
1997
2241
  config = config_element("ROOT", "", {
1998
- "path" => "#{TMP_DIR}/tail.txt*",
1999
- "pos_file" => "#{TMP_DIR}/tail.pos",
2242
+ "path" => "#{@tmp_dir}/tail.txt*",
2243
+ "pos_file" => "#{@tmp_dir}/tail.pos",
2000
2244
  "tag" => "t1",
2001
2245
  "refresh_interval" => "60s",
2002
2246
  "read_from_head" => "true",
@@ -2009,21 +2253,21 @@ class TailInputTest < Test::Unit::TestCase
2009
2253
  while d.events.size < 1 do
2010
2254
  sleep 0.1
2011
2255
  end
2012
- inodes = d.instance.instance_variable_get(:@tails).keys.collect do |key|
2013
- key.ino
2256
+ inodes = d.instance.instance_variable_get(:@tails).values.collect do |tw|
2257
+ tw.ino
2014
2258
  end
2015
2259
  assert_equal([target_info.ino], inodes)
2016
2260
 
2017
- cleanup_file("#{TMP_DIR}/tail.txt")
2018
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f| f.puts "test2\n"}
2261
+ cleanup_file("#{@tmp_dir}/tail.txt")
2262
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f| f.puts "test2\n"}
2019
2263
 
2020
2264
  while d.events.size < 2 do
2021
2265
  sleep 0.1
2022
2266
  end
2023
- inodes = d.instance.instance_variable_get(:@tails).keys.collect do |key|
2024
- key.ino
2267
+ inodes = d.instance.instance_variable_get(:@tails).values.collect do |tw|
2268
+ tw.ino
2025
2269
  end
2026
- new_target_info = create_target_info("#{TMP_DIR}/tail.txt")
2270
+ new_target_info = create_target_info("#{@tmp_dir}/tail.txt")
2027
2271
  assert_not_equal(target_info.ino, new_target_info.ino)
2028
2272
  assert_equal([new_target_info.ino], inodes)
2029
2273
  end
@@ -2032,7 +2276,7 @@ class TailInputTest < Test::Unit::TestCase
2032
2276
 
2033
2277
  sub_test_case "tail_path" do
2034
2278
  def test_tail_path_with_singleline
2035
- File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2279
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") {|f|
2036
2280
  f.puts "test1"
2037
2281
  f.puts "test2"
2038
2282
  }
@@ -2040,7 +2284,7 @@ class TailInputTest < Test::Unit::TestCase
2040
2284
  d = create_driver(SINGLE_LINE_CONFIG + config_element("", "", { "path_key" => "path" }))
2041
2285
 
2042
2286
  d.run(expect_emits: 1) do
2043
- File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
2287
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") {|f|
2044
2288
  f.puts "test3"
2045
2289
  f.puts "test4"
2046
2290
  }
@@ -2049,12 +2293,12 @@ class TailInputTest < Test::Unit::TestCase
2049
2293
  events = d.events
2050
2294
  assert_equal(true, events.length > 0)
2051
2295
  events.each do |emit|
2052
- assert_equal("#{TMP_DIR}/tail.txt", emit[2]["path"])
2296
+ assert_equal("#{@tmp_dir}/tail.txt", emit[2]["path"])
2053
2297
  end
2054
2298
  end
2055
2299
 
2056
2300
  def test_tail_path_with_multiline_with_firstline
2057
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
2301
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
2058
2302
 
2059
2303
  config = config_element("", "", {
2060
2304
  "path_key" => "path",
@@ -2064,7 +2308,7 @@ class TailInputTest < Test::Unit::TestCase
2064
2308
  })
2065
2309
  d = create_driver(config)
2066
2310
  d.run(expect_emits: 1) do
2067
- File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
2311
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
2068
2312
  f.puts "f test1"
2069
2313
  f.puts "s test2"
2070
2314
  f.puts "f test3"
@@ -2079,12 +2323,12 @@ class TailInputTest < Test::Unit::TestCase
2079
2323
  events = d.events
2080
2324
  assert_equal(4, events.length)
2081
2325
  events.each do |emit|
2082
- assert_equal("#{TMP_DIR}/tail.txt", emit[2]["path"])
2326
+ assert_equal("#{@tmp_dir}/tail.txt", emit[2]["path"])
2083
2327
  end
2084
2328
  end
2085
2329
 
2086
2330
  def test_tail_path_with_multiline_without_firstline
2087
- File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
2331
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") { |f| }
2088
2332
 
2089
2333
  config = config_element("", "", {
2090
2334
  "path_key" => "path",
@@ -2095,7 +2339,7 @@ class TailInputTest < Test::Unit::TestCase
2095
2339
  })
2096
2340
  d = create_driver(config)
2097
2341
  d.run(expect_emits: 1) do
2098
- File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
2342
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "ab") { |f|
2099
2343
  f.puts "foo 1"
2100
2344
  f.puts "bar 1"
2101
2345
  f.puts "baz 1"
@@ -2105,7 +2349,7 @@ class TailInputTest < Test::Unit::TestCase
2105
2349
  events = d.events
2106
2350
  assert(events.length > 0)
2107
2351
  events.each do |emit|
2108
- assert_equal("#{TMP_DIR}/tail.txt", emit[2]["path"])
2352
+ assert_equal("#{@tmp_dir}/tail.txt", emit[2]["path"])
2109
2353
  end
2110
2354
  end
2111
2355
 
@@ -2113,8 +2357,8 @@ class TailInputTest < Test::Unit::TestCase
2113
2357
  if ENV["APPVEYOR"] && Fluent.windows?
2114
2358
  omit "This testcase is unstable on AppVeyor."
2115
2359
  end
2116
- files = ["#{TMP_DIR}/tail1.txt", "#{TMP_DIR}/tail2.txt"]
2117
- files.each { |file| File.open(file, "wb") { |f| } }
2360
+ files = ["#{@tmp_dir}/tail1.txt", "#{@tmp_dir}/tail2.txt"]
2361
+ files.each { |file| Fluent::FileWrapper.open(file, "wb") { |f| } }
2118
2362
 
2119
2363
  config = config_element("", "", {
2120
2364
  "path" => "#{files[0]},#{files[1]}",
@@ -2127,7 +2371,7 @@ class TailInputTest < Test::Unit::TestCase
2127
2371
  d = create_driver(config, false)
2128
2372
  d.run(expect_emits: 2) do
2129
2373
  files.each do |file|
2130
- File.open(file, 'ab') { |f|
2374
+ Fluent::FileWrapper.open(file, 'ab') { |f|
2131
2375
  f.puts "f #{file} line should be ignored"
2132
2376
  f.puts "s test1"
2133
2377
  f.puts "f test2"
@@ -2147,30 +2391,30 @@ class TailInputTest < Test::Unit::TestCase
2147
2391
 
2148
2392
  def test_limit_recently_modified
2149
2393
  now = Time.new(2010, 1, 2, 3, 4, 5)
2150
- FileUtils.touch("#{TMP_DIR}/tail_unwatch.txt", mtime: (now - 3601))
2151
- FileUtils.touch("#{TMP_DIR}/tail_watch1.txt", mtime: (now - 3600))
2152
- FileUtils.touch("#{TMP_DIR}/tail_watch2.txt", mtime: now)
2394
+ FileUtils.touch("#{@tmp_dir}/tail_unwatch.txt", mtime: (now - 3601))
2395
+ FileUtils.touch("#{@tmp_dir}/tail_watch1.txt", mtime: (now - 3600))
2396
+ FileUtils.touch("#{@tmp_dir}/tail_watch2.txt", mtime: now)
2153
2397
 
2154
2398
  config = config_element('', '', {
2155
2399
  'tag' => 'tail',
2156
- 'path' => "#{TMP_DIR}/*.txt",
2400
+ 'path' => "#{@tmp_dir}/*.txt",
2157
2401
  'format' => 'none',
2158
2402
  'limit_recently_modified' => '3600s'
2159
2403
  })
2160
2404
 
2161
2405
  expected_files = [
2162
- create_target_info("#{TMP_DIR}/tail_watch1.txt"),
2163
- create_target_info("#{TMP_DIR}/tail_watch2.txt")
2406
+ create_target_info("#{@tmp_dir}/tail_watch1.txt"),
2407
+ create_target_info("#{@tmp_dir}/tail_watch2.txt")
2164
2408
  ]
2165
2409
 
2166
2410
  Timecop.freeze(now) do
2167
2411
  plugin = create_driver(config, false).instance
2168
- assert_equal expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
2412
+ assert_equal(expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
2169
2413
  end
2170
2414
  end
2171
2415
 
2172
2416
  def test_skip_refresh_on_startup
2173
- FileUtils.touch("#{TMP_DIR}/tail.txt")
2417
+ FileUtils.touch("#{@tmp_dir}/tail.txt")
2174
2418
  config = config_element('', '', {
2175
2419
  'format' => 'none',
2176
2420
  'refresh_interval' => 1,
@@ -2178,36 +2422,42 @@ class TailInputTest < Test::Unit::TestCase
2178
2422
  })
2179
2423
  d = create_driver(config)
2180
2424
  d.run(shutdown: false) {}
2181
- assert_equal 0, d.instance.instance_variable_get(:@tails).keys.size
2425
+ assert_equal(0, d.instance.instance_variable_get(:@tails).keys.size)
2182
2426
  # detect a file at first execution of in_tail_refresh_watchers timer
2183
2427
  waiting(5) { sleep 0.1 until d.instance.instance_variable_get(:@tails).keys.size == 1 }
2184
2428
  d.instance_shutdown
2185
2429
  end
2186
2430
 
2187
2431
  def test_ENOENT_error_after_setup_watcher
2188
- path = "#{TMP_DIR}/tail.txt"
2432
+ path = "#{@tmp_dir}/tail.txt"
2189
2433
  FileUtils.touch(path)
2190
2434
  config = config_element('', '', {
2191
2435
  'format' => 'none',
2192
2436
  })
2193
2437
  d = create_driver(config)
2194
- mock.proxy(d.instance).setup_watcher(anything, anything) do |tw|
2195
- cleanup_file(path)
2196
- tw
2197
- end
2438
+ file_deleted = false
2439
+ mock.proxy(d.instance).existence_path do |hash|
2440
+ unless file_deleted
2441
+ cleanup_file(path)
2442
+ file_deleted = true
2443
+ end
2444
+ hash
2445
+ end.twice
2198
2446
  assert_nothing_raised do
2199
2447
  d.run(shutdown: false) {}
2200
2448
  end
2201
- d.instance_shutdown
2202
- assert($log.out.logs.any?{|log| log.include?("stat() for #{path} failed with Errno::ENOENT. Drop tail watcher for now.\n") })
2449
+ assert($log.out.logs.any?{|log| log.include?("stat() for #{path} failed. Continuing without tailing it.\n") },
2450
+ $log.out.logs.join("\n"))
2451
+ ensure
2452
+ d.instance_shutdown if d && d.instance
2203
2453
  end
2204
2454
 
2205
2455
  def test_EACCES_error_after_setup_watcher
2206
2456
  omit "Cannot test with root user" if Process::UID.eid == 0
2207
- path = "#{TMP_DIR}/noaccess/tail.txt"
2457
+ path = "#{@tmp_dir}/noaccess/tail.txt"
2208
2458
  begin
2209
- FileUtils.mkdir_p("#{TMP_DIR}/noaccess")
2210
- FileUtils.chmod(0755, "#{TMP_DIR}/noaccess")
2459
+ FileUtils.mkdir_p("#{@tmp_dir}/noaccess")
2460
+ FileUtils.chmod(0755, "#{@tmp_dir}/noaccess")
2211
2461
  FileUtils.touch(path)
2212
2462
  config = config_element('', '', {
2213
2463
  'tag' => "tail",
@@ -2215,25 +2465,26 @@ class TailInputTest < Test::Unit::TestCase
2215
2465
  'format' => 'none',
2216
2466
  })
2217
2467
  d = create_driver(config, false)
2218
- mock.proxy(d.instance).setup_watcher(anything, anything) do |tw|
2219
- FileUtils.chmod(0000, "#{TMP_DIR}/noaccess")
2220
- tw
2221
- end
2468
+ mock.proxy(d.instance).existence_path do |hash|
2469
+ FileUtils.chmod(0000, "#{@tmp_dir}/noaccess")
2470
+ hash
2471
+ end.twice
2222
2472
  assert_nothing_raised do
2223
2473
  d.run(shutdown: false) {}
2224
2474
  end
2225
- d.instance_shutdown
2226
- assert($log.out.logs.any?{|log| log.include?("stat() for #{path} failed with Errno::EACCES. Drop tail watcher for now.\n") })
2475
+ assert($log.out.logs.any?{|log| log.include?("stat() for #{path} failed. Continuing without tailing it.\n") },
2476
+ $log.out.logs.join("\n"))
2227
2477
  end
2228
2478
  ensure
2229
- if File.exist?("#{TMP_DIR}/noaccess")
2230
- FileUtils.chmod(0755, "#{TMP_DIR}/noaccess")
2231
- FileUtils.rm_rf("#{TMP_DIR}/noaccess")
2479
+ d.instance_shutdown if d && d.instance
2480
+ if File.exist?("#{@tmp_dir}/noaccess")
2481
+ FileUtils.chmod(0755, "#{@tmp_dir}/noaccess")
2482
+ FileUtils.rm_rf("#{@tmp_dir}/noaccess")
2232
2483
  end
2233
2484
  end unless Fluent.windows?
2234
2485
 
2235
2486
  def test_EACCES
2236
- path = "#{TMP_DIR}/tail.txt"
2487
+ path = "#{@tmp_dir}/tail.txt"
2237
2488
  FileUtils.touch(path)
2238
2489
  config = config_element('', '', {
2239
2490
  'format' => 'none',
@@ -2245,13 +2496,14 @@ class TailInputTest < Test::Unit::TestCase
2245
2496
  assert_nothing_raised do
2246
2497
  d.run(shutdown: false) {}
2247
2498
  end
2248
- d.instance_shutdown
2249
2499
  assert($log.out.logs.any?{|log| log.include?("expand_paths: stat() for #{path} failed with Errno::EACCES. Skip file.\n") })
2500
+ ensure
2501
+ d.instance_shutdown if d && d.instance
2250
2502
  end
2251
2503
 
2252
2504
  def test_shutdown_timeout
2253
- path = "#{TMP_DIR}/tail.txt"
2254
- File.open("#{TMP_DIR}/tail.txt", "wb") do |f|
2505
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt", "wb") do |f|
2506
+ # Should be large enough to take too long time to consume
2255
2507
  (1024 * 1024 * 5).times do
2256
2508
  f.puts "{\"test\":\"fizzbuzz\"}"
2257
2509
  end
@@ -2263,18 +2515,774 @@ class TailInputTest < Test::Unit::TestCase
2263
2515
  'format' => 'json',
2264
2516
  'skip_refresh_on_startup' => true,
2265
2517
  })
2518
+ shutdown_start_time = 0
2519
+
2266
2520
  d = create_driver(config)
2267
2521
  mock.proxy(d.instance).io_handler(anything, anything) do |io_handler|
2522
+ mock.proxy(io_handler).ready_to_shutdown(anything) do
2523
+ shutdown_start_time = Fluent::Clock.now
2524
+ end
2268
2525
  io_handler.shutdown_timeout = 0.5
2269
2526
  io_handler
2270
2527
  end
2271
2528
 
2272
- start_time = Fluent::Clock.now
2273
2529
  assert_nothing_raised do
2274
2530
  d.run(expect_emits: 1)
2275
2531
  end
2276
2532
 
2277
- elapsed = Fluent::Clock.now - start_time
2278
- assert_true(elapsed > 0.5 && elapsed < 2.5)
2533
+ elapsed = Fluent::Clock.now - shutdown_start_time
2534
+ assert_true(elapsed > 0.5 && elapsed < 2.0,
2535
+ "elapsed time: #{elapsed}")
2536
+ end
2537
+
2538
+ sub_test_case "throttling logs at in_tail level" do
2539
+ data("file test1.log no_limit 5120 text: msg" => ["test1.log", 5120, "msg"],
2540
+ "file test2.log no_limit 1024 text: test" => ["test2.log", 1024, "test"])
2541
+ def test_lines_collected_with_no_throttling(data)
2542
+ file, num_lines, msg = data
2543
+
2544
+ pattern = "/^#{@tmp_dir}\/(?<file>.+)\.log$/"
2545
+ rule = create_rule_directive({
2546
+ "file" => "/test.*/",
2547
+ }, -1)
2548
+ group = create_group_directive(pattern, "1s", rule)
2549
+ path_element = create_path_element(file)
2550
+
2551
+ conf = ROOT_CONFIG + group + path_element + CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG
2552
+
2553
+ Fluent::FileWrapper.open("#{@tmp_dir}/#{file}", 'wb') do |f|
2554
+ num_lines.times do
2555
+ f.puts "#{msg}\n"
2556
+ end
2557
+ end
2558
+
2559
+
2560
+ d = create_driver(conf, false)
2561
+ d.run(timeout: 3) do
2562
+ start_time = Fluent::Clock.now
2563
+
2564
+ assert_equal(num_lines, d.record_count)
2565
+ assert_equal({ "message" => msg }, d.events[0][2])
2566
+
2567
+ prev_count = d.record_count
2568
+ sleep(0.1) while d.emit_count < 1
2569
+ assert_true(Fluent::Clock.now - start_time < 2)
2570
+ ## after waiting for 1 (+ jitter) secs, limit will reset
2571
+ ## Plugin will start reading but it will encounter EOF Error
2572
+ ## since no logs are left to be read
2573
+ ## Hence, d.record_count = prev_count
2574
+ tail_watcher_interval = 1.0 # hard coded value in in_tail
2575
+ safety_ratio = 1.2
2576
+ jitter = tail_watcher_interval * safety_ratio
2577
+ sleep(1.0 + jitter)
2578
+ assert_equal(0, d.record_count - prev_count)
2579
+ end
2580
+ end
2581
+
2582
+ test "lines collected with throttling" do
2583
+ file = "podname1_namespace12_container-123456.log"
2584
+ limit = 1000
2585
+ rate_period = 2
2586
+ num_lines = 3000
2587
+ msg = "a" * 8190 # Total size = 8190 bytes + 2 (\n) bytes
2588
+
2589
+ rule = create_rule_directive({
2590
+ "namespace"=> "/namespace.+/",
2591
+ "podname"=> "/podname.+/",
2592
+ }, limit)
2593
+ path_element = create_path_element(file)
2594
+ conf = ROOT_CONFIG + create_group_directive(tailing_group_pattern, "#{rate_period}s", rule) + path_element + SINGLE_LINE_CONFIG + CONFIG_READ_FROM_HEAD
2595
+
2596
+ d = create_driver(conf, false)
2597
+ file_path = "#{@tmp_dir}/#{file}"
2598
+
2599
+ Fluent::FileWrapper.open(file_path, 'wb') do |f|
2600
+ num_lines.times do
2601
+ f.puts msg
2602
+ end
2603
+ end
2604
+
2605
+ d.run(timeout: 15) do
2606
+ sleep_interval = 0.1
2607
+ tail_watcher_interval = 1.0 # hard coded value in in_tail
2608
+ safety_ratio = 1.2
2609
+ lower_jitter = sleep_interval * safety_ratio
2610
+ upper_jitter = (tail_watcher_interval + sleep_interval) * safety_ratio
2611
+ lower_interval = rate_period - lower_jitter
2612
+ upper_interval = rate_period + upper_jitter
2613
+
2614
+ emit_count = 0
2615
+ prev_count = 0
2616
+
2617
+ while emit_count < 3 do
2618
+ start_time = Fluent::Clock.now
2619
+ sleep(sleep_interval) while d.emit_count <= emit_count
2620
+ elapsed_seconds = Fluent::Clock.now - start_time
2621
+ if emit_count > 0
2622
+ assert_true(elapsed_seconds > lower_interval && elapsed_seconds < upper_interval,
2623
+ "elapsed_seconds #{elapsed_seconds} is out of allowed range:\n" +
2624
+ " lower: #{lower_interval} [sec]\n" +
2625
+ " upper: #{upper_interval} [sec]")
2626
+ end
2627
+ assert_equal(limit, d.record_count - prev_count)
2628
+ emit_count = d.emit_count
2629
+ prev_count = d.record_count
2630
+ end
2631
+
2632
+ ## When all the lines are read and rate_period seconds are over
2633
+ ## limit will reset and since there are no more logs to be read,
2634
+ ## number_lines_read will be 0
2635
+ sleep upper_interval
2636
+ gw = d.instance.find_group_from_metadata(file_path)
2637
+ assert_equal(0, gw.current_paths[file_path].number_lines_read)
2638
+ end
2639
+ end
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
+
3020
+ def test_next_rotation_occurs_very_fast_while_old_TW_still_waiting_rotate_wait
3021
+ config = config_element(
3022
+ "ROOT",
3023
+ "",
3024
+ {
3025
+ "path" => "#{@tmp_dir}/tail.txt*",
3026
+ "pos_file" => "#{@tmp_dir}/tail.pos",
3027
+ "tag" => "t1",
3028
+ "format" => "none",
3029
+ "read_from_head" => "true",
3030
+ "follow_inodes" => "true",
3031
+ "rotate_wait" => "3s",
3032
+ "refresh_interval" => "1h",
3033
+ # stat_watcher often calls `TailWatcher::on_notify` faster than creating a new log file,
3034
+ # so disable it in order to reproduce the same condition stably.
3035
+ "enable_stat_watcher" => "false",
3036
+ }
3037
+ )
3038
+ d = create_driver(config, false)
3039
+
3040
+ tail_watchers = []
3041
+ stub.proxy(d.instance).setup_watcher do |tw|
3042
+ tail_watchers.append(tw)
3043
+ mock.proxy(tw).close.once # Note: Currently, there is no harm in duplicate calls.
3044
+ tw
3045
+ end
3046
+
3047
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file1 log1"}
3048
+
3049
+ d.run(expect_records: 6, timeout: 15) do
3050
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file1 log2"}
3051
+
3052
+ sleep 1.5 # Need to be larger than 1s (the interval of watch_timer)
3053
+
3054
+ FileUtils.move("#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt" + "1")
3055
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file2 log1"}
3056
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file2 log2"}
3057
+
3058
+ sleep 1.5 # Need to be larger than 1s (the interval of watch_timer)
3059
+
3060
+ # Rotate again (Old TailWatcher waiting rotate_wait also calls update_watcher)
3061
+ [1, 0].each do |i|
3062
+ FileUtils.move("#{@tmp_dir}/tail.txt#{i}", "#{@tmp_dir}/tail.txt#{i + 1}")
3063
+ end
3064
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file3 log1"}
3065
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file3 log2"}
3066
+
3067
+ # Wait rotate_wait to confirm that TailWatcher.close is not called in duplicate.
3068
+ # (Note: Currently, there is no harm in duplicate calls)
3069
+ sleep 4
3070
+ end
3071
+
3072
+ inode_0 = tail_watchers[0]&.ino
3073
+ inode_1 = tail_watchers[1]&.ino
3074
+ inode_2 = tail_watchers[2]&.ino
3075
+ record_values = d.events.collect { |event| event[2]["message"] }.sort
3076
+ position_entries = []
3077
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
3078
+ f.readlines(chomp: true).each do |line|
3079
+ values = line.split("\t")
3080
+ position_entries.append([values[0], values[1], values[2].to_i(16)])
3081
+ end
3082
+ end
3083
+
3084
+ assert_equal(
3085
+ {
3086
+ record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2", "file3 log1", "file3 log2"],
3087
+ tail_watcher_paths: ["#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0"],
3088
+ tail_watcher_inodes: [inode_0, inode_1, inode_2],
3089
+ tail_watcher_io_handler_opened_statuses: [false, false, false],
3090
+ position_entries: [
3091
+ ["#{@tmp_dir}/tail.txt0", "0000000000000016", inode_0],
3092
+ ["#{@tmp_dir}/tail.txt0", "0000000000000016", inode_1],
3093
+ ["#{@tmp_dir}/tail.txt0", "0000000000000016", inode_2],
3094
+ ],
3095
+ },
3096
+ {
3097
+ record_values: record_values,
3098
+ tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
3099
+ tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
3100
+ tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
3101
+ position_entries: position_entries
3102
+ },
3103
+ )
3104
+ end
3105
+ end
3106
+
3107
+ sub_test_case "Update watchers for rotation without follow_inodes" do
3108
+ # The scenario where in_tail wrongly unwatches the PositionEntry.
3109
+ # This is reported in https://github.com/fluent/fluentd/issues/3614.
3110
+ def test_refreshTW_during_rotation
3111
+ config = config_element(
3112
+ "ROOT",
3113
+ "",
3114
+ {
3115
+ "path" => "#{@tmp_dir}/tail.txt0",
3116
+ "pos_file" => "#{@tmp_dir}/tail.pos",
3117
+ "tag" => "t1",
3118
+ "format" => "none",
3119
+ "read_from_head" => "true",
3120
+ # In order to detach the old watcher quickly.
3121
+ "rotate_wait" => "3s",
3122
+ # In order to reproduce the same condition stably, ensure that `refresh_watchers` is not
3123
+ # called by a timer.
3124
+ "refresh_interval" => "1h",
3125
+ # stat_watcher often calls `TailWatcher::on_notify` faster than creating a new log file,
3126
+ # so disable it in order to reproduce the same condition stably.
3127
+ "enable_stat_watcher" => "false",
3128
+ }
3129
+ )
3130
+ d = create_driver(config, false)
3131
+
3132
+ tail_watchers = []
3133
+ stub.proxy(d.instance).setup_watcher do |tw|
3134
+ tail_watchers.append(tw)
3135
+ tw
3136
+ end
3137
+
3138
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file1 log1"}
3139
+
3140
+ d.run(expect_records: 6, timeout: 15) do
3141
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file1 log2"}
3142
+ FileUtils.move("#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt" + "1")
3143
+
3144
+ # This reproduces the following situation:
3145
+ # `refresh_watchers` is called during the rotation process and it detects the current file being lost.
3146
+ # Then it stops and unwatches the TailWatcher.
3147
+ d.instance.refresh_watchers
3148
+
3149
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file2 log1"}
3150
+
3151
+ # `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` trys to add the new TailWatcher.
3152
+ # After `rotate_wait` interval, the PositionEntry is unwatched.
3153
+ # HOWEVER, the new TailWatcher is still using that PositionEntry, so this breaks the PositionFile!!
3154
+ # That PositionEntry is removed from `PositionFile::map`, but it is still working and remaining in the real pos file.
3155
+ sleep 5
3156
+
3157
+ # Append to the new current log file.
3158
+ # The PositionEntry is updated although it does not exist in `PositionFile::map`.
3159
+ # `PositionFile::map`: empty
3160
+ # Real pos file: `.../tail.txt 0000000000000016 (inode)`
3161
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file2 log2"}
3162
+
3163
+ # Rotate again
3164
+ [1, 0].each do |i|
3165
+ FileUtils.move("#{@tmp_dir}/tail.txt#{i}", "#{@tmp_dir}/tail.txt#{i + 1}")
3166
+ end
3167
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file3 log1"}
3168
+
3169
+ # `watch_timer` calls `TailWatcher::on_notify`, and then `update_watcher` trys to update the TailWatcher.
3170
+ sleep 3
3171
+
3172
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file3 log2"}
3173
+ end
3174
+
3175
+ inode_0 = tail_watchers[0]&.ino
3176
+ inode_1 = tail_watchers[1]&.ino
3177
+ inode_2 = tail_watchers[2]&.ino
3178
+ record_values = d.events.collect { |event| event[2]["message"] }.sort
3179
+ position_entries = []
3180
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
3181
+ f.readlines(chomp: true).each do |line|
3182
+ values = line.split("\t")
3183
+ position_entries.append([values[0], values[1], values[2].to_i(16)])
3184
+ end
3185
+ end
3186
+
3187
+ assert_equal(
3188
+ {
3189
+ record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2", "file3 log1", "file3 log2"],
3190
+ tail_watcher_paths: ["#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0"],
3191
+ tail_watcher_inodes: [inode_0, inode_1, inode_2],
3192
+ tail_watcher_io_handler_opened_statuses: [false, false, false],
3193
+ position_entries: [
3194
+ # The recorded path is old, but it is no problem. The path is not used when using follow_inodes.
3195
+ ["#{@tmp_dir}/tail.txt0", "0000000000000016", inode_2],
3196
+ ],
3197
+ },
3198
+ {
3199
+ record_values: record_values,
3200
+ tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
3201
+ tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
3202
+ tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
3203
+ position_entries: position_entries
3204
+ },
3205
+ )
3206
+ end
3207
+
3208
+ def test_next_rotation_occurs_very_fast_while_old_TW_still_waiting_rotate_wait
3209
+ config = config_element(
3210
+ "ROOT",
3211
+ "",
3212
+ {
3213
+ "path" => "#{@tmp_dir}/tail.txt0",
3214
+ "pos_file" => "#{@tmp_dir}/tail.pos",
3215
+ "tag" => "t1",
3216
+ "format" => "none",
3217
+ "read_from_head" => "true",
3218
+ "rotate_wait" => "3s",
3219
+ "refresh_interval" => "1h",
3220
+ }
3221
+ )
3222
+ d = create_driver(config, false)
3223
+
3224
+ tail_watchers = []
3225
+ stub.proxy(d.instance).setup_watcher do |tw|
3226
+ tail_watchers.append(tw)
3227
+ mock.proxy(tw).close.once # Note: Currently, there is no harm in duplicate calls.
3228
+ tw
3229
+ end
3230
+
3231
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file1 log1"}
3232
+
3233
+ d.run(expect_records: 6, timeout: 15) do
3234
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file1 log2"}
3235
+
3236
+ sleep 1.5 # Need to be larger than 1s (the interval of watch_timer)
3237
+
3238
+ FileUtils.move("#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt" + "1")
3239
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file2 log1"}
3240
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file2 log2"}
3241
+
3242
+ sleep 1.5 # Need to be larger than 1s (the interval of watch_timer)
3243
+
3244
+ # Rotate again (Old TailWatcher waiting rotate_wait also calls update_watcher)
3245
+ [1, 0].each do |i|
3246
+ FileUtils.move("#{@tmp_dir}/tail.txt#{i}", "#{@tmp_dir}/tail.txt#{i + 1}")
3247
+ end
3248
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "wb") {|f| f.puts "file3 log1"}
3249
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.txt0", "ab") {|f| f.puts "file3 log2"}
3250
+
3251
+ # Wait rotate_wait to confirm that TailWatcher.close is not called in duplicate.
3252
+ # (Note: Currently, there is no harm in duplicate calls)
3253
+ sleep 4
3254
+ end
3255
+
3256
+ inode_0 = tail_watchers[0]&.ino
3257
+ inode_1 = tail_watchers[1]&.ino
3258
+ inode_2 = tail_watchers[2]&.ino
3259
+ record_values = d.events.collect { |event| event[2]["message"] }.sort
3260
+ position_entries = []
3261
+ Fluent::FileWrapper.open("#{@tmp_dir}/tail.pos", "r") do |f|
3262
+ f.readlines(chomp: true).each do |line|
3263
+ values = line.split("\t")
3264
+ position_entries.append([values[0], values[1], values[2].to_i(16)])
3265
+ end
3266
+ end
3267
+
3268
+ assert_equal(
3269
+ {
3270
+ record_values: ["file1 log1", "file1 log2", "file2 log1", "file2 log2", "file3 log1", "file3 log2"],
3271
+ tail_watcher_paths: ["#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0", "#{@tmp_dir}/tail.txt0"],
3272
+ tail_watcher_inodes: [inode_0, inode_1, inode_2],
3273
+ tail_watcher_io_handler_opened_statuses: [false, false, false],
3274
+ position_entries: [
3275
+ ["#{@tmp_dir}/tail.txt0", "0000000000000016", inode_2],
3276
+ ],
3277
+ },
3278
+ {
3279
+ record_values: record_values,
3280
+ tail_watcher_paths: tail_watchers.collect { |tw| tw.path },
3281
+ tail_watcher_inodes: tail_watchers.collect { |tw| tw.ino },
3282
+ tail_watcher_io_handler_opened_statuses: tail_watchers.collect { |tw| tw.instance_variable_get(:@io_handler)&.opened? || false },
3283
+ position_entries: position_entries
3284
+ },
3285
+ )
3286
+ end
2279
3287
  end
2280
3288
  end