dtomasgu-fluentd 1.14.7.pre.dev

Sign up to get free protection for your applications and to get access to all the features.
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