fluentd 1.14.4-x64-mingw-ucrt

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

Files changed (558) hide show
  1. checksums.yaml +7 -0
  2. data/.deepsource.toml +13 -0
  3. data/.drone.yml +35 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.yaml +70 -0
  5. data/.github/ISSUE_TEMPLATE/config.yml +5 -0
  6. data/.github/ISSUE_TEMPLATE/feature_request.yaml +38 -0
  7. data/.github/ISSUE_TEMPLATE.md +17 -0
  8. data/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  9. data/.github/workflows/issue-auto-closer.yml +12 -0
  10. data/.github/workflows/linux-test.yaml +36 -0
  11. data/.github/workflows/macos-test.yaml +30 -0
  12. data/.github/workflows/stale-actions.yml +22 -0
  13. data/.github/workflows/windows-test.yaml +46 -0
  14. data/.gitignore +30 -0
  15. data/.gitlab-ci.yml +103 -0
  16. data/ADOPTERS.md +5 -0
  17. data/AUTHORS +2 -0
  18. data/CHANGELOG.md +2409 -0
  19. data/CONTRIBUTING.md +45 -0
  20. data/GOVERNANCE.md +55 -0
  21. data/Gemfile +9 -0
  22. data/GithubWorkflow.md +78 -0
  23. data/LICENSE +202 -0
  24. data/MAINTAINERS.md +11 -0
  25. data/README.md +97 -0
  26. data/Rakefile +79 -0
  27. data/SECURITY.md +18 -0
  28. data/bin/fluent-binlog-reader +7 -0
  29. data/bin/fluent-ca-generate +6 -0
  30. data/bin/fluent-cap-ctl +7 -0
  31. data/bin/fluent-cat +5 -0
  32. data/bin/fluent-ctl +7 -0
  33. data/bin/fluent-debug +5 -0
  34. data/bin/fluent-gem +9 -0
  35. data/bin/fluent-plugin-config-format +5 -0
  36. data/bin/fluent-plugin-generate +5 -0
  37. data/bin/fluentd +15 -0
  38. data/code-of-conduct.md +3 -0
  39. data/docs/SECURITY_AUDIT.pdf +0 -0
  40. data/example/copy_roundrobin.conf +39 -0
  41. data/example/counter.conf +18 -0
  42. data/example/filter_stdout.conf +22 -0
  43. data/example/in_forward.conf +14 -0
  44. data/example/in_forward_client.conf +37 -0
  45. data/example/in_forward_shared_key.conf +15 -0
  46. data/example/in_forward_tls.conf +14 -0
  47. data/example/in_forward_users.conf +24 -0
  48. data/example/in_forward_workers.conf +21 -0
  49. data/example/in_http.conf +16 -0
  50. data/example/in_out_forward.conf +17 -0
  51. data/example/in_sample_blocks.conf +17 -0
  52. data/example/in_sample_with_compression.conf +23 -0
  53. data/example/in_syslog.conf +15 -0
  54. data/example/in_tail.conf +14 -0
  55. data/example/in_tcp.conf +13 -0
  56. data/example/in_udp.conf +13 -0
  57. data/example/logevents.conf +25 -0
  58. data/example/multi_filters.conf +61 -0
  59. data/example/out_copy.conf +20 -0
  60. data/example/out_exec_filter.conf +42 -0
  61. data/example/out_file.conf +13 -0
  62. data/example/out_forward.conf +35 -0
  63. data/example/out_forward_buf_file.conf +23 -0
  64. data/example/out_forward_client.conf +109 -0
  65. data/example/out_forward_heartbeat_none.conf +16 -0
  66. data/example/out_forward_sd.conf +17 -0
  67. data/example/out_forward_shared_key.conf +36 -0
  68. data/example/out_forward_tls.conf +18 -0
  69. data/example/out_forward_users.conf +65 -0
  70. data/example/out_null.conf +36 -0
  71. data/example/sd.yaml +8 -0
  72. data/example/secondary_file.conf +42 -0
  73. data/example/suppress_config_dump.conf +7 -0
  74. data/example/v0_12_filter.conf +78 -0
  75. data/example/v1_literal_example.conf +36 -0
  76. data/example/worker_section.conf +36 -0
  77. data/fluent.conf +139 -0
  78. data/fluentd.gemspec +55 -0
  79. data/lib/fluent/agent.rb +168 -0
  80. data/lib/fluent/capability.rb +87 -0
  81. data/lib/fluent/clock.rb +66 -0
  82. data/lib/fluent/command/binlog_reader.rb +244 -0
  83. data/lib/fluent/command/bundler_injection.rb +45 -0
  84. data/lib/fluent/command/ca_generate.rb +184 -0
  85. data/lib/fluent/command/cap_ctl.rb +174 -0
  86. data/lib/fluent/command/cat.rb +365 -0
  87. data/lib/fluent/command/ctl.rb +177 -0
  88. data/lib/fluent/command/debug.rb +103 -0
  89. data/lib/fluent/command/fluentd.rb +374 -0
  90. data/lib/fluent/command/plugin_config_formatter.rb +308 -0
  91. data/lib/fluent/command/plugin_generator.rb +365 -0
  92. data/lib/fluent/compat/call_super_mixin.rb +76 -0
  93. data/lib/fluent/compat/detach_process_mixin.rb +33 -0
  94. data/lib/fluent/compat/exec_util.rb +129 -0
  95. data/lib/fluent/compat/file_util.rb +54 -0
  96. data/lib/fluent/compat/filter.rb +68 -0
  97. data/lib/fluent/compat/formatter.rb +111 -0
  98. data/lib/fluent/compat/formatter_utils.rb +85 -0
  99. data/lib/fluent/compat/handle_tag_and_time_mixin.rb +62 -0
  100. data/lib/fluent/compat/handle_tag_name_mixin.rb +53 -0
  101. data/lib/fluent/compat/input.rb +49 -0
  102. data/lib/fluent/compat/output.rb +721 -0
  103. data/lib/fluent/compat/output_chain.rb +60 -0
  104. data/lib/fluent/compat/parser.rb +310 -0
  105. data/lib/fluent/compat/parser_utils.rb +40 -0
  106. data/lib/fluent/compat/propagate_default.rb +62 -0
  107. data/lib/fluent/compat/record_filter_mixin.rb +34 -0
  108. data/lib/fluent/compat/set_tag_key_mixin.rb +50 -0
  109. data/lib/fluent/compat/set_time_key_mixin.rb +69 -0
  110. data/lib/fluent/compat/socket_util.rb +165 -0
  111. data/lib/fluent/compat/string_util.rb +34 -0
  112. data/lib/fluent/compat/structured_format_mixin.rb +26 -0
  113. data/lib/fluent/compat/type_converter.rb +90 -0
  114. data/lib/fluent/config/basic_parser.rb +123 -0
  115. data/lib/fluent/config/configure_proxy.rb +424 -0
  116. data/lib/fluent/config/dsl.rb +152 -0
  117. data/lib/fluent/config/element.rb +265 -0
  118. data/lib/fluent/config/error.rb +32 -0
  119. data/lib/fluent/config/literal_parser.rb +286 -0
  120. data/lib/fluent/config/parser.rb +107 -0
  121. data/lib/fluent/config/section.rb +272 -0
  122. data/lib/fluent/config/types.rb +249 -0
  123. data/lib/fluent/config/v1_parser.rb +192 -0
  124. data/lib/fluent/config.rb +76 -0
  125. data/lib/fluent/configurable.rb +201 -0
  126. data/lib/fluent/counter/base_socket.rb +44 -0
  127. data/lib/fluent/counter/client.rb +297 -0
  128. data/lib/fluent/counter/error.rb +86 -0
  129. data/lib/fluent/counter/mutex_hash.rb +163 -0
  130. data/lib/fluent/counter/server.rb +273 -0
  131. data/lib/fluent/counter/store.rb +205 -0
  132. data/lib/fluent/counter/validator.rb +145 -0
  133. data/lib/fluent/counter.rb +23 -0
  134. data/lib/fluent/daemon.rb +15 -0
  135. data/lib/fluent/daemonizer.rb +88 -0
  136. data/lib/fluent/engine.rb +253 -0
  137. data/lib/fluent/env.rb +40 -0
  138. data/lib/fluent/error.rb +34 -0
  139. data/lib/fluent/event.rb +326 -0
  140. data/lib/fluent/event_router.rb +297 -0
  141. data/lib/fluent/ext_monitor_require.rb +28 -0
  142. data/lib/fluent/filter.rb +21 -0
  143. data/lib/fluent/fluent_log_event_router.rb +141 -0
  144. data/lib/fluent/formatter.rb +23 -0
  145. data/lib/fluent/input.rb +21 -0
  146. data/lib/fluent/label.rb +46 -0
  147. data/lib/fluent/load.rb +34 -0
  148. data/lib/fluent/log.rb +713 -0
  149. data/lib/fluent/match.rb +187 -0
  150. data/lib/fluent/mixin.rb +31 -0
  151. data/lib/fluent/msgpack_factory.rb +106 -0
  152. data/lib/fluent/oj_options.rb +62 -0
  153. data/lib/fluent/output.rb +29 -0
  154. data/lib/fluent/output_chain.rb +23 -0
  155. data/lib/fluent/parser.rb +23 -0
  156. data/lib/fluent/plugin/bare_output.rb +104 -0
  157. data/lib/fluent/plugin/base.rb +197 -0
  158. data/lib/fluent/plugin/buf_file.rb +213 -0
  159. data/lib/fluent/plugin/buf_file_single.rb +225 -0
  160. data/lib/fluent/plugin/buf_memory.rb +34 -0
  161. data/lib/fluent/plugin/buffer/chunk.rb +240 -0
  162. data/lib/fluent/plugin/buffer/file_chunk.rb +413 -0
  163. data/lib/fluent/plugin/buffer/file_single_chunk.rb +311 -0
  164. data/lib/fluent/plugin/buffer/memory_chunk.rb +91 -0
  165. data/lib/fluent/plugin/buffer.rb +918 -0
  166. data/lib/fluent/plugin/compressable.rb +96 -0
  167. data/lib/fluent/plugin/exec_util.rb +22 -0
  168. data/lib/fluent/plugin/file_util.rb +22 -0
  169. data/lib/fluent/plugin/file_wrapper.rb +187 -0
  170. data/lib/fluent/plugin/filter.rb +127 -0
  171. data/lib/fluent/plugin/filter_grep.rb +189 -0
  172. data/lib/fluent/plugin/filter_parser.rb +130 -0
  173. data/lib/fluent/plugin/filter_record_transformer.rb +324 -0
  174. data/lib/fluent/plugin/filter_stdout.rb +53 -0
  175. data/lib/fluent/plugin/formatter.rb +75 -0
  176. data/lib/fluent/plugin/formatter_csv.rb +78 -0
  177. data/lib/fluent/plugin/formatter_hash.rb +35 -0
  178. data/lib/fluent/plugin/formatter_json.rb +59 -0
  179. data/lib/fluent/plugin/formatter_ltsv.rb +44 -0
  180. data/lib/fluent/plugin/formatter_msgpack.rb +33 -0
  181. data/lib/fluent/plugin/formatter_out_file.rb +53 -0
  182. data/lib/fluent/plugin/formatter_single_value.rb +36 -0
  183. data/lib/fluent/plugin/formatter_stdout.rb +76 -0
  184. data/lib/fluent/plugin/formatter_tsv.rb +40 -0
  185. data/lib/fluent/plugin/in_debug_agent.rb +71 -0
  186. data/lib/fluent/plugin/in_dummy.rb +18 -0
  187. data/lib/fluent/plugin/in_exec.rb +110 -0
  188. data/lib/fluent/plugin/in_forward.rb +473 -0
  189. data/lib/fluent/plugin/in_gc_stat.rb +72 -0
  190. data/lib/fluent/plugin/in_http.rb +667 -0
  191. data/lib/fluent/plugin/in_monitor_agent.rb +412 -0
  192. data/lib/fluent/plugin/in_object_space.rb +93 -0
  193. data/lib/fluent/plugin/in_sample.rb +141 -0
  194. data/lib/fluent/plugin/in_syslog.rb +276 -0
  195. data/lib/fluent/plugin/in_tail/position_file.rb +269 -0
  196. data/lib/fluent/plugin/in_tail.rb +1228 -0
  197. data/lib/fluent/plugin/in_tcp.rb +181 -0
  198. data/lib/fluent/plugin/in_udp.rb +92 -0
  199. data/lib/fluent/plugin/in_unix.rb +195 -0
  200. data/lib/fluent/plugin/input.rb +75 -0
  201. data/lib/fluent/plugin/metrics.rb +119 -0
  202. data/lib/fluent/plugin/metrics_local.rb +96 -0
  203. data/lib/fluent/plugin/multi_output.rb +195 -0
  204. data/lib/fluent/plugin/out_copy.rb +120 -0
  205. data/lib/fluent/plugin/out_exec.rb +105 -0
  206. data/lib/fluent/plugin/out_exec_filter.rb +319 -0
  207. data/lib/fluent/plugin/out_file.rb +334 -0
  208. data/lib/fluent/plugin/out_forward/ack_handler.rb +161 -0
  209. data/lib/fluent/plugin/out_forward/connection_manager.rb +113 -0
  210. data/lib/fluent/plugin/out_forward/error.rb +28 -0
  211. data/lib/fluent/plugin/out_forward/failure_detector.rb +84 -0
  212. data/lib/fluent/plugin/out_forward/handshake_protocol.rb +125 -0
  213. data/lib/fluent/plugin/out_forward/load_balancer.rb +114 -0
  214. data/lib/fluent/plugin/out_forward/socket_cache.rb +140 -0
  215. data/lib/fluent/plugin/out_forward.rb +826 -0
  216. data/lib/fluent/plugin/out_http.rb +275 -0
  217. data/lib/fluent/plugin/out_null.rb +74 -0
  218. data/lib/fluent/plugin/out_relabel.rb +32 -0
  219. data/lib/fluent/plugin/out_roundrobin.rb +84 -0
  220. data/lib/fluent/plugin/out_secondary_file.rb +131 -0
  221. data/lib/fluent/plugin/out_stdout.rb +74 -0
  222. data/lib/fluent/plugin/out_stream.rb +130 -0
  223. data/lib/fluent/plugin/output.rb +1556 -0
  224. data/lib/fluent/plugin/owned_by_mixin.rb +42 -0
  225. data/lib/fluent/plugin/parser.rb +275 -0
  226. data/lib/fluent/plugin/parser_apache.rb +28 -0
  227. data/lib/fluent/plugin/parser_apache2.rb +88 -0
  228. data/lib/fluent/plugin/parser_apache_error.rb +26 -0
  229. data/lib/fluent/plugin/parser_csv.rb +114 -0
  230. data/lib/fluent/plugin/parser_json.rb +96 -0
  231. data/lib/fluent/plugin/parser_ltsv.rb +51 -0
  232. data/lib/fluent/plugin/parser_msgpack.rb +50 -0
  233. data/lib/fluent/plugin/parser_multiline.rb +152 -0
  234. data/lib/fluent/plugin/parser_nginx.rb +28 -0
  235. data/lib/fluent/plugin/parser_none.rb +36 -0
  236. data/lib/fluent/plugin/parser_regexp.rb +68 -0
  237. data/lib/fluent/plugin/parser_syslog.rb +496 -0
  238. data/lib/fluent/plugin/parser_tsv.rb +42 -0
  239. data/lib/fluent/plugin/sd_file.rb +156 -0
  240. data/lib/fluent/plugin/sd_srv.rb +135 -0
  241. data/lib/fluent/plugin/sd_static.rb +58 -0
  242. data/lib/fluent/plugin/service_discovery.rb +65 -0
  243. data/lib/fluent/plugin/socket_util.rb +22 -0
  244. data/lib/fluent/plugin/storage.rb +84 -0
  245. data/lib/fluent/plugin/storage_local.rb +162 -0
  246. data/lib/fluent/plugin/string_util.rb +22 -0
  247. data/lib/fluent/plugin.rb +206 -0
  248. data/lib/fluent/plugin_helper/cert_option.rb +191 -0
  249. data/lib/fluent/plugin_helper/child_process.rb +366 -0
  250. data/lib/fluent/plugin_helper/compat_parameters.rb +343 -0
  251. data/lib/fluent/plugin_helper/counter.rb +51 -0
  252. data/lib/fluent/plugin_helper/event_emitter.rb +100 -0
  253. data/lib/fluent/plugin_helper/event_loop.rb +170 -0
  254. data/lib/fluent/plugin_helper/extract.rb +104 -0
  255. data/lib/fluent/plugin_helper/formatter.rb +147 -0
  256. data/lib/fluent/plugin_helper/http_server/app.rb +79 -0
  257. data/lib/fluent/plugin_helper/http_server/compat/server.rb +92 -0
  258. data/lib/fluent/plugin_helper/http_server/compat/ssl_context_extractor.rb +52 -0
  259. data/lib/fluent/plugin_helper/http_server/compat/webrick_handler.rb +58 -0
  260. data/lib/fluent/plugin_helper/http_server/methods.rb +35 -0
  261. data/lib/fluent/plugin_helper/http_server/request.rb +42 -0
  262. data/lib/fluent/plugin_helper/http_server/router.rb +54 -0
  263. data/lib/fluent/plugin_helper/http_server/server.rb +93 -0
  264. data/lib/fluent/plugin_helper/http_server/ssl_context_builder.rb +41 -0
  265. data/lib/fluent/plugin_helper/http_server.rb +135 -0
  266. data/lib/fluent/plugin_helper/inject.rb +154 -0
  267. data/lib/fluent/plugin_helper/metrics.rb +129 -0
  268. data/lib/fluent/plugin_helper/parser.rb +147 -0
  269. data/lib/fluent/plugin_helper/record_accessor.rb +207 -0
  270. data/lib/fluent/plugin_helper/retry_state.rb +209 -0
  271. data/lib/fluent/plugin_helper/server.rb +801 -0
  272. data/lib/fluent/plugin_helper/service_discovery/manager.rb +146 -0
  273. data/lib/fluent/plugin_helper/service_discovery/round_robin_balancer.rb +43 -0
  274. data/lib/fluent/plugin_helper/service_discovery.rb +125 -0
  275. data/lib/fluent/plugin_helper/socket.rb +277 -0
  276. data/lib/fluent/plugin_helper/socket_option.rb +98 -0
  277. data/lib/fluent/plugin_helper/storage.rb +349 -0
  278. data/lib/fluent/plugin_helper/thread.rb +180 -0
  279. data/lib/fluent/plugin_helper/timer.rb +92 -0
  280. data/lib/fluent/plugin_helper.rb +75 -0
  281. data/lib/fluent/plugin_id.rb +93 -0
  282. data/lib/fluent/process.rb +22 -0
  283. data/lib/fluent/registry.rb +116 -0
  284. data/lib/fluent/root_agent.rb +372 -0
  285. data/lib/fluent/rpc.rb +94 -0
  286. data/lib/fluent/static_config_analysis.rb +194 -0
  287. data/lib/fluent/supervisor.rb +1054 -0
  288. data/lib/fluent/system_config.rb +187 -0
  289. data/lib/fluent/test/base.rb +78 -0
  290. data/lib/fluent/test/driver/base.rb +225 -0
  291. data/lib/fluent/test/driver/base_owned.rb +83 -0
  292. data/lib/fluent/test/driver/base_owner.rb +135 -0
  293. data/lib/fluent/test/driver/event_feeder.rb +98 -0
  294. data/lib/fluent/test/driver/filter.rb +57 -0
  295. data/lib/fluent/test/driver/formatter.rb +30 -0
  296. data/lib/fluent/test/driver/input.rb +31 -0
  297. data/lib/fluent/test/driver/multi_output.rb +53 -0
  298. data/lib/fluent/test/driver/output.rb +102 -0
  299. data/lib/fluent/test/driver/parser.rb +30 -0
  300. data/lib/fluent/test/driver/storage.rb +30 -0
  301. data/lib/fluent/test/driver/test_event_router.rb +45 -0
  302. data/lib/fluent/test/filter_test.rb +77 -0
  303. data/lib/fluent/test/formatter_test.rb +65 -0
  304. data/lib/fluent/test/helpers.rb +134 -0
  305. data/lib/fluent/test/input_test.rb +174 -0
  306. data/lib/fluent/test/log.rb +79 -0
  307. data/lib/fluent/test/output_test.rb +156 -0
  308. data/lib/fluent/test/parser_test.rb +70 -0
  309. data/lib/fluent/test/startup_shutdown.rb +46 -0
  310. data/lib/fluent/test.rb +58 -0
  311. data/lib/fluent/time.rb +512 -0
  312. data/lib/fluent/timezone.rb +171 -0
  313. data/lib/fluent/tls.rb +81 -0
  314. data/lib/fluent/unique_id.rb +39 -0
  315. data/lib/fluent/variable_store.rb +40 -0
  316. data/lib/fluent/version.rb +21 -0
  317. data/lib/fluent/winsvc.rb +103 -0
  318. data/templates/new_gem/Gemfile +3 -0
  319. data/templates/new_gem/README.md.erb +43 -0
  320. data/templates/new_gem/Rakefile +13 -0
  321. data/templates/new_gem/fluent-plugin.gemspec.erb +27 -0
  322. data/templates/new_gem/lib/fluent/plugin/filter.rb.erb +14 -0
  323. data/templates/new_gem/lib/fluent/plugin/formatter.rb.erb +14 -0
  324. data/templates/new_gem/lib/fluent/plugin/input.rb.erb +11 -0
  325. data/templates/new_gem/lib/fluent/plugin/output.rb.erb +11 -0
  326. data/templates/new_gem/lib/fluent/plugin/parser.rb.erb +15 -0
  327. data/templates/new_gem/lib/fluent/plugin/storage.rb.erb +40 -0
  328. data/templates/new_gem/test/helper.rb.erb +8 -0
  329. data/templates/new_gem/test/plugin/test_filter.rb.erb +18 -0
  330. data/templates/new_gem/test/plugin/test_formatter.rb.erb +18 -0
  331. data/templates/new_gem/test/plugin/test_input.rb.erb +18 -0
  332. data/templates/new_gem/test/plugin/test_output.rb.erb +18 -0
  333. data/templates/new_gem/test/plugin/test_parser.rb.erb +18 -0
  334. data/templates/new_gem/test/plugin/test_storage.rb.erb +18 -0
  335. data/templates/plugin_config_formatter/param.md-compact.erb +25 -0
  336. data/templates/plugin_config_formatter/param.md-table.erb +10 -0
  337. data/templates/plugin_config_formatter/param.md.erb +34 -0
  338. data/templates/plugin_config_formatter/section.md.erb +12 -0
  339. data/test/command/test_binlog_reader.rb +362 -0
  340. data/test/command/test_ca_generate.rb +70 -0
  341. data/test/command/test_cap_ctl.rb +100 -0
  342. data/test/command/test_cat.rb +128 -0
  343. data/test/command/test_ctl.rb +57 -0
  344. data/test/command/test_fluentd.rb +1106 -0
  345. data/test/command/test_plugin_config_formatter.rb +398 -0
  346. data/test/command/test_plugin_generator.rb +109 -0
  347. data/test/compat/test_calls_super.rb +166 -0
  348. data/test/compat/test_parser.rb +92 -0
  349. data/test/config/assertions.rb +42 -0
  350. data/test/config/test_config_parser.rb +551 -0
  351. data/test/config/test_configurable.rb +1784 -0
  352. data/test/config/test_configure_proxy.rb +604 -0
  353. data/test/config/test_dsl.rb +415 -0
  354. data/test/config/test_element.rb +518 -0
  355. data/test/config/test_literal_parser.rb +309 -0
  356. data/test/config/test_plugin_configuration.rb +56 -0
  357. data/test/config/test_section.rb +191 -0
  358. data/test/config/test_system_config.rb +199 -0
  359. data/test/config/test_types.rb +408 -0
  360. data/test/counter/test_client.rb +563 -0
  361. data/test/counter/test_error.rb +44 -0
  362. data/test/counter/test_mutex_hash.rb +179 -0
  363. data/test/counter/test_server.rb +589 -0
  364. data/test/counter/test_store.rb +258 -0
  365. data/test/counter/test_validator.rb +137 -0
  366. data/test/helper.rb +155 -0
  367. data/test/helpers/fuzzy_assert.rb +89 -0
  368. data/test/helpers/process_extenstion.rb +33 -0
  369. data/test/plugin/data/2010/01/20100102-030405.log +0 -0
  370. data/test/plugin/data/2010/01/20100102-030406.log +0 -0
  371. data/test/plugin/data/2010/01/20100102.log +0 -0
  372. data/test/plugin/data/log/bar +0 -0
  373. data/test/plugin/data/log/foo/bar.log +0 -0
  374. data/test/plugin/data/log/foo/bar2 +0 -0
  375. data/test/plugin/data/log/test.log +0 -0
  376. data/test/plugin/data/sd_file/config +11 -0
  377. data/test/plugin/data/sd_file/config.json +17 -0
  378. data/test/plugin/data/sd_file/config.yaml +11 -0
  379. data/test/plugin/data/sd_file/config.yml +11 -0
  380. data/test/plugin/data/sd_file/invalid_config.yml +7 -0
  381. data/test/plugin/in_tail/test_fifo.rb +121 -0
  382. data/test/plugin/in_tail/test_io_handler.rb +140 -0
  383. data/test/plugin/in_tail/test_position_file.rb +379 -0
  384. data/test/plugin/out_forward/test_ack_handler.rb +101 -0
  385. data/test/plugin/out_forward/test_connection_manager.rb +145 -0
  386. data/test/plugin/out_forward/test_handshake_protocol.rb +112 -0
  387. data/test/plugin/out_forward/test_load_balancer.rb +106 -0
  388. data/test/plugin/out_forward/test_socket_cache.rb +149 -0
  389. data/test/plugin/test_bare_output.rb +131 -0
  390. data/test/plugin/test_base.rb +115 -0
  391. data/test/plugin/test_buf_file.rb +1275 -0
  392. data/test/plugin/test_buf_file_single.rb +833 -0
  393. data/test/plugin/test_buf_memory.rb +42 -0
  394. data/test/plugin/test_buffer.rb +1383 -0
  395. data/test/plugin/test_buffer_chunk.rb +198 -0
  396. data/test/plugin/test_buffer_file_chunk.rb +871 -0
  397. data/test/plugin/test_buffer_file_single_chunk.rb +611 -0
  398. data/test/plugin/test_buffer_memory_chunk.rb +339 -0
  399. data/test/plugin/test_compressable.rb +87 -0
  400. data/test/plugin/test_file_util.rb +96 -0
  401. data/test/plugin/test_file_wrapper.rb +126 -0
  402. data/test/plugin/test_filter.rb +368 -0
  403. data/test/plugin/test_filter_grep.rb +697 -0
  404. data/test/plugin/test_filter_parser.rb +731 -0
  405. data/test/plugin/test_filter_record_transformer.rb +577 -0
  406. data/test/plugin/test_filter_stdout.rb +207 -0
  407. data/test/plugin/test_formatter_csv.rb +136 -0
  408. data/test/plugin/test_formatter_hash.rb +38 -0
  409. data/test/plugin/test_formatter_json.rb +61 -0
  410. data/test/plugin/test_formatter_ltsv.rb +70 -0
  411. data/test/plugin/test_formatter_msgpack.rb +28 -0
  412. data/test/plugin/test_formatter_out_file.rb +116 -0
  413. data/test/plugin/test_formatter_single_value.rb +44 -0
  414. data/test/plugin/test_formatter_tsv.rb +76 -0
  415. data/test/plugin/test_in_debug_agent.rb +49 -0
  416. data/test/plugin/test_in_exec.rb +261 -0
  417. data/test/plugin/test_in_forward.rb +1180 -0
  418. data/test/plugin/test_in_gc_stat.rb +62 -0
  419. data/test/plugin/test_in_http.rb +1080 -0
  420. data/test/plugin/test_in_monitor_agent.rb +923 -0
  421. data/test/plugin/test_in_object_space.rb +60 -0
  422. data/test/plugin/test_in_sample.rb +190 -0
  423. data/test/plugin/test_in_syslog.rb +505 -0
  424. data/test/plugin/test_in_tail.rb +2363 -0
  425. data/test/plugin/test_in_tcp.rb +243 -0
  426. data/test/plugin/test_in_udp.rb +268 -0
  427. data/test/plugin/test_in_unix.rb +181 -0
  428. data/test/plugin/test_input.rb +137 -0
  429. data/test/plugin/test_metadata.rb +89 -0
  430. data/test/plugin/test_metrics.rb +294 -0
  431. data/test/plugin/test_metrics_local.rb +96 -0
  432. data/test/plugin/test_multi_output.rb +204 -0
  433. data/test/plugin/test_out_copy.rb +308 -0
  434. data/test/plugin/test_out_exec.rb +312 -0
  435. data/test/plugin/test_out_exec_filter.rb +606 -0
  436. data/test/plugin/test_out_file.rb +1037 -0
  437. data/test/plugin/test_out_forward.rb +1348 -0
  438. data/test/plugin/test_out_http.rb +428 -0
  439. data/test/plugin/test_out_null.rb +105 -0
  440. data/test/plugin/test_out_relabel.rb +28 -0
  441. data/test/plugin/test_out_roundrobin.rb +146 -0
  442. data/test/plugin/test_out_secondary_file.rb +458 -0
  443. data/test/plugin/test_out_stdout.rb +205 -0
  444. data/test/plugin/test_out_stream.rb +103 -0
  445. data/test/plugin/test_output.rb +1065 -0
  446. data/test/plugin/test_output_as_buffered.rb +2024 -0
  447. data/test/plugin/test_output_as_buffered_backup.rb +363 -0
  448. data/test/plugin/test_output_as_buffered_compress.rb +165 -0
  449. data/test/plugin/test_output_as_buffered_overflow.rb +250 -0
  450. data/test/plugin/test_output_as_buffered_retries.rb +919 -0
  451. data/test/plugin/test_output_as_buffered_secondary.rb +882 -0
  452. data/test/plugin/test_output_as_standard.rb +374 -0
  453. data/test/plugin/test_owned_by.rb +35 -0
  454. data/test/plugin/test_parser.rb +399 -0
  455. data/test/plugin/test_parser_apache.rb +42 -0
  456. data/test/plugin/test_parser_apache2.rb +47 -0
  457. data/test/plugin/test_parser_apache_error.rb +45 -0
  458. data/test/plugin/test_parser_csv.rb +200 -0
  459. data/test/plugin/test_parser_json.rb +138 -0
  460. data/test/plugin/test_parser_labeled_tsv.rb +160 -0
  461. data/test/plugin/test_parser_multiline.rb +111 -0
  462. data/test/plugin/test_parser_nginx.rb +88 -0
  463. data/test/plugin/test_parser_none.rb +52 -0
  464. data/test/plugin/test_parser_regexp.rb +289 -0
  465. data/test/plugin/test_parser_syslog.rb +650 -0
  466. data/test/plugin/test_parser_tsv.rb +122 -0
  467. data/test/plugin/test_sd_file.rb +228 -0
  468. data/test/plugin/test_sd_srv.rb +230 -0
  469. data/test/plugin/test_storage.rb +167 -0
  470. data/test/plugin/test_storage_local.rb +335 -0
  471. data/test/plugin/test_string_util.rb +26 -0
  472. data/test/plugin_helper/data/cert/cert-key.pem +27 -0
  473. data/test/plugin_helper/data/cert/cert-with-CRLF.pem +19 -0
  474. data/test/plugin_helper/data/cert/cert-with-no-newline.pem +19 -0
  475. data/test/plugin_helper/data/cert/cert.pem +19 -0
  476. data/test/plugin_helper/data/cert/cert_chains/ca-cert-key.pem +27 -0
  477. data/test/plugin_helper/data/cert/cert_chains/ca-cert.pem +20 -0
  478. data/test/plugin_helper/data/cert/cert_chains/cert-key.pem +27 -0
  479. data/test/plugin_helper/data/cert/cert_chains/cert.pem +40 -0
  480. data/test/plugin_helper/data/cert/empty.pem +0 -0
  481. data/test/plugin_helper/data/cert/generate_cert.rb +125 -0
  482. data/test/plugin_helper/data/cert/with_ca/ca-cert-key-pass.pem +30 -0
  483. data/test/plugin_helper/data/cert/with_ca/ca-cert-key.pem +27 -0
  484. data/test/plugin_helper/data/cert/with_ca/ca-cert-pass.pem +20 -0
  485. data/test/plugin_helper/data/cert/with_ca/ca-cert.pem +20 -0
  486. data/test/plugin_helper/data/cert/with_ca/cert-key-pass.pem +30 -0
  487. data/test/plugin_helper/data/cert/with_ca/cert-key.pem +27 -0
  488. data/test/plugin_helper/data/cert/with_ca/cert-pass.pem +21 -0
  489. data/test/plugin_helper/data/cert/with_ca/cert.pem +21 -0
  490. data/test/plugin_helper/data/cert/without_ca/cert-key-pass.pem +30 -0
  491. data/test/plugin_helper/data/cert/without_ca/cert-key.pem +27 -0
  492. data/test/plugin_helper/data/cert/without_ca/cert-pass.pem +20 -0
  493. data/test/plugin_helper/data/cert/without_ca/cert.pem +20 -0
  494. data/test/plugin_helper/http_server/test_app.rb +65 -0
  495. data/test/plugin_helper/http_server/test_route.rb +32 -0
  496. data/test/plugin_helper/service_discovery/test_manager.rb +93 -0
  497. data/test/plugin_helper/service_discovery/test_round_robin_balancer.rb +21 -0
  498. data/test/plugin_helper/test_cert_option.rb +25 -0
  499. data/test/plugin_helper/test_child_process.rb +840 -0
  500. data/test/plugin_helper/test_compat_parameters.rb +358 -0
  501. data/test/plugin_helper/test_event_emitter.rb +80 -0
  502. data/test/plugin_helper/test_event_loop.rb +52 -0
  503. data/test/plugin_helper/test_extract.rb +194 -0
  504. data/test/plugin_helper/test_formatter.rb +255 -0
  505. data/test/plugin_helper/test_http_server_helper.rb +372 -0
  506. data/test/plugin_helper/test_inject.rb +561 -0
  507. data/test/plugin_helper/test_metrics.rb +137 -0
  508. data/test/plugin_helper/test_parser.rb +264 -0
  509. data/test/plugin_helper/test_record_accessor.rb +238 -0
  510. data/test/plugin_helper/test_retry_state.rb +442 -0
  511. data/test/plugin_helper/test_server.rb +1823 -0
  512. data/test/plugin_helper/test_service_discovery.rb +165 -0
  513. data/test/plugin_helper/test_socket.rb +146 -0
  514. data/test/plugin_helper/test_storage.rb +542 -0
  515. data/test/plugin_helper/test_thread.rb +164 -0
  516. data/test/plugin_helper/test_timer.rb +130 -0
  517. data/test/scripts/exec_script.rb +32 -0
  518. data/test/scripts/fluent/plugin/formatter1/formatter_test1.rb +7 -0
  519. data/test/scripts/fluent/plugin/formatter2/formatter_test2.rb +7 -0
  520. data/test/scripts/fluent/plugin/formatter_known.rb +8 -0
  521. data/test/scripts/fluent/plugin/out_test.rb +81 -0
  522. data/test/scripts/fluent/plugin/out_test2.rb +80 -0
  523. data/test/scripts/fluent/plugin/parser_known.rb +4 -0
  524. data/test/test_capability.rb +74 -0
  525. data/test/test_clock.rb +164 -0
  526. data/test/test_config.rb +202 -0
  527. data/test/test_configdsl.rb +148 -0
  528. data/test/test_daemonizer.rb +91 -0
  529. data/test/test_engine.rb +203 -0
  530. data/test/test_event.rb +531 -0
  531. data/test/test_event_router.rb +331 -0
  532. data/test/test_event_time.rb +199 -0
  533. data/test/test_filter.rb +121 -0
  534. data/test/test_fluent_log_event_router.rb +99 -0
  535. data/test/test_formatter.rb +366 -0
  536. data/test/test_input.rb +31 -0
  537. data/test/test_log.rb +994 -0
  538. data/test/test_logger_initializer.rb +46 -0
  539. data/test/test_match.rb +148 -0
  540. data/test/test_mixin.rb +351 -0
  541. data/test/test_msgpack_factory.rb +18 -0
  542. data/test/test_oj_options.rb +55 -0
  543. data/test/test_output.rb +278 -0
  544. data/test/test_plugin.rb +251 -0
  545. data/test/test_plugin_classes.rb +370 -0
  546. data/test/test_plugin_helper.rb +81 -0
  547. data/test/test_plugin_id.rb +119 -0
  548. data/test/test_process.rb +14 -0
  549. data/test/test_root_agent.rb +951 -0
  550. data/test/test_static_config_analysis.rb +177 -0
  551. data/test/test_supervisor.rb +601 -0
  552. data/test/test_test_drivers.rb +136 -0
  553. data/test/test_time_formatter.rb +301 -0
  554. data/test/test_time_parser.rb +362 -0
  555. data/test/test_tls.rb +65 -0
  556. data/test/test_unique_id.rb +47 -0
  557. data/test/test_variable_store.rb +65 -0
  558. metadata +1261 -0
@@ -0,0 +1,2363 @@
1
+ require_relative '../helper'
2
+ require 'fluent/test/driver/input'
3
+ require 'fluent/plugin/in_tail'
4
+ require 'fluent/plugin/buffer'
5
+ require 'fluent/system_config'
6
+ require 'net/http'
7
+ require 'flexmock/test_unit'
8
+ require 'timecop'
9
+ require 'tmpdir'
10
+ require 'securerandom'
11
+
12
+ class TailInputTest < Test::Unit::TestCase
13
+ include FlexMock::TestCase
14
+
15
+ def setup
16
+ Fluent::Test.setup
17
+ cleanup_directory(TMP_DIR)
18
+ end
19
+
20
+ def teardown
21
+ super
22
+ cleanup_directory(TMP_DIR)
23
+ Fluent::Engine.stop
24
+ end
25
+
26
+ def cleanup_directory(path)
27
+ unless Dir.exist?(path)
28
+ FileUtils.mkdir_p(path)
29
+ return
30
+ end
31
+
32
+ if Fluent.windows?
33
+ Dir.glob("*", base: path).each do |name|
34
+ begin
35
+ cleanup_file(File.join(path, name))
36
+ rescue
37
+ # expect test driver block release already owned file handle.
38
+ end
39
+ end
40
+ else
41
+ begin
42
+ FileUtils.rm_f(path, secure:true)
43
+ rescue ArgumentError
44
+ FileUtils.rm_f(path) # For Ruby 2.6 or before.
45
+ end
46
+ if File.exist?(path)
47
+ FileUtils.remove_entry_secure(path, true)
48
+ end
49
+ end
50
+ FileUtils.mkdir_p(path)
51
+ end
52
+
53
+ def cleanup_file(path)
54
+ if Fluent.windows?
55
+ # On Windows, when the file or directory is removed and created
56
+ # frequently, there is a case that creating file or directory will
57
+ # fail. This situation is caused by pending file or directory
58
+ # deletion which is mentioned on win32 API document [1]
59
+ # As a workaround, execute rename and remove method.
60
+ #
61
+ # [1] https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#files
62
+ #
63
+ file = File.join(Dir.tmpdir, SecureRandom.hex(10))
64
+ begin
65
+ FileUtils.mv(path, file)
66
+ FileUtils.rm_rf(file, secure: true)
67
+ rescue ArgumentError
68
+ FileUtils.rm_rf(file) # For Ruby 2.6 or before.
69
+ end
70
+ if File.exist?(file)
71
+ # ensure files are closed for Windows, on which deleted files
72
+ # are still visible from filesystem
73
+ GC.start(full_mark: true, immediate_mark: true, immediate_sweep: true)
74
+ FileUtils.remove_entry_secure(file, true)
75
+ end
76
+ else
77
+ begin
78
+ FileUtils.rm_f(path, secure: true)
79
+ rescue ArgumentError
80
+ FileUtils.rm_f(path) # For Ruby 2.6 or before.
81
+ end
82
+ if File.exist?(path)
83
+ FileUtils.remove_entry_secure(path, true)
84
+ end
85
+ end
86
+ end
87
+
88
+ def create_target_info(path)
89
+ Fluent::Plugin::TailInput::TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
90
+ end
91
+
92
+ TMP_DIR = File.dirname(__FILE__) + "/../tmp/tail#{ENV['TEST_ENV_NUMBER']}"
93
+
94
+ CONFIG = config_element("ROOT", "", {
95
+ "path" => "#{TMP_DIR}/tail.txt",
96
+ "tag" => "t1",
97
+ "rotate_wait" => "2s",
98
+ "refresh_interval" => "1s"
99
+ })
100
+ COMMON_CONFIG = CONFIG + config_element("", "", { "pos_file" => "#{TMP_DIR}/tail.pos" })
101
+ CONFIG_READ_FROM_HEAD = config_element("", "", { "read_from_head" => true })
102
+ CONFIG_DISABLE_WATCH_TIMER = config_element("", "", { "enable_watch_timer" => false })
103
+ CONFIG_DISABLE_STAT_WATCHER = config_element("", "", { "enable_stat_watcher" => false })
104
+ CONFIG_OPEN_ON_EVERY_UPDATE = config_element("", "", { "open_on_every_update" => true })
105
+ COMMON_FOLLOW_INODE_CONFIG = config_element("ROOT", "", {
106
+ "path" => "#{TMP_DIR}/tail.txt*",
107
+ "pos_file" => "#{TMP_DIR}/tail.pos",
108
+ "tag" => "t1",
109
+ "refresh_interval" => "1s",
110
+ "read_from_head" => "true",
111
+ "format" => "none",
112
+ "rotate_wait" => "1s",
113
+ "follow_inodes" => "true"
114
+ })
115
+ SINGLE_LINE_CONFIG = config_element("", "", { "format" => "/(?<message>.*)/" })
116
+ PARSE_SINGLE_LINE_CONFIG = config_element("", "", {}, [config_element("parse", "", { "@type" => "/(?<message>.*)/" })])
117
+ MULTILINE_CONFIG = config_element(
118
+ "", "", {
119
+ "format" => "multiline",
120
+ "format1" => "/^s (?<message1>[^\\n]+)(\\nf (?<message2>[^\\n]+))?(\\nf (?<message3>.*))?/",
121
+ "format_firstline" => "/^[s]/"
122
+ })
123
+ PARSE_MULTILINE_CONFIG = config_element(
124
+ "", "", {},
125
+ [config_element("parse", "", {
126
+ "@type" => "multiline",
127
+ "format1" => "/^s (?<message1>[^\\n]+)(\\nf (?<message2>[^\\n]+))?(\\nf (?<message3>.*))?/",
128
+ "format_firstline" => "/^[s]/"
129
+ })
130
+ ])
131
+
132
+ MULTILINE_CONFIG_WITH_NEWLINE = config_element(
133
+ "", "", {
134
+ "format" => "multiline",
135
+ "format1" => "/^s (?<message1>[^\\n]+)(\\nf (?<message2>[^\\n]+))?(\\nf (?<message3>.[^\\n]+))?/",
136
+ "format_firstline" => "/^[s]/"
137
+ })
138
+ PARSE_MULTILINE_CONFIG_WITH_NEWLINE = config_element(
139
+ "", "", {},
140
+ [config_element("parse", "", {
141
+ "@type" => "multiline",
142
+ "format1" => "/^s (?<message1>[^\\n]+)(\\nf (?<message2>[^\\n]+))?(\\nf (?<message3>.[^\\n]+))?/",
143
+ "format_firstline" => "/^[s]/"
144
+ })
145
+ ])
146
+
147
+ def create_driver(conf = SINGLE_LINE_CONFIG, use_common_conf = true)
148
+ config = use_common_conf ? COMMON_CONFIG + conf : conf
149
+ Fluent::Test::Driver::Input.new(Fluent::Plugin::TailInput).configure(config)
150
+ end
151
+
152
+ sub_test_case "configure" do
153
+ test "plain single line" do
154
+ 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
162
+ assert_nothing_raised do
163
+ d.instance.have_read_capability?
164
+ end
165
+ end
166
+
167
+ data("empty" => config_element,
168
+ "w/o @type" => config_element("", "", {}, [config_element("parse", "", {})]))
169
+ test "w/o parse section" do |conf|
170
+ assert_raise(Fluent::ConfigError) do
171
+ create_driver(conf)
172
+ end
173
+ end
174
+
175
+ test "multi paths with path_delimiter" do
176
+ c = config_element("ROOT", "", { "path" => "tail.txt|test2|tmp,dev", "tag" => "t1", "path_delimiter" => "|" })
177
+ d = create_driver(c + PARSE_SINGLE_LINE_CONFIG, false)
178
+ assert_equal ["tail.txt", "test2", "tmp,dev"], d.instance.paths
179
+ end
180
+
181
+ test "multi paths with same path configured twice" do
182
+ c = config_element("ROOT", "", { "path" => "test1.txt,test2.txt,test1.txt", "tag" => "t1", "path_delimiter" => "," })
183
+ d = create_driver(c + PARSE_SINGLE_LINE_CONFIG, false)
184
+ assert_equal ["test2.txt","test1.txt"].sort, d.instance.paths.sort
185
+ end
186
+
187
+ test "multi paths with invaid path_delimiter" do
188
+ c = config_element("ROOT", "", { "path" => "tail.txt|test2|tmp,dev", "tag" => "t1", "path_delimiter" => "*" })
189
+ assert_raise(Fluent::ConfigError) do
190
+ create_driver(c + PARSE_SINGLE_LINE_CONFIG, false)
191
+ end
192
+ end
193
+
194
+ test "follow_inodes w/o pos file" do
195
+ assert_raise(Fluent::ConfigError) do
196
+ create_driver(CONFIG + config_element('', '', {'follow_inodes' => 'true'}))
197
+ end
198
+ end
199
+
200
+ sub_test_case "log throttling per file" do
201
+ test "w/o watcher timer is invalid" do
202
+ conf = CONFIG_DISABLE_WATCH_TIMER + config_element("ROOT", "", {"read_bytes_limit_per_second" => "8k"})
203
+ assert_raise(Fluent::ConfigError) do
204
+ create_driver(conf)
205
+ end
206
+ end
207
+
208
+ test "valid" do
209
+ conf = config_element("ROOT", "", {"read_bytes_limit_per_second" => "8k"})
210
+ assert_raise(Fluent::ConfigError) do
211
+ create_driver(conf)
212
+ end
213
+ end
214
+ end
215
+
216
+ test "both enable_watch_timer and enable_stat_watcher are false" do
217
+ assert_raise(Fluent::ConfigError) do
218
+ create_driver(CONFIG_DISABLE_WATCH_TIMER + CONFIG_DISABLE_STAT_WATCHER + PARSE_SINGLE_LINE_CONFIG)
219
+ end
220
+ end
221
+
222
+ sub_test_case "encoding" do
223
+ test "valid" do
224
+ conf = SINGLE_LINE_CONFIG + config_element("", "", { "encoding" => "utf-8" })
225
+ d = create_driver(conf)
226
+ assert_equal Encoding::UTF_8, d.instance.encoding
227
+ end
228
+
229
+ test "invalid" do
230
+ conf = SINGLE_LINE_CONFIG + config_element("", "", { "encoding" => "no-such-encoding" })
231
+ assert_raise(Fluent::ConfigError) do
232
+ create_driver(conf)
233
+ end
234
+ end
235
+ end
236
+
237
+ sub_test_case "from_encoding" do
238
+ test "only specified from_encoding raise ConfigError" do
239
+ conf = SINGLE_LINE_CONFIG + config_element("", "", { "from_encoding" => "utf-8" })
240
+ assert_raise(Fluent::ConfigError) do
241
+ create_driver(conf)
242
+ end
243
+ end
244
+
245
+ test "valid" do
246
+ conf = SINGLE_LINE_CONFIG + config_element("", "", {
247
+ "from_encoding" => "utf-8",
248
+ "encoding" => "utf-8"
249
+ })
250
+ d = create_driver(conf)
251
+ assert_equal(Encoding::UTF_8, d.instance.from_encoding)
252
+ end
253
+
254
+ test "invalid" do
255
+ conf = SINGLE_LINE_CONFIG + config_element("", "", {
256
+ "from_encoding" => "no-such-encoding",
257
+ "encoding" => "utf-8"
258
+ })
259
+ assert_raise(Fluent::ConfigError) do
260
+ create_driver(conf)
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ sub_test_case "singleline" do
267
+ data(flat: SINGLE_LINE_CONFIG,
268
+ parse: PARSE_SINGLE_LINE_CONFIG)
269
+ def test_emit(data)
270
+ config = data
271
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
272
+ f.puts "test1"
273
+ f.puts "test2"
274
+ }
275
+
276
+ d = create_driver(config)
277
+
278
+ d.run(expect_emits: 1) do
279
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
280
+ f.puts "test3\ntest4"
281
+ }
282
+ end
283
+
284
+ events = d.events
285
+ assert_equal(true, events.length > 0)
286
+ assert_equal({"message" => "test3"}, events[0][2])
287
+ assert_equal({"message" => "test4"}, events[1][2])
288
+ assert(events[0][1].is_a?(Fluent::EventTime))
289
+ assert(events[1][1].is_a?(Fluent::EventTime))
290
+ assert_equal(1, d.emit_count)
291
+ end
292
+
293
+ def test_emit_with_emit_unmatched_lines_true
294
+ config = config_element("", "", { "format" => "/^(?<message>test.*)/", "emit_unmatched_lines" => true })
295
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
296
+
297
+ d = create_driver(config)
298
+ d.run(expect_emits: 1) do
299
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
300
+ f.puts "test line 1"
301
+ f.puts "test line 2"
302
+ f.puts "bad line 1"
303
+ f.puts "test line 3"
304
+ }
305
+ end
306
+
307
+ events = d.events
308
+ assert_equal(4, events.length)
309
+ assert_equal({"message" => "test line 1"}, events[0][2])
310
+ assert_equal({"message" => "test line 2"}, events[1][2])
311
+ assert_equal({"unmatched_line" => "bad line 1"}, events[2][2])
312
+ assert_equal({"message" => "test line 3"}, events[3][2])
313
+ end
314
+
315
+ data('flat 1' => [:flat, 1, 2],
316
+ 'flat 10' => [:flat, 10, 1],
317
+ 'parse 1' => [:parse, 1, 2],
318
+ 'parse 10' => [:parse, 10, 1])
319
+ def test_emit_with_read_lines_limit(data)
320
+ config_style, limit, num_events = data
321
+ case config_style
322
+ when :flat
323
+ config = CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG + config_element("", "", { "read_lines_limit" => limit })
324
+ when :parse
325
+ config = CONFIG_READ_FROM_HEAD + config_element("", "", { "read_lines_limit" => limit }) + PARSE_SINGLE_LINE_CONFIG
326
+ end
327
+ d = create_driver(config)
328
+ msg = 'test' * 2000 # in_tail reads 8192 bytes at once.
329
+
330
+ d.run(expect_emits: num_events, timeout: 2) do
331
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
332
+ f.puts msg
333
+ f.puts msg
334
+ }
335
+ end
336
+
337
+ events = d.events
338
+ assert_equal(true, events.length > 0)
339
+ assert_equal({"message" => msg}, events[0][2])
340
+ assert_equal({"message" => msg}, events[1][2])
341
+ assert num_events <= d.emit_count
342
+ end
343
+
344
+ sub_test_case "log throttling per file" do
345
+ teardown do
346
+ cleanup_file("#{TMP_DIR}/tail.txt")
347
+ end
348
+
349
+ sub_test_case "reads_bytes_per_second w/o throttled" do
350
+ data("flat 8192 bytes, 2 events" => [:flat, 100, 8192, 2],
351
+ "flat 8192 bytes, 2 events w/o stat watcher" => [:flat_without_stat, 100, 8192, 2],
352
+ "flat #{8192*10} bytes, 20 events" => [:flat, 100, (8192 * 10), 20],
353
+ "flat #{8192*10} bytes, 20 events w/o stat watcher" => [:flat_without_stat, 100, (8192 * 10), 20],
354
+ "parse #{8192*4} bytes, 8 events" => [:parse, 100, (8192 * 4), 8],
355
+ "parse #{8192*4} bytes, 8 events w/o stat watcher" => [:parse_without_stat, 100, (8192 * 4), 8],
356
+ "parse #{8192*10} bytes, 20 events" => [:parse, 100, (8192 * 10), 20],
357
+ "parse #{8192*10} bytes, 20 events w/o stat watcher" => [:parse_without_stat, 100, (8192 * 10), 20],
358
+ "flat 8k bytes with unit, 2 events" => [:flat, 100, "8k", 2])
359
+ def test_emit_with_read_bytes_limit_per_second(data)
360
+ config_style, limit, limit_bytes, num_events = data
361
+ case config_style
362
+ when :flat
363
+ config = CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG + config_element("", "", { "read_lines_limit" => limit, "read_bytes_limit_per_second" => limit_bytes })
364
+ when :parse
365
+ config = CONFIG_READ_FROM_HEAD + config_element("", "", { "read_lines_limit" => limit, "read_bytes_limit_per_second" => limit_bytes }) + PARSE_SINGLE_LINE_CONFIG
366
+ when :flat_without_stat
367
+ config = CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG + CONFIG_DISABLE_STAT_WATCHER + config_element("", "", { "read_lines_limit" => limit, "read_bytes_limit_per_second" => limit_bytes })
368
+ when :parse_without_stat
369
+ config = CONFIG_READ_FROM_HEAD + CONFIG_DISABLE_STAT_WATCHER + config_element("", "", { "read_lines_limit" => limit, "read_bytes_limit_per_second" => limit_bytes }) + PARSE_SINGLE_LINE_CONFIG
370
+ end
371
+
372
+ msg = 'test' * 2000 # in_tail reads 8192 bytes at once.
373
+ start_time = Fluent::Clock.now
374
+
375
+ d = create_driver(config)
376
+ d.run(expect_emits: 2) do
377
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
378
+ 100.times do
379
+ f.puts msg
380
+ end
381
+ }
382
+ end
383
+
384
+ assert_true(Fluent::Clock.now - start_time > 1)
385
+ assert_equal(num_events.times.map { {"message" => msg} },
386
+ d.events.collect { |event| event[2] })
387
+ end
388
+
389
+ def test_read_bytes_limit_precede_read_lines_limit
390
+ config = CONFIG_READ_FROM_HEAD +
391
+ SINGLE_LINE_CONFIG +
392
+ config_element("", "", {
393
+ "read_lines_limit" => 1000,
394
+ "read_bytes_limit_per_second" => 8192
395
+ })
396
+ msg = 'abc'
397
+ start_time = Fluent::Clock.now
398
+ d = create_driver(config)
399
+ d.run(expect_emits: 2) do
400
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
401
+ 8000.times do
402
+ f.puts msg
403
+ end
404
+ }
405
+ end
406
+
407
+ assert_true(Fluent::Clock.now - start_time > 1)
408
+ assert_equal(4096.times.map { {"message" => msg} },
409
+ d.events.collect { |event| event[2] })
410
+ end
411
+ end
412
+
413
+ sub_test_case "reads_bytes_per_second w/ throttled already" do
414
+ data("flat 8192 bytes" => [:flat, 100, 8192],
415
+ "parse 8192 bytes" => [:parse, 100, 8192])
416
+ def test_emit_with_read_bytes_limit_per_second(data)
417
+ config_style, limit, limit_bytes = data
418
+ case config_style
419
+ when :flat
420
+ config = CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG + config_element("", "", { "read_lines_limit" => limit, "read_bytes_limit_per_second" => limit_bytes })
421
+ when :parse
422
+ config = CONFIG_READ_FROM_HEAD + config_element("", "", { "read_lines_limit" => limit, "read_bytes_limit_per_second" => limit_bytes }) + PARSE_SINGLE_LINE_CONFIG
423
+ end
424
+ d = create_driver(config)
425
+ msg = 'test' * 2000 # in_tail reads 8192 bytes at once.
426
+
427
+ mock.proxy(d.instance).io_handler(anything, anything) do |io_handler|
428
+ require 'fluent/config/types'
429
+ limit_bytes_value = Fluent::Config.size_value(limit_bytes)
430
+ io_handler.instance_variable_set(:@number_bytes_read, limit_bytes_value)
431
+ if Fluent.linux?
432
+ mock.proxy(io_handler).handle_notify.at_least(5)
433
+ else
434
+ mock.proxy(io_handler).handle_notify.twice
435
+ end
436
+ io_handler
437
+ end
438
+
439
+ File.open("#{TMP_DIR}/tail.txt", "ab") do |f|
440
+ 100.times do
441
+ f.puts msg
442
+ end
443
+ end
444
+
445
+ # We should not do shutdown here due to hard timeout.
446
+ d.run do
447
+ start_time = Fluent::Clock.now
448
+ while Fluent::Clock.now - start_time < 0.8 do
449
+ File.open("#{TMP_DIR}/tail.txt", "ab") do |f|
450
+ f.puts msg
451
+ f.flush
452
+ end
453
+ sleep 0.05
454
+ end
455
+ end
456
+
457
+ assert_equal([], d.events)
458
+ end
459
+ end
460
+
461
+ sub_test_case "EOF with reads_bytes_per_second" do
462
+ def test_longer_than_rotate_wait
463
+ limit_bytes = 8192
464
+ num_lines = 1024 * 3
465
+ msg = "08bytes"
466
+
467
+ File.open("#{TMP_DIR}/tail.txt", "wb") do |f|
468
+ f.write("#{msg}\n" * num_lines)
469
+ end
470
+
471
+ config = CONFIG_READ_FROM_HEAD +
472
+ SINGLE_LINE_CONFIG +
473
+ config_element("", "", {
474
+ "read_bytes_limit_per_second" => limit_bytes,
475
+ "rotate_wait" => 0.1,
476
+ "refresh_interval" => 0.5,
477
+ })
478
+
479
+ rotated = false
480
+ d = create_driver(config)
481
+ d.run(timeout: 10) do
482
+ while d.events.size < num_lines do
483
+ if d.events.size > 0 && !rotated
484
+ cleanup_file("#{TMP_DIR}/tail.txt")
485
+ FileUtils.touch("#{TMP_DIR}/tail.txt")
486
+ rotated = true
487
+ end
488
+ sleep 0.3
489
+ end
490
+ end
491
+
492
+ assert_equal(num_lines,
493
+ d.events.count do |event|
494
+ event[2]["message"] == msg
495
+ end)
496
+ end
497
+
498
+ def test_shorter_than_rotate_wait
499
+ limit_bytes = 8192
500
+ num_lines = 1024 * 2
501
+ msg = "08bytes"
502
+
503
+ File.open("#{TMP_DIR}/tail.txt", "wb") do |f|
504
+ f.write("#{msg}\n" * num_lines)
505
+ end
506
+
507
+ config = CONFIG_READ_FROM_HEAD +
508
+ SINGLE_LINE_CONFIG +
509
+ config_element("", "", {
510
+ "read_bytes_limit_per_second" => limit_bytes,
511
+ "rotate_wait" => 2,
512
+ "refresh_interval" => 0.5,
513
+ })
514
+
515
+ start_time = Fluent::Clock.now
516
+ rotated = false
517
+ detached = false
518
+ d = create_driver(config)
519
+ mock.proxy(d.instance).setup_watcher(anything, anything) do |tw|
520
+ mock.proxy(tw).detach(anything) do |v|
521
+ detached = true
522
+ v
523
+ end
524
+ tw
525
+ end.twice
526
+
527
+ d.run(timeout: 10) do
528
+ until detached do
529
+ if d.events.size > 0 && !rotated
530
+ cleanup_file("#{TMP_DIR}/tail.txt")
531
+ FileUtils.touch("#{TMP_DIR}/tail.txt")
532
+ rotated = true
533
+ end
534
+ sleep 0.3
535
+ end
536
+ end
537
+
538
+ assert_true(Fluent::Clock.now - start_time > 2)
539
+ assert_equal(num_lines,
540
+ d.events.count do |event|
541
+ event[2]["message"] == msg
542
+ end)
543
+ end
544
+ end
545
+ end
546
+
547
+ data(flat: CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG,
548
+ parse: CONFIG_READ_FROM_HEAD + PARSE_SINGLE_LINE_CONFIG)
549
+ def test_emit_with_read_from_head(data)
550
+ config = data
551
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
552
+ f.puts "test1"
553
+ f.puts "test2"
554
+ }
555
+
556
+ d = create_driver(config)
557
+
558
+ d.run(expect_emits: 2) do
559
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
560
+ f.puts "test3"
561
+ f.puts "test4"
562
+ }
563
+ end
564
+
565
+ events = d.events
566
+ assert(events.length > 0)
567
+ assert_equal({"message" => "test1"}, events[0][2])
568
+ assert_equal({"message" => "test2"}, events[1][2])
569
+ assert_equal({"message" => "test3"}, events[2][2])
570
+ assert_equal({"message" => "test4"}, events[3][2])
571
+ end
572
+
573
+ data(flat: CONFIG_DISABLE_WATCH_TIMER + SINGLE_LINE_CONFIG,
574
+ parse: CONFIG_DISABLE_WATCH_TIMER + PARSE_SINGLE_LINE_CONFIG)
575
+ def test_emit_without_watch_timer(data)
576
+ config = data
577
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
578
+ f.puts "test1"
579
+ f.puts "test2"
580
+ }
581
+
582
+ d = create_driver(config)
583
+
584
+ d.run(expect_emits: 1) do
585
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
586
+ f.puts "test3"
587
+ f.puts "test4"
588
+ }
589
+ # according to cool.io's stat_watcher.c, systems without inotify will use
590
+ # an "automatic" value, typically around 5 seconds
591
+ end
592
+
593
+ events = d.events
594
+ assert(events.length > 0)
595
+ assert_equal({"message" => "test3"}, events[0][2])
596
+ assert_equal({"message" => "test4"}, events[1][2])
597
+ end
598
+
599
+ # https://github.com/fluent/fluentd/pull/3541#discussion_r740197711
600
+ def test_watch_wildcard_path_without_watch_timer
601
+ omit "need inotify" unless Fluent.linux?
602
+
603
+ config = config_element("ROOT", "", {
604
+ "path" => "#{TMP_DIR}/tail*.txt",
605
+ "tag" => "t1",
606
+ })
607
+ config = config + CONFIG_DISABLE_WATCH_TIMER + SINGLE_LINE_CONFIG
608
+
609
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
610
+ f.puts "test1"
611
+ f.puts "test2"
612
+ }
613
+
614
+ d = create_driver(config, false)
615
+
616
+ d.run(expect_emits: 1, timeout: 1) do
617
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
618
+ f.puts "test3"
619
+ f.puts "test4"
620
+ }
621
+ end
622
+
623
+ assert_equal(
624
+ [
625
+ {"message" => "test3"},
626
+ {"message" => "test4"},
627
+ ],
628
+ d.events.collect { |event| event[2] })
629
+ end
630
+
631
+ data(flat: CONFIG_DISABLE_STAT_WATCHER + SINGLE_LINE_CONFIG,
632
+ parse: CONFIG_DISABLE_STAT_WATCHER + PARSE_SINGLE_LINE_CONFIG)
633
+ def test_emit_with_disable_stat_watcher(data)
634
+ config = data
635
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
636
+ f.puts "test1"
637
+ f.puts "test2"
638
+ }
639
+
640
+ d = create_driver(config)
641
+
642
+ d.run(expect_emits: 1) do
643
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
644
+ f.puts "test3"
645
+ f.puts "test4"
646
+ }
647
+ end
648
+
649
+ events = d.events
650
+ assert(events.length > 0)
651
+ assert_equal({"message" => "test3"}, events[0][2])
652
+ assert_equal({"message" => "test4"}, events[1][2])
653
+ end
654
+
655
+ def test_always_read_from_head_on_detecting_a_new_file
656
+ d = create_driver(SINGLE_LINE_CONFIG)
657
+
658
+ d.run(expect_emits: 1, timeout: 3) do
659
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
660
+ f.puts "test1\ntest2\n"
661
+ }
662
+ end
663
+
664
+ assert_equal(
665
+ [
666
+ {"message" => "test1"},
667
+ {"message" => "test2"},
668
+ ],
669
+ d.events.collect { |event| event[2] })
670
+ end
671
+ end
672
+
673
+ class TestWithSystem < self
674
+ include Fluent::SystemConfig::Mixin
675
+
676
+ OVERRIDE_FILE_PERMISSION = 0620
677
+ CONFIG_SYSTEM = %[
678
+ <system>
679
+ file_permission #{OVERRIDE_FILE_PERMISSION}
680
+ </system>
681
+ ]
682
+
683
+ def setup
684
+ omit "NTFS doesn't support UNIX like permissions" if Fluent.windows?
685
+ # Store default permission
686
+ @default_permission = system_config.instance_variable_get(:@file_permission)
687
+ end
688
+
689
+ def teardown
690
+ # Restore default permission
691
+ system_config.instance_variable_set(:@file_permission, @default_permission)
692
+ end
693
+
694
+ def parse_system(text)
695
+ basepath = File.expand_path(File.dirname(__FILE__) + '/../../')
696
+ Fluent::Config.parse(text, '(test)', basepath, true).elements.find { |e| e.name == 'system' }
697
+ end
698
+
699
+ def test_emit_with_system
700
+ system_conf = parse_system(CONFIG_SYSTEM)
701
+ sc = Fluent::SystemConfig.new(system_conf)
702
+ Fluent::Engine.init(sc)
703
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
704
+ f.puts "test1"
705
+ f.puts "test2"
706
+ }
707
+
708
+ d = create_driver
709
+
710
+ d.run(expect_emits: 1) do
711
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
712
+ f.puts "test3"
713
+ f.puts "test4"
714
+ }
715
+ end
716
+
717
+ events = d.events
718
+ assert_equal(true, events.length > 0)
719
+ assert_equal({"message" => "test3"}, events[0][2])
720
+ assert_equal({"message" => "test4"}, events[1][2])
721
+ assert(events[0][1].is_a?(Fluent::EventTime))
722
+ assert(events[1][1].is_a?(Fluent::EventTime))
723
+ assert_equal(1, d.emit_count)
724
+ pos = d.instance.instance_variable_get(:@pf_file)
725
+ mode = "%o" % File.stat(pos).mode
726
+ assert_equal OVERRIDE_FILE_PERMISSION, mode[-3, 3].to_i
727
+ end
728
+ end
729
+
730
+ sub_test_case "rotate file" do
731
+ data(flat: SINGLE_LINE_CONFIG,
732
+ parse: PARSE_SINGLE_LINE_CONFIG)
733
+ def test_rotate_file(data)
734
+ config = data
735
+ events = sub_test_rotate_file(config, expect_emits: 2)
736
+ assert_equal(4, events.length)
737
+ assert_equal({"message" => "test3"}, events[0][2])
738
+ assert_equal({"message" => "test4"}, events[1][2])
739
+ assert_equal({"message" => "test5"}, events[2][2])
740
+ assert_equal({"message" => "test6"}, events[3][2])
741
+ end
742
+
743
+ data(flat: CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG,
744
+ parse: CONFIG_READ_FROM_HEAD + PARSE_SINGLE_LINE_CONFIG)
745
+ def test_rotate_file_with_read_from_head(data)
746
+ config = data
747
+ events = sub_test_rotate_file(config, expect_records: 6)
748
+ assert_equal(6, events.length)
749
+ assert_equal({"message" => "test1"}, events[0][2])
750
+ assert_equal({"message" => "test2"}, events[1][2])
751
+ assert_equal({"message" => "test3"}, events[2][2])
752
+ assert_equal({"message" => "test4"}, events[3][2])
753
+ assert_equal({"message" => "test5"}, events[4][2])
754
+ assert_equal({"message" => "test6"}, events[5][2])
755
+ end
756
+
757
+ data(flat: CONFIG_OPEN_ON_EVERY_UPDATE + CONFIG_READ_FROM_HEAD + SINGLE_LINE_CONFIG,
758
+ parse: CONFIG_OPEN_ON_EVERY_UPDATE + CONFIG_READ_FROM_HEAD + PARSE_SINGLE_LINE_CONFIG)
759
+ def test_rotate_file_with_open_on_every_update(data)
760
+ config = data
761
+ events = sub_test_rotate_file(config, expect_records: 6)
762
+ assert_equal(6, events.length)
763
+ assert_equal({"message" => "test1"}, events[0][2])
764
+ assert_equal({"message" => "test2"}, events[1][2])
765
+ assert_equal({"message" => "test3"}, events[2][2])
766
+ assert_equal({"message" => "test4"}, events[3][2])
767
+ assert_equal({"message" => "test5"}, events[4][2])
768
+ assert_equal({"message" => "test6"}, events[5][2])
769
+ end
770
+
771
+ data(flat: SINGLE_LINE_CONFIG,
772
+ parse: PARSE_SINGLE_LINE_CONFIG)
773
+ def test_rotate_file_with_write_old(data)
774
+ config = data
775
+ events = sub_test_rotate_file(config, expect_emits: 3) { |rotated_file|
776
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
777
+ rotated_file.puts "test7"
778
+ rotated_file.puts "test8"
779
+ rotated_file.flush
780
+
781
+ sleep 1
782
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
783
+ f.puts "test5"
784
+ f.puts "test6"
785
+ }
786
+ }
787
+ # This test sometimes fails and it shows a potential bug of in_tail
788
+ # https://github.com/fluent/fluentd/issues/1434
789
+ assert_equal(6, events.length)
790
+ assert_equal({"message" => "test3"}, events[0][2])
791
+ assert_equal({"message" => "test4"}, events[1][2])
792
+ assert_equal({"message" => "test7"}, events[2][2])
793
+ assert_equal({"message" => "test8"}, events[3][2])
794
+ assert_equal({"message" => "test5"}, events[4][2])
795
+ assert_equal({"message" => "test6"}, events[5][2])
796
+ end
797
+
798
+ data(flat: SINGLE_LINE_CONFIG,
799
+ parse: PARSE_SINGLE_LINE_CONFIG)
800
+ def test_rotate_file_with_write_old_and_no_new_file(data)
801
+ config = data
802
+ events = sub_test_rotate_file(config, expect_emits: 2) { |rotated_file|
803
+ rotated_file.puts "test7"
804
+ rotated_file.puts "test8"
805
+ rotated_file.flush
806
+ }
807
+ assert_equal(4, events.length)
808
+ assert_equal({"message" => "test3"}, events[0][2])
809
+ assert_equal({"message" => "test4"}, events[1][2])
810
+ assert_equal({"message" => "test7"}, events[2][2])
811
+ assert_equal({"message" => "test8"}, events[3][2])
812
+ end
813
+
814
+ def sub_test_rotate_file(config = nil, expect_emits: nil, expect_records: nil, timeout: nil)
815
+ file = Fluent::FileWrapper.open("#{TMP_DIR}/tail.txt", "wb")
816
+ file.puts "test1"
817
+ file.puts "test2"
818
+ file.flush
819
+
820
+ d = create_driver(config)
821
+ d.run(expect_emits: expect_emits, expect_records: expect_records, timeout: timeout) do
822
+ size = d.emit_count
823
+ file.puts "test3"
824
+ file.puts "test4"
825
+ file.flush
826
+ sleep(0.1) until d.emit_count >= size + 1
827
+ size = d.emit_count
828
+
829
+ if Fluent.windows?
830
+ file.close
831
+ FileUtils.mv("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail2.txt", force: true)
832
+ file = File.open("#{TMP_DIR}/tail.txt", "ab")
833
+ else
834
+ FileUtils.mv("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail2.txt")
835
+ end
836
+ if block_given?
837
+ yield file
838
+ else
839
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
840
+ sleep 1
841
+
842
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
843
+ f.puts "test5"
844
+ f.puts "test6"
845
+ }
846
+ end
847
+ end
848
+
849
+ d.events
850
+ ensure
851
+ file.close if file && !file.closed?
852
+ end
853
+ end
854
+
855
+ def test_truncate_file
856
+ omit "Permission denied error happen on Windows. Need fix" if Fluent.windows?
857
+
858
+ config = SINGLE_LINE_CONFIG
859
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
860
+ f.puts "test1"
861
+ f.puts "test2"
862
+ f.flush
863
+ }
864
+
865
+ d = create_driver(config)
866
+
867
+ d.run(expect_emits: 2) do
868
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
869
+ f.puts "test3\ntest4"
870
+ f.flush
871
+ }
872
+ waiting(2) { sleep 0.1 until d.events.length == 2 }
873
+ File.truncate("#{TMP_DIR}/tail.txt", 6)
874
+ end
875
+
876
+ expected = {
877
+ emit_count: 2,
878
+ events: [
879
+ [Fluent::EventTime, {"message" => "test3"}],
880
+ [Fluent::EventTime, {"message" => "test4"}],
881
+ [Fluent::EventTime, {"message" => "test1"}],
882
+ ]
883
+ }
884
+ actual = {
885
+ emit_count: d.emit_count,
886
+ events: d.events.collect{|event| [event[1].class, event[2]]}
887
+ }
888
+ assert_equal(expected, actual)
889
+ end
890
+
891
+ def test_move_truncate_move_back
892
+ omit "Permission denied error happen on Windows. Need fix" if Fluent.windows?
893
+
894
+ config = SINGLE_LINE_CONFIG
895
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
896
+ f.puts "test1"
897
+ f.puts "test2"
898
+ }
899
+
900
+ d = create_driver(config)
901
+
902
+ d.run(expect_emits: 1) do
903
+ if Fluent.windows?
904
+ FileUtils.mv("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail2.txt", force: true)
905
+ else
906
+ FileUtils.mv("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail2.txt")
907
+ end
908
+ sleep(1)
909
+ File.truncate("#{TMP_DIR}/tail2.txt", 6)
910
+ sleep(1)
911
+ if Fluent.windows?
912
+ FileUtils.mv("#{TMP_DIR}/tail2.txt", "#{TMP_DIR}/tail.txt", force: true)
913
+ else
914
+ FileUtils.mv("#{TMP_DIR}/tail2.txt", "#{TMP_DIR}/tail.txt")
915
+ end
916
+ end
917
+
918
+ events = d.events
919
+ assert_equal(1, events.length)
920
+ assert_equal({"message" => "test1"}, events[0][2])
921
+ assert(events[0][1].is_a?(Fluent::EventTime))
922
+ assert_equal(1, d.emit_count)
923
+ end
924
+
925
+ def test_lf
926
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f| }
927
+
928
+ d = create_driver
929
+
930
+ d.run(expect_emits: 1) do
931
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
932
+ f.print "test3"
933
+ }
934
+ sleep 1
935
+
936
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
937
+ f.puts "test4"
938
+ }
939
+ end
940
+
941
+ events = d.events
942
+ assert_equal(true, events.length > 0)
943
+ assert_equal({"message" => "test3test4"}, events[0][2])
944
+ end
945
+
946
+ def test_whitespace
947
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f| }
948
+
949
+ d = create_driver
950
+
951
+ d.run(expect_emits: 1) do
952
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
953
+ f.puts " " # 4 spaces
954
+ f.puts " 4 spaces"
955
+ f.puts "4 spaces "
956
+ f.puts " " # tab
957
+ f.puts " tab"
958
+ f.puts "tab "
959
+ }
960
+ end
961
+
962
+ events = d.events
963
+ assert_equal(true, events.length > 0)
964
+ assert_equal({"message" => " "}, events[0][2])
965
+ assert_equal({"message" => " 4 spaces"}, events[1][2])
966
+ assert_equal({"message" => "4 spaces "}, events[2][2])
967
+ assert_equal({"message" => " "}, events[3][2])
968
+ assert_equal({"message" => " tab"}, events[4][2])
969
+ assert_equal({"message" => "tab "}, events[5][2])
970
+ end
971
+
972
+ data(
973
+ 'flat default encoding' => [SINGLE_LINE_CONFIG, Encoding::ASCII_8BIT],
974
+ 'flat explicit encoding config' => [SINGLE_LINE_CONFIG + config_element("", "", { "encoding" => "utf-8" }), Encoding::UTF_8],
975
+ 'parse default encoding' => [PARSE_SINGLE_LINE_CONFIG, Encoding::ASCII_8BIT],
976
+ 'parse explicit encoding config' => [PARSE_SINGLE_LINE_CONFIG + config_element("", "", { "encoding" => "utf-8" }), Encoding::UTF_8])
977
+ def test_encoding(data)
978
+ encoding_config, encoding = data
979
+
980
+ d = create_driver(CONFIG_READ_FROM_HEAD + encoding_config)
981
+
982
+ d.run(expect_emits: 1) do
983
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
984
+ f.puts "test"
985
+ }
986
+ end
987
+
988
+ events = d.events
989
+ assert_equal(encoding, events[0][2]['message'].encoding)
990
+ end
991
+
992
+ def test_from_encoding
993
+ conf = config_element(
994
+ "", "", {
995
+ "format" => "/(?<message>.*)/",
996
+ "read_from_head" => "true",
997
+ "from_encoding" => "cp932",
998
+ "encoding" => "utf-8"
999
+ })
1000
+ d = create_driver(conf)
1001
+ cp932_message = "\x82\xCD\x82\xEB\x81\x5B\x82\xED\x81\x5B\x82\xE9\x82\xC7".force_encoding(Encoding::CP932)
1002
+ utf8_message = cp932_message.encode(Encoding::UTF_8)
1003
+
1004
+ d.run(expect_emits: 1) do
1005
+ File.open("#{TMP_DIR}/tail.txt", "w:cp932") {|f|
1006
+ f.puts cp932_message
1007
+ }
1008
+ end
1009
+
1010
+ events = d.events
1011
+ assert_equal(utf8_message, events[0][2]['message'])
1012
+ assert_equal(Encoding::UTF_8, events[0][2]['message'].encoding)
1013
+ end
1014
+
1015
+ def test_from_encoding_utf16
1016
+ conf = config_element(
1017
+ "", "", {
1018
+ "format" => "/(?<message>.*)/",
1019
+ "read_from_head" => "true",
1020
+ "from_encoding" => "utf-16le",
1021
+ "encoding" => "utf-8"
1022
+ })
1023
+ d = create_driver(conf)
1024
+ utf16_message = "\u306F\u308D\u30FC\u308F\u30FC\u308B\u3069\n".encode(Encoding::UTF_16LE)
1025
+ utf8_message = utf16_message.encode(Encoding::UTF_8).strip
1026
+
1027
+ d.run(expect_emits: 1) do
1028
+ File.open("#{TMP_DIR}/tail.txt", "w:utf-16le") { |f|
1029
+ f.write utf16_message
1030
+ }
1031
+ end
1032
+
1033
+ events = d.events
1034
+ assert_equal(utf8_message, events[0][2]['message'])
1035
+ assert_equal(Encoding::UTF_8, events[0][2]['message'].encoding)
1036
+ end
1037
+
1038
+ def test_encoding_with_bad_character
1039
+ conf = config_element(
1040
+ "", "", {
1041
+ "format" => "/(?<message>.*)/",
1042
+ "read_from_head" => "true",
1043
+ "from_encoding" => "ASCII-8BIT",
1044
+ "encoding" => "utf-8"
1045
+ })
1046
+ d = create_driver(conf)
1047
+
1048
+ d.run(expect_emits: 1) do
1049
+ File.open("#{TMP_DIR}/tail.txt", "w") { |f|
1050
+ f.write "te\x86st\n"
1051
+ }
1052
+ end
1053
+
1054
+ events = d.events
1055
+ assert_equal("te\uFFFDst", events[0][2]['message'])
1056
+ assert_equal(Encoding::UTF_8, events[0][2]['message'].encoding)
1057
+ end
1058
+
1059
+ sub_test_case "multiline" do
1060
+ data(flat: MULTILINE_CONFIG,
1061
+ parse: PARSE_MULTILINE_CONFIG)
1062
+ def test_multiline(data)
1063
+ config = data
1064
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1065
+
1066
+ d = create_driver(config)
1067
+ d.run(expect_emits: 1) do
1068
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1069
+ f.puts "f test1"
1070
+ f.puts "s test2"
1071
+ f.puts "f test3"
1072
+ f.puts "f test4"
1073
+ f.puts "s test5"
1074
+ f.puts "s test6"
1075
+ f.puts "f test7"
1076
+ f.puts "s test8"
1077
+ }
1078
+ end
1079
+
1080
+ events = d.events
1081
+ assert_equal(4, events.length)
1082
+ assert_equal({"message1" => "test2", "message2" => "test3", "message3" => "test4"}, events[0][2])
1083
+ assert_equal({"message1" => "test5"}, events[1][2])
1084
+ assert_equal({"message1" => "test6", "message2" => "test7"}, events[2][2])
1085
+ assert_equal({"message1" => "test8"}, events[3][2])
1086
+ end
1087
+
1088
+ data(flat: MULTILINE_CONFIG,
1089
+ parse: PARSE_MULTILINE_CONFIG)
1090
+ def test_multiline_with_emit_unmatched_lines_true(data)
1091
+ config = data + config_element("", "", { "emit_unmatched_lines" => true })
1092
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1093
+
1094
+ d = create_driver(config)
1095
+ d.run(expect_emits: 1) do
1096
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1097
+ f.puts "f test1"
1098
+ f.puts "s test2"
1099
+ f.puts "f test3"
1100
+ f.puts "f test4"
1101
+ f.puts "s test5"
1102
+ f.puts "s test6"
1103
+ f.puts "f test7"
1104
+ f.puts "s test8"
1105
+ }
1106
+ end
1107
+
1108
+ events = d.events
1109
+ assert_equal(5, events.length)
1110
+ assert_equal({"unmatched_line" => "f test1"}, events[0][2])
1111
+ assert_equal({"message1" => "test2", "message2" => "test3", "message3" => "test4"}, events[1][2])
1112
+ assert_equal({"message1" => "test5"}, events[2][2])
1113
+ assert_equal({"message1" => "test6", "message2" => "test7"}, events[3][2])
1114
+ assert_equal({"message1" => "test8"}, events[4][2])
1115
+ end
1116
+
1117
+ data(
1118
+ flat: MULTILINE_CONFIG_WITH_NEWLINE,
1119
+ parse: PARSE_MULTILINE_CONFIG_WITH_NEWLINE)
1120
+ def test_multiline_with_emit_unmatched_lines2(data)
1121
+ config = data + config_element("", "", { "emit_unmatched_lines" => true })
1122
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1123
+
1124
+ d = create_driver(config)
1125
+ d.run(expect_emits: 0, timeout: 1) do
1126
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1127
+ f.puts "s test0"
1128
+ f.puts "f test1"
1129
+ f.puts "f test2"
1130
+
1131
+ f.puts "f test3"
1132
+
1133
+ f.puts "s test4"
1134
+ f.puts "f test5"
1135
+ f.puts "f test6"
1136
+ }
1137
+ end
1138
+
1139
+ events = d.events
1140
+ assert_equal({"message1" => "test0", "message2" => "test1", "message3" => "test2"}, events[0][2])
1141
+ assert_equal({ 'unmatched_line' => "f test3" }, events[1][2])
1142
+ assert_equal({"message1" => "test4", "message2" => "test5", "message3" => "test6"}, events[2][2])
1143
+ end
1144
+
1145
+ data(flat: MULTILINE_CONFIG,
1146
+ parse: PARSE_MULTILINE_CONFIG)
1147
+ def test_multiline_with_flush_interval(data)
1148
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1149
+
1150
+ config = data + config_element("", "", { "multiline_flush_interval" => "2s" })
1151
+ d = create_driver(config)
1152
+
1153
+ assert_equal 2, d.instance.multiline_flush_interval
1154
+
1155
+ d.run(expect_emits: 1) do
1156
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1157
+ f.puts "f test1"
1158
+ f.puts "s test2"
1159
+ f.puts "f test3"
1160
+ f.puts "f test4"
1161
+ f.puts "s test5"
1162
+ f.puts "s test6"
1163
+ f.puts "f test7"
1164
+ f.puts "s test8"
1165
+ }
1166
+ end
1167
+
1168
+ events = d.events
1169
+ assert_equal(4, events.length)
1170
+ assert_equal({"message1" => "test2", "message2" => "test3", "message3" => "test4"}, events[0][2])
1171
+ assert_equal({"message1" => "test5"}, events[1][2])
1172
+ assert_equal({"message1" => "test6", "message2" => "test7"}, events[2][2])
1173
+ assert_equal({"message1" => "test8"}, events[3][2])
1174
+ end
1175
+
1176
+ data(
1177
+ 'flat default encoding' => [MULTILINE_CONFIG, Encoding::ASCII_8BIT],
1178
+ 'flat explicit encoding config' => [MULTILINE_CONFIG + config_element("", "", { "encoding" => "utf-8" }), Encoding::UTF_8],
1179
+ 'parse default encoding' => [PARSE_MULTILINE_CONFIG, Encoding::ASCII_8BIT],
1180
+ 'parse explicit encoding config' => [PARSE_MULTILINE_CONFIG + config_element("", "", { "encoding" => "utf-8" }), Encoding::UTF_8])
1181
+ def test_multiline_encoding_of_flushed_record(data)
1182
+ encoding_config, encoding = data
1183
+
1184
+ config = config_element("", "", {
1185
+ "multiline_flush_interval" => "2s",
1186
+ "read_from_head" => "true",
1187
+ })
1188
+ d = create_driver(config + encoding_config)
1189
+
1190
+ d.run(expect_emits: 1) do
1191
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f|
1192
+ f.puts "s test"
1193
+ }
1194
+ end
1195
+ events = d.events
1196
+ assert_equal(1, events.length)
1197
+ assert_equal(encoding, events[0][2]['message1'].encoding)
1198
+ end
1199
+
1200
+ def test_multiline_from_encoding_of_flushed_record
1201
+ conf = MULTILINE_CONFIG + config_element(
1202
+ "", "",
1203
+ {
1204
+ "multiline_flush_interval" => "1s",
1205
+ "read_from_head" => "true",
1206
+ "from_encoding" => "cp932",
1207
+ "encoding" => "utf-8"
1208
+ })
1209
+ d = create_driver(conf)
1210
+
1211
+ cp932_message = "s \x82\xCD\x82\xEB\x81\x5B\x82\xED\x81\x5B\x82\xE9\x82\xC7".force_encoding(Encoding::CP932)
1212
+ utf8_message = "\x82\xCD\x82\xEB\x81\x5B\x82\xED\x81\x5B\x82\xE9\x82\xC7".encode(Encoding::UTF_8, Encoding::CP932)
1213
+ d.run(expect_emits: 1) do
1214
+ File.open("#{TMP_DIR}/tail.txt", "w:cp932") { |f|
1215
+ f.puts cp932_message
1216
+ }
1217
+ end
1218
+
1219
+ events = d.events
1220
+ assert_equal(1, events.length)
1221
+ assert_equal(utf8_message, events[0][2]['message1'])
1222
+ assert_equal(Encoding::UTF_8, events[0][2]['message1'].encoding)
1223
+ end
1224
+
1225
+ data(flat: config_element(
1226
+ "", "", {
1227
+ "format" => "multiline",
1228
+ "format1" => "/^s (?<message1>[^\\n]+)\\n?/",
1229
+ "format2" => "/(f (?<message2>[^\\n]+)\\n?)?/",
1230
+ "format3" => "/(f (?<message3>.*))?/",
1231
+ "format_firstline" => "/^[s]/"
1232
+ }),
1233
+ parse: config_element(
1234
+ "", "", {},
1235
+ [config_element("parse", "", {
1236
+ "@type" => "multiline",
1237
+ "format1" => "/^s (?<message1>[^\\n]+)\\n?/",
1238
+ "format2" => "/(f (?<message2>[^\\n]+)\\n?)?/",
1239
+ "format3" => "/(f (?<message3>.*))?/",
1240
+ "format_firstline" => "/^[s]/"
1241
+ })
1242
+ ])
1243
+ )
1244
+ def test_multiline_with_multiple_formats(data)
1245
+ config = data
1246
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1247
+
1248
+ d = create_driver(config)
1249
+ d.run(expect_emits: 1) do
1250
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1251
+ f.puts "f test1"
1252
+ f.puts "s test2"
1253
+ f.puts "f test3"
1254
+ f.puts "f test4"
1255
+ f.puts "s test5"
1256
+ f.puts "s test6"
1257
+ f.puts "f test7"
1258
+ f.puts "s test8"
1259
+ }
1260
+ end
1261
+
1262
+ events = d.events
1263
+ assert(events.length > 0)
1264
+ assert_equal({"message1" => "test2", "message2" => "test3", "message3" => "test4"}, events[0][2])
1265
+ assert_equal({"message1" => "test5"}, events[1][2])
1266
+ assert_equal({"message1" => "test6", "message2" => "test7"}, events[2][2])
1267
+ assert_equal({"message1" => "test8"}, events[3][2])
1268
+ end
1269
+
1270
+ data(flat: config_element(
1271
+ "", "", {
1272
+ "format" => "multiline",
1273
+ "format1" => "/^[s|f] (?<message>.*)/",
1274
+ "format_firstline" => "/^[s]/"
1275
+ }),
1276
+ parse: config_element(
1277
+ "", "", {},
1278
+ [config_element("parse", "", {
1279
+ "@type" => "multiline",
1280
+ "format1" => "/^[s|f] (?<message>.*)/",
1281
+ "format_firstline" => "/^[s]/"
1282
+ })
1283
+ ])
1284
+ )
1285
+ def test_multilinelog_with_multiple_paths(data)
1286
+ files = ["#{TMP_DIR}/tail1.txt", "#{TMP_DIR}/tail2.txt"]
1287
+ files.each { |file| File.open(file, "wb") { |f| } }
1288
+
1289
+ config = data + config_element("", "", {
1290
+ "path" => "#{files[0]},#{files[1]}",
1291
+ "tag" => "t1",
1292
+ })
1293
+ d = create_driver(config, false)
1294
+ d.run(expect_emits: 2) do
1295
+ files.each do |file|
1296
+ File.open(file, 'ab') { |f|
1297
+ f.puts "f #{file} line should be ignored"
1298
+ f.puts "s test1"
1299
+ f.puts "f test2"
1300
+ f.puts "f test3"
1301
+ f.puts "s test4"
1302
+ }
1303
+ end
1304
+ end
1305
+
1306
+ events = d.events
1307
+ assert_equal({"message" => "test1\nf test2\nf test3"}, events[0][2])
1308
+ assert_equal({"message" => "test1\nf test2\nf test3"}, events[1][2])
1309
+ # "test4" events are here because these events are flushed at shutdown phase
1310
+ assert_equal({"message" => "test4"}, events[2][2])
1311
+ assert_equal({"message" => "test4"}, events[3][2])
1312
+ end
1313
+
1314
+ data(flat: config_element("", "", {
1315
+ "format" => "multiline",
1316
+ "format1" => "/(?<var1>foo \\d)\\n/",
1317
+ "format2" => "/(?<var2>bar \\d)\\n/",
1318
+ "format3" => "/(?<var3>baz \\d)/"
1319
+ }),
1320
+ parse: config_element(
1321
+ "", "", {},
1322
+ [config_element("parse", "", {
1323
+ "@type" => "multiline",
1324
+ "format1" => "/(?<var1>foo \\d)\\n/",
1325
+ "format2" => "/(?<var2>bar \\d)\\n/",
1326
+ "format3" => "/(?<var3>baz \\d)/"
1327
+ })
1328
+ ])
1329
+ )
1330
+ def test_multiline_without_firstline(data)
1331
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
1332
+
1333
+ config = data
1334
+ d = create_driver(config)
1335
+ d.run(expect_emits: 1) do
1336
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
1337
+ f.puts "foo 1"
1338
+ f.puts "bar 1"
1339
+ f.puts "baz 1"
1340
+ f.puts "foo 2"
1341
+ f.puts "bar 2"
1342
+ f.puts "baz 2"
1343
+ }
1344
+ end
1345
+
1346
+ events = d.events
1347
+ assert_equal(2, events.length)
1348
+ assert_equal({"var1" => "foo 1", "var2" => "bar 1", "var3" => "baz 1"}, events[0][2])
1349
+ assert_equal({"var1" => "foo 2", "var2" => "bar 2", "var3" => "baz 2"}, events[1][2])
1350
+ end
1351
+ end
1352
+
1353
+ sub_test_case "path" do
1354
+ # * path test
1355
+ # TODO: Clean up tests
1356
+ EX_ROTATE_WAIT = 0
1357
+ EX_FOLLOW_INODES = false
1358
+
1359
+ EX_CONFIG = config_element("", "", {
1360
+ "tag" => "tail",
1361
+ "path" => "test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log",
1362
+ "format" => "none",
1363
+ "pos_file" => "#{TMP_DIR}/tail.pos",
1364
+ "read_from_head" => true,
1365
+ "refresh_interval" => 30,
1366
+ "rotate_wait" => "#{EX_ROTATE_WAIT}s",
1367
+ "follow_inodes" => "#{EX_FOLLOW_INODES}",
1368
+ })
1369
+ def test_expand_paths
1370
+ ex_paths = [
1371
+ create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
1372
+ create_target_info('test/plugin/data/log/foo/bar.log'),
1373
+ create_target_info('test/plugin/data/log/test.log')
1374
+ ]
1375
+ plugin = create_driver(EX_CONFIG, false).instance
1376
+ flexstub(Time) do |timeclass|
1377
+ timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 2, 3, 4, 5))
1378
+ assert_equal ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1379
+ end
1380
+
1381
+ # Test exclusion
1382
+ exclude_config = EX_CONFIG + config_element("", "", { "exclude_path" => %Q(["#{ex_paths.last.path}"]) })
1383
+ plugin = create_driver(exclude_config, false).instance
1384
+ assert_equal ex_paths - [ex_paths.last], plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1385
+ end
1386
+
1387
+ def test_expand_paths_with_duplicate_configuration
1388
+ expanded_paths = [
1389
+ create_target_info('test/plugin/data/log/foo/bar.log'),
1390
+ create_target_info('test/plugin/data/log/test.log')
1391
+ ]
1392
+ duplicate_config = EX_CONFIG.dup
1393
+ duplicate_config["path"]="test/plugin/data/log/**/*.log, test/plugin/data/log/**/*.log"
1394
+ plugin = create_driver(EX_CONFIG, false).instance
1395
+ assert_equal expanded_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1396
+ end
1397
+
1398
+ def test_expand_paths_with_timezone
1399
+ ex_paths = [
1400
+ create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
1401
+ create_target_info('test/plugin/data/log/foo/bar.log'),
1402
+ create_target_info('test/plugin/data/log/test.log')
1403
+ ]
1404
+ ['Asia/Taipei', '+08'].each do |tz_type|
1405
+ taipei_config = EX_CONFIG + config_element("", "", {"path_timezone" => tz_type})
1406
+ plugin = create_driver(taipei_config, false).instance
1407
+
1408
+ # Test exclude
1409
+ exclude_config = taipei_config + config_element("", "", { "exclude_path" => %Q(["test/plugin/**/%Y%m%d-%H%M%S.log"]) })
1410
+ exclude_plugin = create_driver(exclude_config, false).instance
1411
+
1412
+ with_timezone('utc') do
1413
+ flexstub(Time) do |timeclass|
1414
+ # env : 2010-01-01 19:04:05 (UTC), tail path : 2010-01-02 03:04:05 (Asia/Taipei)
1415
+ timeclass.should_receive(:now).with_no_args.and_return(Time.new(2010, 1, 1, 19, 4, 5))
1416
+
1417
+ assert_equal ex_paths, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1418
+ assert_equal ex_paths - [ex_paths.first], exclude_plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1419
+ end
1420
+ end
1421
+ end
1422
+ end
1423
+
1424
+ def test_log_file_without_extension
1425
+ expected_files = [
1426
+ create_target_info('test/plugin/data/log/bar'),
1427
+ create_target_info('test/plugin/data/log/foo/bar.log'),
1428
+ create_target_info('test/plugin/data/log/foo/bar2'),
1429
+ create_target_info('test/plugin/data/log/test.log')
1430
+ ]
1431
+
1432
+ config = config_element("", "", {
1433
+ "tag" => "tail",
1434
+ "path" => "test/plugin/data/log/**/*",
1435
+ "format" => "none",
1436
+ "pos_file" => "#{TMP_DIR}/tail.pos"
1437
+ })
1438
+
1439
+ plugin = create_driver(config, false).instance
1440
+ assert_equal expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
1441
+ end
1442
+
1443
+ def test_unwatched_files_should_be_removed
1444
+ config = config_element("", "", {
1445
+ "tag" => "tail",
1446
+ "path" => "#{TMP_DIR}/*.txt",
1447
+ "format" => "none",
1448
+ "pos_file" => "#{TMP_DIR}/tail.pos",
1449
+ "read_from_head" => true,
1450
+ "refresh_interval" => 1,
1451
+ })
1452
+ d = create_driver(config, false)
1453
+ d.end_if { d.instance.instance_variable_get(:@tails).keys.size >= 1 }
1454
+ d.run(expect_emits: 1, shutdown: false) do
1455
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f| f.puts "test3\n" }
1456
+ end
1457
+
1458
+ cleanup_directory(TMP_DIR)
1459
+ waiting(20) { sleep 0.1 until Dir.glob("#{TMP_DIR}/*.txt").size == 0 } # Ensure file is deleted on Windows
1460
+ waiting(5) { sleep 0.1 until d.instance.instance_variable_get(:@tails).keys.size <= 0 }
1461
+
1462
+ assert_equal 0, d.instance.instance_variable_get(:@tails).keys.size
1463
+
1464
+ d.instance_shutdown
1465
+ end
1466
+
1467
+ def count_timer_object
1468
+ num = 0
1469
+ ObjectSpace.each_object(Fluent::PluginHelper::Timer::TimerWatcher) { |obj|
1470
+ num += 1
1471
+ }
1472
+ num
1473
+ end
1474
+ end
1475
+
1476
+ sub_test_case "path w/ Linux capability" do
1477
+ def capability_enabled?
1478
+ if Fluent.linux?
1479
+ begin
1480
+ require 'capng'
1481
+ true
1482
+ rescue LoadError
1483
+ false
1484
+ end
1485
+ else
1486
+ false
1487
+ end
1488
+ end
1489
+
1490
+ setup do
1491
+ omit "This environment is not enabled Linux capability handling feature" unless capability_enabled?
1492
+
1493
+ @capng = CapNG.new(:current_process)
1494
+ flexstub(Fluent::Capability) do |klass|
1495
+ klass.should_receive(:new).with(:current_process).and_return(@capng)
1496
+ end
1497
+ end
1498
+
1499
+ data("dac_read_search" => [:dac_read_search, true, 1],
1500
+ "dac_override" => [:dac_override, true, 1],
1501
+ "chown" => [:chown, false, 0],
1502
+ )
1503
+ test "with partially elevated privileges" do |data|
1504
+ cap, result, readable_paths = data
1505
+ @capng.update(:add, :effective, cap)
1506
+
1507
+ d = create_driver(
1508
+ config_element("ROOT", "", {
1509
+ "path" => "/var/log/ker*.log", # Use /var/log/kern.log
1510
+ "tag" => "t1",
1511
+ "rotate_wait" => "2s"
1512
+ }) + PARSE_SINGLE_LINE_CONFIG, false)
1513
+
1514
+ assert_equal readable_paths, d.instance.expand_paths.length
1515
+ assert_equal result, d.instance.have_read_capability?
1516
+ end
1517
+ end
1518
+
1519
+ def test_pos_file_dir_creation
1520
+ config = config_element("", "", {
1521
+ "tag" => "tail",
1522
+ "path" => "#{TMP_DIR}/*.txt",
1523
+ "format" => "none",
1524
+ "pos_file" => "#{TMP_DIR}/pos/tail.pos",
1525
+ "read_from_head" => true,
1526
+ "refresh_interval" => 1
1527
+ })
1528
+
1529
+ assert_path_not_exist("#{TMP_DIR}/pos")
1530
+ d = create_driver(config, false)
1531
+ d.run
1532
+ assert_path_exist("#{TMP_DIR}/pos")
1533
+ assert_equal '755', File.stat("#{TMP_DIR}/pos").mode.to_s(8)[-3, 3]
1534
+ ensure
1535
+ cleanup_directory(TMP_DIR)
1536
+ end
1537
+
1538
+ def test_pos_file_dir_creation_with_system_dir_permission
1539
+ config = config_element("", "", {
1540
+ "tag" => "tail",
1541
+ "path" => "#{TMP_DIR}/*.txt",
1542
+ "format" => "none",
1543
+ "pos_file" => "#{TMP_DIR}/pos/tail.pos",
1544
+ "read_from_head" => true,
1545
+ "refresh_interval" => 1
1546
+ })
1547
+
1548
+ assert_path_not_exist("#{TMP_DIR}/pos")
1549
+
1550
+ Fluent::SystemConfig.overwrite_system_config({ "dir_permission" => "744" }) do
1551
+ d = create_driver(config, false)
1552
+ d.run
1553
+ end
1554
+
1555
+ assert_path_exist("#{TMP_DIR}/pos")
1556
+ if Fluent.windows?
1557
+ assert_equal '755', File.stat("#{TMP_DIR}/pos").mode.to_s(8)[-3, 3]
1558
+ else
1559
+ assert_equal '744', File.stat("#{TMP_DIR}/pos").mode.to_s(8)[-3, 3]
1560
+ end
1561
+ ensure
1562
+ cleanup_directory(TMP_DIR)
1563
+ end
1564
+
1565
+ def test_z_refresh_watchers
1566
+ ex_paths = [
1567
+ create_target_info('test/plugin/data/2010/01/20100102-030405.log'),
1568
+ create_target_info('test/plugin/data/log/foo/bar.log'),
1569
+ create_target_info('test/plugin/data/log/test.log'),
1570
+ ]
1571
+ plugin = create_driver(EX_CONFIG, false).instance
1572
+ sio = StringIO.new
1573
+ plugin.instance_eval do
1574
+ @pf = Fluent::Plugin::TailInput::PositionFile.load(sio, EX_FOLLOW_INODES, {}, logger: $log)
1575
+ @loop = Coolio::Loop.new
1576
+ opened_file_metrics = Fluent::Plugin::LocalMetrics.new
1577
+ opened_file_metrics.configure(config_element('metrics', '', {}))
1578
+ closed_file_metrics = Fluent::Plugin::LocalMetrics.new
1579
+ closed_file_metrics.configure(config_element('metrics', '', {}))
1580
+ rotated_file_metrics = Fluent::Plugin::LocalMetrics.new
1581
+ rotated_file_metrics.configure(config_element('metrics', '', {}))
1582
+ @metrics = Fluent::Plugin::TailInput::MetricsInfo.new(opened_file_metrics, closed_file_metrics, rotated_file_metrics)
1583
+ end
1584
+
1585
+ Timecop.freeze(2010, 1, 2, 3, 4, 5) do
1586
+ ex_paths.each do |target_info|
1587
+ mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything, anything).once
1588
+ end
1589
+
1590
+ plugin.refresh_watchers
1591
+ end
1592
+
1593
+ path = 'test/plugin/data/2010/01/20100102-030405.log'
1594
+ target_info = Fluent::Plugin::TailInput::TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
1595
+ mock.proxy(plugin).detach_watcher_after_rotate_wait(plugin.instance_variable_get(:@tails)[target_info], target_info.ino)
1596
+
1597
+ Timecop.freeze(2010, 1, 2, 3, 4, 6) do
1598
+ path = "test/plugin/data/2010/01/20100102-030406.log"
1599
+ inode = Fluent::FileWrapper.stat(path).ino
1600
+ target_info = Fluent::Plugin::TailInput::TargetInfo.new(path, inode)
1601
+ mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, false, anything, nil, anything, anything).once
1602
+ plugin.refresh_watchers
1603
+
1604
+ flexstub(Fluent::Plugin::TailInput::TailWatcher) do |watcherclass|
1605
+ watcherclass.should_receive(:new).never
1606
+ plugin.refresh_watchers
1607
+ end
1608
+ end
1609
+ end
1610
+
1611
+ sub_test_case "refresh of pos file" do
1612
+ test 'type of pos_file_compaction_interval is time' do
1613
+ tail = {
1614
+ "tag" => "tail",
1615
+ "path" => "#{TMP_DIR}/*.txt",
1616
+ "format" => "none",
1617
+ "pos_file" => "#{TMP_DIR}/pos/tail.pos",
1618
+ "refresh_interval" => 1,
1619
+ "read_from_head" => true,
1620
+ 'pos_file_compaction_interval' => '24h',
1621
+ }
1622
+ config = config_element("", "", tail)
1623
+ d = create_driver(config, false)
1624
+ mock(d.instance).timer_execute(:in_tail_refresh_watchers, 1.0).once
1625
+ mock(d.instance).timer_execute(:in_tail_refresh_compact_pos_file, 60 * 60 * 24).once
1626
+ d.run # call start
1627
+ end
1628
+ end
1629
+
1630
+ sub_test_case "receive_lines" do
1631
+ DummyWatcher = Struct.new("DummyWatcher", :tag)
1632
+
1633
+ def test_tag
1634
+ d = create_driver(EX_CONFIG, false)
1635
+ d.run {}
1636
+ plugin = d.instance
1637
+ mock(plugin.router).emit_stream('tail', anything).once
1638
+ plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
1639
+ end
1640
+
1641
+ def test_tag_with_only_star
1642
+ config = config_element("", "", {
1643
+ "tag" => "*",
1644
+ "path" => "test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log",
1645
+ "format" => "none",
1646
+ "read_from_head" => true
1647
+ })
1648
+ d = create_driver(config, false)
1649
+ d.run {}
1650
+ plugin = d.instance
1651
+ mock(plugin.router).emit_stream('foo.bar.log', anything).once
1652
+ plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
1653
+ end
1654
+
1655
+ def test_tag_prefix
1656
+ config = config_element("", "", {
1657
+ "tag" => "pre.*",
1658
+ "path" => "test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log",
1659
+ "format" => "none",
1660
+ "read_from_head" => true
1661
+ })
1662
+ d = create_driver(config, false)
1663
+ d.run {}
1664
+ plugin = d.instance
1665
+ mock(plugin.router).emit_stream('pre.foo.bar.log', anything).once
1666
+ plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
1667
+ end
1668
+
1669
+ def test_tag_suffix
1670
+ config = config_element("", "", {
1671
+ "tag" => "*.post",
1672
+ "path" => "test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log",
1673
+ "format" => "none",
1674
+ "read_from_head" => true
1675
+ })
1676
+ d = create_driver(config, false)
1677
+ d.run {}
1678
+ plugin = d.instance
1679
+ mock(plugin.router).emit_stream('foo.bar.log.post', anything).once
1680
+ plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
1681
+ end
1682
+
1683
+ def test_tag_prefix_and_suffix
1684
+ config = config_element("", "", {
1685
+ "tag" => "pre.*.post",
1686
+ "path" => "test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log",
1687
+ "format" => "none",
1688
+ "read_from_head" => true
1689
+ })
1690
+ d = create_driver(config, false)
1691
+ d.run {}
1692
+ plugin = d.instance
1693
+ mock(plugin.router).emit_stream('pre.foo.bar.log.post', anything).once
1694
+ plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
1695
+ end
1696
+
1697
+ def test_tag_prefix_and_suffix_ignore
1698
+ config = config_element("", "", {
1699
+ "tag" => "pre.*.post*ignore",
1700
+ "path" => "test/plugin/*/%Y/%m/%Y%m%d-%H%M%S.log,test/plugin/data/log/**/*.log",
1701
+ "format" => "none",
1702
+ "read_from_head" => true
1703
+ })
1704
+ d = create_driver(config, false)
1705
+ d.run {}
1706
+ plugin = d.instance
1707
+ mock(plugin.router).emit_stream('pre.foo.bar.log.post', anything).once
1708
+ plugin.receive_lines(['foo', 'bar'], DummyWatcher.new('foo.bar.log'))
1709
+ end
1710
+
1711
+ data(
1712
+ small: ["128", 128],
1713
+ KiB: ["1k", 1024]
1714
+ )
1715
+ test 'max_line_size' do |(label, size)|
1716
+ config = config_element("", "", {
1717
+ "tag" => "max_line_size",
1718
+ "path" => "#{TMP_DIR}/with_long_lines.txt",
1719
+ "format" => "none",
1720
+ "read_from_head" => true,
1721
+ "max_line_size" => label,
1722
+ "log_level" => "debug"
1723
+ })
1724
+ File.open("#{TMP_DIR}/with_long_lines.txt", "w+") do |f|
1725
+ f.puts "foo"
1726
+ f.puts "x" * size # 'x' * size + \n > @max_line_size
1727
+ f.puts "bar"
1728
+ end
1729
+ d = create_driver(config, false)
1730
+ timestamp = Time.parse("Mon Nov 29 11:22:33 UTC 2021")
1731
+ Timecop.freeze(timestamp)
1732
+ d.run(expect_records: 2)
1733
+ assert_equal([
1734
+ [{"message" => "foo"},{"message" => "bar"}],
1735
+ [
1736
+ "2021-11-29 11:22:33 +0000 [warn]: received line length is longer than #{size}\n",
1737
+ "2021-11-29 11:22:33 +0000 [debug]: skipped line: #{'x' * size}\n"
1738
+ ]
1739
+ ],
1740
+ [
1741
+ d.events.collect { |event| event.last },
1742
+ d.logs[-2..]
1743
+ ])
1744
+ end
1745
+ end
1746
+
1747
+ # Ensure that no fatal exception is raised when a file is missing and that
1748
+ # files that do exist are still tailed as expected.
1749
+ def test_missing_file
1750
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1751
+ f.puts "test1"
1752
+ f.puts "test2"
1753
+ }
1754
+
1755
+ # Try two different configs - one with read_from_head and one without,
1756
+ # since their interactions with the filesystem differ.
1757
+ config1 = config_element("", "", {
1758
+ "tag" => "t1",
1759
+ "path" => "#{TMP_DIR}/non_existent_file.txt,#{TMP_DIR}/tail.txt",
1760
+ "format" => "none",
1761
+ "rotate_wait" => "2s",
1762
+ "pos_file" => "#{TMP_DIR}/tail.pos"
1763
+ })
1764
+ config2 = config1 + config_element("", "", { "read_from_head" => true })
1765
+ [config1, config2].each do |config|
1766
+ d = create_driver(config, false)
1767
+ d.run(expect_emits: 1) do
1768
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
1769
+ f.puts "test3"
1770
+ f.puts "test4"
1771
+ }
1772
+ end
1773
+ # This test sometimes fails and it shows a potential bug of in_tail
1774
+ # https://github.com/fluent/fluentd/issues/1434
1775
+ events = d.events
1776
+ assert_equal(2, events.length)
1777
+ assert_equal({"message" => "test3"}, events[0][2])
1778
+ assert_equal({"message" => "test4"}, events[1][2])
1779
+ end
1780
+ end
1781
+
1782
+ sub_test_case 'inode_processing' do
1783
+ def test_should_delete_file_pos_entry_for_non_existing_file_with_follow_inodes
1784
+ config = COMMON_FOLLOW_INODE_CONFIG
1785
+
1786
+ path = "#{TMP_DIR}/tail.txt"
1787
+ ino = 1
1788
+ pos = 1234
1789
+ File.open("#{TMP_DIR}/tail.pos", "wb") {|f|
1790
+ f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
1791
+ }
1792
+
1793
+ d = create_driver(config, false)
1794
+ d.run
1795
+
1796
+ pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
1797
+ pos_file.pos = 0
1798
+
1799
+ assert_raise(EOFError) do
1800
+ pos_file.readline
1801
+ end
1802
+ end
1803
+
1804
+ def test_should_write_latest_offset_after_rotate_wait
1805
+ config = COMMON_FOLLOW_INODE_CONFIG
1806
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1807
+ f.puts "test1"
1808
+ f.puts "test2"
1809
+ }
1810
+
1811
+ d = create_driver(config, false)
1812
+ d.run(expect_emits: 2, shutdown: false) do
1813
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1814
+ FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt" + "1")
1815
+ sleep 1
1816
+ File.open("#{TMP_DIR}/tail.txt" + "1", "ab") {|f| f.puts "test4\n"}
1817
+ end
1818
+
1819
+ pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
1820
+ pos_file.pos = 0
1821
+ line_parts = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
1822
+ waiting(5) {
1823
+ while line_parts[2].to_i(16) != 24
1824
+ sleep(0.1)
1825
+ pos_file.pos = 0
1826
+ line_parts = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(pos_file.readline)
1827
+ end
1828
+ }
1829
+ assert_equal(24, line_parts[2].to_i(16))
1830
+ d.instance_shutdown
1831
+ end
1832
+
1833
+ def test_should_remove_deleted_file
1834
+ config = config_element("", "", {"format" => "none"})
1835
+
1836
+ path = "#{TMP_DIR}/tail.txt"
1837
+ ino = 1
1838
+ pos = 1234
1839
+ File.open("#{TMP_DIR}/tail.pos", "wb") {|f|
1840
+ f.puts ("%s\t%016x\t%016x\n" % [path, pos, ino])
1841
+ }
1842
+
1843
+ d = create_driver(config)
1844
+ d.run do
1845
+ pos_file = File.open("#{TMP_DIR}/tail.pos", "r")
1846
+ pos_file.pos = 0
1847
+ assert_equal([], pos_file.readlines)
1848
+ end
1849
+ end
1850
+
1851
+ def test_should_mark_file_unwatched_after_limit_recently_modified_and_rotate_wait
1852
+ config = config_element("ROOT", "", {
1853
+ "path" => "#{TMP_DIR}/tail.txt*",
1854
+ "pos_file" => "#{TMP_DIR}/tail.pos",
1855
+ "tag" => "t1",
1856
+ "rotate_wait" => "1s",
1857
+ "refresh_interval" => "1s",
1858
+ "limit_recently_modified" => "1s",
1859
+ "read_from_head" => "true",
1860
+ "format" => "none",
1861
+ "follow_inodes" => "true",
1862
+ })
1863
+
1864
+ d = create_driver(config, false)
1865
+
1866
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1867
+ f.puts "test1"
1868
+ f.puts "test2"
1869
+ }
1870
+ target_info = create_target_info("#{TMP_DIR}/tail.txt")
1871
+
1872
+ d.run(expect_emits: 1, shutdown: false) do
1873
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1874
+ end
1875
+
1876
+
1877
+ Timecop.travel(Time.now + 10) do
1878
+ waiting(5) {
1879
+ # @pos will be reset as 0 when UNWATCHED_POSITION is specified.
1880
+ sleep 0.1 until d.instance.instance_variable_get(:@pf)[target_info].read_pos == 0
1881
+ }
1882
+ end
1883
+
1884
+ assert_equal(0, d.instance.instance_variable_get(:@pf)[target_info].read_pos)
1885
+
1886
+ d.instance_shutdown
1887
+ end
1888
+
1889
+ def test_should_read_from_head_on_file_renaming_with_star_in_pattern
1890
+ config = config_element("ROOT", "", {
1891
+ "path" => "#{TMP_DIR}/tail.txt*",
1892
+ "pos_file" => "#{TMP_DIR}/tail.pos",
1893
+ "tag" => "t1",
1894
+ "rotate_wait" => "10s",
1895
+ "refresh_interval" => "1s",
1896
+ "limit_recently_modified" => "60s",
1897
+ "read_from_head" => "true",
1898
+ "format" => "none",
1899
+ "follow_inodes" => "true"
1900
+ })
1901
+
1902
+ d = create_driver(config, false)
1903
+
1904
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1905
+ f.puts "test1"
1906
+ f.puts "test2"
1907
+ }
1908
+
1909
+ d.run(expect_emits: 2, shutdown: false) do
1910
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1911
+ FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt1")
1912
+ end
1913
+
1914
+ events = d.events
1915
+ assert_equal(3, events.length)
1916
+ d.instance_shutdown
1917
+ end
1918
+
1919
+ def test_should_not_read_from_head_on_rotation_when_watching_inodes
1920
+ config = COMMON_FOLLOW_INODE_CONFIG
1921
+
1922
+ d = create_driver(config, false)
1923
+
1924
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1925
+ f.puts "test1"
1926
+ f.puts "test2"
1927
+ }
1928
+
1929
+ d.run(expect_emits: 1, shutdown: false) do
1930
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1931
+ end
1932
+
1933
+ FileUtils.move("#{TMP_DIR}/tail.txt", "#{TMP_DIR}/tail.txt1")
1934
+ Timecop.travel(Time.now + 10) do
1935
+ sleep 2
1936
+ events = d.events
1937
+ assert_equal(3, events.length)
1938
+ end
1939
+
1940
+ d.instance_shutdown
1941
+ end
1942
+
1943
+ def test_should_mark_file_unwatched_if_same_name_file_created_with_different_inode
1944
+ config = COMMON_FOLLOW_INODE_CONFIG
1945
+
1946
+ d = create_driver(config, false)
1947
+
1948
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1949
+ f.puts "test1"
1950
+ f.puts "test2"
1951
+ }
1952
+ target_info = create_target_info("#{TMP_DIR}/tail.txt")
1953
+
1954
+ d.run(expect_emits: 2, shutdown: false) do
1955
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
1956
+ cleanup_file("#{TMP_DIR}/tail.txt")
1957
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f| f.puts "test4\n"}
1958
+ end
1959
+
1960
+ new_target_info = create_target_info("#{TMP_DIR}/tail.txt")
1961
+
1962
+ pos_file = d.instance.instance_variable_get(:@pf)
1963
+
1964
+ waiting(10) {
1965
+ # @pos will be reset as 0 when UNWATCHED_POSITION is specified.
1966
+ sleep 0.1 until pos_file[target_info].read_pos == 0
1967
+ }
1968
+ new_position = pos_file[new_target_info].read_pos
1969
+ assert_equal(6, new_position)
1970
+
1971
+ d.instance_shutdown
1972
+ end
1973
+
1974
+ def test_should_close_watcher_after_rotate_wait
1975
+ now = Time.now
1976
+ config = COMMON_FOLLOW_INODE_CONFIG + config_element('', '', {"rotate_wait" => "1s", "limit_recently_modified" => "1s"})
1977
+
1978
+ d = create_driver(config, false)
1979
+ d.instance.instance_eval do
1980
+ opened_file_metrics = Fluent::Plugin::LocalMetrics.new
1981
+ opened_file_metrics.configure(config_element('metrics', '', {}))
1982
+ closed_file_metrics = Fluent::Plugin::LocalMetrics.new
1983
+ closed_file_metrics.configure(config_element('metrics', '', {}))
1984
+ rotated_file_metrics = Fluent::Plugin::LocalMetrics.new
1985
+ rotated_file_metrics.configure(config_element('metrics', '', {}))
1986
+ @metrics = Fluent::Plugin::TailInput::MetricsInfo.new(opened_file_metrics, closed_file_metrics, rotated_file_metrics)
1987
+ end
1988
+
1989
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
1990
+ f.puts "test1"
1991
+ f.puts "test2"
1992
+ }
1993
+ target_info = create_target_info("#{TMP_DIR}/tail.txt")
1994
+ mock.proxy(Fluent::Plugin::TailInput::TailWatcher).new(target_info, anything, anything, true, true, anything, nil, anything, anything).once
1995
+ d.run(shutdown: false)
1996
+ assert d.instance.instance_variable_get(:@tails)[target_info]
1997
+
1998
+ Timecop.travel(now + 10) do
1999
+ d.instance.instance_eval do
2000
+ sleep 0.1 until @tails[target_info] == nil
2001
+ end
2002
+ assert_nil d.instance.instance_variable_get(:@tails)[target_info]
2003
+ end
2004
+ d.instance_shutdown
2005
+ end
2006
+
2007
+ def test_should_create_new_watcher_for_new_file_with_same_name
2008
+ now = Time.now
2009
+ config = COMMON_FOLLOW_INODE_CONFIG + config_element('', '', {"limit_recently_modified" => "2s"})
2010
+
2011
+ d = create_driver(config, false)
2012
+
2013
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2014
+ f.puts "test1"
2015
+ f.puts "test2"
2016
+ }
2017
+ path_ino = create_target_info("#{TMP_DIR}/tail.txt")
2018
+
2019
+ d.run(expect_emits: 1, shutdown: false) do
2020
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
2021
+ end
2022
+
2023
+ cleanup_file("#{TMP_DIR}/tail.txt")
2024
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2025
+ f.puts "test3"
2026
+ f.puts "test4"
2027
+ }
2028
+ new_path_ino = create_target_info("#{TMP_DIR}/tail.txt")
2029
+
2030
+ Timecop.travel(now + 10) do
2031
+ sleep 3
2032
+ d.instance.instance_eval do
2033
+ @tails[path_ino] == nil
2034
+ @tails[new_path_ino] != nil
2035
+ end
2036
+ end
2037
+
2038
+ events = d.events
2039
+
2040
+ assert_equal(5, events.length)
2041
+
2042
+ d.instance_shutdown
2043
+ end
2044
+
2045
+ def test_truncate_file_with_follow_inodes
2046
+ config = COMMON_FOLLOW_INODE_CONFIG
2047
+
2048
+ d = create_driver(config, false)
2049
+
2050
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2051
+ f.puts "test1"
2052
+ f.puts "test2"
2053
+ }
2054
+
2055
+ d.run(expect_emits: 3, shutdown: false) do
2056
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f| f.puts "test3\n"}
2057
+ sleep 2
2058
+ File.open("#{TMP_DIR}/tail.txt", "w+b") {|f| f.puts "test4\n"}
2059
+ end
2060
+
2061
+ events = d.events
2062
+ assert_equal(4, events.length)
2063
+ assert_equal({"message" => "test1"}, events[0][2])
2064
+ assert_equal({"message" => "test2"}, events[1][2])
2065
+ assert_equal({"message" => "test3"}, events[2][2])
2066
+ assert_equal({"message" => "test4"}, events[3][2])
2067
+ d.instance_shutdown
2068
+ end
2069
+
2070
+ # issue #3464
2071
+ def test_should_replace_target_info
2072
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2073
+ f.puts "test1\n"
2074
+ }
2075
+ target_info = create_target_info("#{TMP_DIR}/tail.txt")
2076
+ inodes = []
2077
+
2078
+ config = config_element("ROOT", "", {
2079
+ "path" => "#{TMP_DIR}/tail.txt*",
2080
+ "pos_file" => "#{TMP_DIR}/tail.pos",
2081
+ "tag" => "t1",
2082
+ "refresh_interval" => "60s",
2083
+ "read_from_head" => "true",
2084
+ "format" => "none",
2085
+ "rotate_wait" => "1s",
2086
+ "follow_inodes" => "true"
2087
+ })
2088
+ d = create_driver(config, false)
2089
+ d.run(timeout: 5) do
2090
+ while d.events.size < 1 do
2091
+ sleep 0.1
2092
+ end
2093
+ inodes = d.instance.instance_variable_get(:@tails).keys.collect do |key|
2094
+ key.ino
2095
+ end
2096
+ assert_equal([target_info.ino], inodes)
2097
+
2098
+ cleanup_file("#{TMP_DIR}/tail.txt")
2099
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f| f.puts "test2\n"}
2100
+
2101
+ while d.events.size < 2 do
2102
+ sleep 0.1
2103
+ end
2104
+ inodes = d.instance.instance_variable_get(:@tails).keys.collect do |key|
2105
+ key.ino
2106
+ end
2107
+ new_target_info = create_target_info("#{TMP_DIR}/tail.txt")
2108
+ assert_not_equal(target_info.ino, new_target_info.ino)
2109
+ assert_equal([new_target_info.ino], inodes)
2110
+ end
2111
+ end
2112
+ end
2113
+
2114
+ sub_test_case "tail_path" do
2115
+ def test_tail_path_with_singleline
2116
+ File.open("#{TMP_DIR}/tail.txt", "wb") {|f|
2117
+ f.puts "test1"
2118
+ f.puts "test2"
2119
+ }
2120
+
2121
+ d = create_driver(SINGLE_LINE_CONFIG + config_element("", "", { "path_key" => "path" }))
2122
+
2123
+ d.run(expect_emits: 1) do
2124
+ File.open("#{TMP_DIR}/tail.txt", "ab") {|f|
2125
+ f.puts "test3"
2126
+ f.puts "test4"
2127
+ }
2128
+ end
2129
+
2130
+ events = d.events
2131
+ assert_equal(true, events.length > 0)
2132
+ events.each do |emit|
2133
+ assert_equal("#{TMP_DIR}/tail.txt", emit[2]["path"])
2134
+ end
2135
+ end
2136
+
2137
+ def test_tail_path_with_multiline_with_firstline
2138
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
2139
+
2140
+ config = config_element("", "", {
2141
+ "path_key" => "path",
2142
+ "format" => "multiline",
2143
+ "format1" => "/^s (?<message1>[^\\n]+)(\\nf (?<message2>[^\\n]+))?(\\nf (?<message3>.*))?/",
2144
+ "format_firstline" => "/^[s]/"
2145
+ })
2146
+ d = create_driver(config)
2147
+ d.run(expect_emits: 1) do
2148
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
2149
+ f.puts "f test1"
2150
+ f.puts "s test2"
2151
+ f.puts "f test3"
2152
+ f.puts "f test4"
2153
+ f.puts "s test5"
2154
+ f.puts "s test6"
2155
+ f.puts "f test7"
2156
+ f.puts "s test8"
2157
+ }
2158
+ end
2159
+
2160
+ events = d.events
2161
+ assert_equal(4, events.length)
2162
+ events.each do |emit|
2163
+ assert_equal("#{TMP_DIR}/tail.txt", emit[2]["path"])
2164
+ end
2165
+ end
2166
+
2167
+ def test_tail_path_with_multiline_without_firstline
2168
+ File.open("#{TMP_DIR}/tail.txt", "wb") { |f| }
2169
+
2170
+ config = config_element("", "", {
2171
+ "path_key" => "path",
2172
+ "format" => "multiline",
2173
+ "format1" => "/(?<var1>foo \\d)\\n/",
2174
+ "format2" => "/(?<var2>bar \\d)\\n/",
2175
+ "format3" => "/(?<var3>baz \\d)/",
2176
+ })
2177
+ d = create_driver(config)
2178
+ d.run(expect_emits: 1) do
2179
+ File.open("#{TMP_DIR}/tail.txt", "ab") { |f|
2180
+ f.puts "foo 1"
2181
+ f.puts "bar 1"
2182
+ f.puts "baz 1"
2183
+ }
2184
+ end
2185
+
2186
+ events = d.events
2187
+ assert(events.length > 0)
2188
+ events.each do |emit|
2189
+ assert_equal("#{TMP_DIR}/tail.txt", emit[2]["path"])
2190
+ end
2191
+ end
2192
+
2193
+ def test_tail_path_with_multiline_with_multiple_paths
2194
+ if ENV["APPVEYOR"] && Fluent.windows?
2195
+ omit "This testcase is unstable on AppVeyor."
2196
+ end
2197
+ files = ["#{TMP_DIR}/tail1.txt", "#{TMP_DIR}/tail2.txt"]
2198
+ files.each { |file| File.open(file, "wb") { |f| } }
2199
+
2200
+ config = config_element("", "", {
2201
+ "path" => "#{files[0]},#{files[1]}",
2202
+ "path_key" => "path",
2203
+ "tag" => "t1",
2204
+ "format" => "multiline",
2205
+ "format1" => "/^[s|f] (?<message>.*)/",
2206
+ "format_firstline" => "/^[s]/"
2207
+ })
2208
+ d = create_driver(config, false)
2209
+ d.run(expect_emits: 2) do
2210
+ files.each do |file|
2211
+ File.open(file, 'ab') { |f|
2212
+ f.puts "f #{file} line should be ignored"
2213
+ f.puts "s test1"
2214
+ f.puts "f test2"
2215
+ f.puts "f test3"
2216
+ f.puts "s test4"
2217
+ }
2218
+ end
2219
+ end
2220
+
2221
+ events = d.events
2222
+ assert_equal(4, events.length)
2223
+ assert_equal(files, [events[0][2]["path"], events[1][2]["path"]].sort)
2224
+ # "test4" events are here because these events are flushed at shutdown phase
2225
+ assert_equal(files, [events[2][2]["path"], events[3][2]["path"]].sort)
2226
+ end
2227
+ end
2228
+
2229
+ def test_limit_recently_modified
2230
+ now = Time.new(2010, 1, 2, 3, 4, 5)
2231
+ FileUtils.touch("#{TMP_DIR}/tail_unwatch.txt", mtime: (now - 3601))
2232
+ FileUtils.touch("#{TMP_DIR}/tail_watch1.txt", mtime: (now - 3600))
2233
+ FileUtils.touch("#{TMP_DIR}/tail_watch2.txt", mtime: now)
2234
+
2235
+ config = config_element('', '', {
2236
+ 'tag' => 'tail',
2237
+ 'path' => "#{TMP_DIR}/*.txt",
2238
+ 'format' => 'none',
2239
+ 'limit_recently_modified' => '3600s'
2240
+ })
2241
+
2242
+ expected_files = [
2243
+ create_target_info("#{TMP_DIR}/tail_watch1.txt"),
2244
+ create_target_info("#{TMP_DIR}/tail_watch2.txt")
2245
+ ]
2246
+
2247
+ Timecop.freeze(now) do
2248
+ plugin = create_driver(config, false).instance
2249
+ assert_equal expected_files, plugin.expand_paths.values.sort_by { |path_ino| path_ino.path }
2250
+ end
2251
+ end
2252
+
2253
+ def test_skip_refresh_on_startup
2254
+ FileUtils.touch("#{TMP_DIR}/tail.txt")
2255
+ config = config_element('', '', {
2256
+ 'format' => 'none',
2257
+ 'refresh_interval' => 1,
2258
+ 'skip_refresh_on_startup' => true
2259
+ })
2260
+ d = create_driver(config)
2261
+ d.run(shutdown: false) {}
2262
+ assert_equal 0, d.instance.instance_variable_get(:@tails).keys.size
2263
+ # detect a file at first execution of in_tail_refresh_watchers timer
2264
+ waiting(5) { sleep 0.1 until d.instance.instance_variable_get(:@tails).keys.size == 1 }
2265
+ d.instance_shutdown
2266
+ end
2267
+
2268
+ def test_ENOENT_error_after_setup_watcher
2269
+ path = "#{TMP_DIR}/tail.txt"
2270
+ FileUtils.touch(path)
2271
+ config = config_element('', '', {
2272
+ 'format' => 'none',
2273
+ })
2274
+ d = create_driver(config)
2275
+ mock.proxy(d.instance).setup_watcher(anything, anything) do |tw|
2276
+ cleanup_file(path)
2277
+ tw
2278
+ end
2279
+ assert_nothing_raised do
2280
+ d.run(shutdown: false) {}
2281
+ end
2282
+ assert($log.out.logs.any?{|log| log.include?("stat() for #{path} failed with Errno::ENOENT. Drop tail watcher for now.\n") })
2283
+ ensure
2284
+ d.instance_shutdown if d && d.instance
2285
+ end
2286
+
2287
+ def test_EACCES_error_after_setup_watcher
2288
+ omit "Cannot test with root user" if Process::UID.eid == 0
2289
+ path = "#{TMP_DIR}/noaccess/tail.txt"
2290
+ begin
2291
+ FileUtils.mkdir_p("#{TMP_DIR}/noaccess")
2292
+ FileUtils.chmod(0755, "#{TMP_DIR}/noaccess")
2293
+ FileUtils.touch(path)
2294
+ config = config_element('', '', {
2295
+ 'tag' => "tail",
2296
+ 'path' => path,
2297
+ 'format' => 'none',
2298
+ })
2299
+ d = create_driver(config, false)
2300
+ mock.proxy(d.instance).setup_watcher(anything, anything) do |tw|
2301
+ FileUtils.chmod(0000, "#{TMP_DIR}/noaccess")
2302
+ tw
2303
+ end
2304
+ assert_nothing_raised do
2305
+ d.run(shutdown: false) {}
2306
+ end
2307
+ assert($log.out.logs.any?{|log| log.include?("stat() for #{path} failed with Errno::EACCES. Drop tail watcher for now.\n") })
2308
+ end
2309
+ ensure
2310
+ d.instance_shutdown if d && d.instance
2311
+ if File.exist?("#{TMP_DIR}/noaccess")
2312
+ FileUtils.chmod(0755, "#{TMP_DIR}/noaccess")
2313
+ FileUtils.rm_rf("#{TMP_DIR}/noaccess")
2314
+ end
2315
+ end unless Fluent.windows?
2316
+
2317
+ def test_EACCES
2318
+ path = "#{TMP_DIR}/tail.txt"
2319
+ FileUtils.touch(path)
2320
+ config = config_element('', '', {
2321
+ 'format' => 'none',
2322
+ })
2323
+ d = create_driver(config)
2324
+ mock.proxy(Fluent::FileWrapper).stat(path) do |stat|
2325
+ raise Errno::EACCES
2326
+ end.at_least(1)
2327
+ assert_nothing_raised do
2328
+ d.run(shutdown: false) {}
2329
+ end
2330
+ assert($log.out.logs.any?{|log| log.include?("expand_paths: stat() for #{path} failed with Errno::EACCES. Skip file.\n") })
2331
+ ensure
2332
+ d.instance_shutdown if d && d.instance
2333
+ end
2334
+
2335
+ def test_shutdown_timeout
2336
+ path = "#{TMP_DIR}/tail.txt"
2337
+ File.open("#{TMP_DIR}/tail.txt", "wb") do |f|
2338
+ (1024 * 1024 * 5).times do
2339
+ f.puts "{\"test\":\"fizzbuzz\"}"
2340
+ end
2341
+ end
2342
+
2343
+ config =
2344
+ CONFIG_READ_FROM_HEAD +
2345
+ config_element('', '', {
2346
+ 'format' => 'json',
2347
+ 'skip_refresh_on_startup' => true,
2348
+ })
2349
+ d = create_driver(config)
2350
+ mock.proxy(d.instance).io_handler(anything, anything) do |io_handler|
2351
+ io_handler.shutdown_timeout = 0.5
2352
+ io_handler
2353
+ end
2354
+
2355
+ start_time = Fluent::Clock.now
2356
+ assert_nothing_raised do
2357
+ d.run(expect_emits: 1)
2358
+ end
2359
+
2360
+ elapsed = Fluent::Clock.now - start_time
2361
+ assert_true(elapsed > 0.5 && elapsed < 2.5)
2362
+ end
2363
+ end