dtomasgu-fluentd 1.14.7.pre.dev

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