fluentd 1.20.0-aarch64-mingw-ucrt

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +116 -0
  3. data/ADOPTERS.md +5 -0
  4. data/AGENTS.md +46 -0
  5. data/AUTHORS +2 -0
  6. data/CHANGELOG.md +3324 -0
  7. data/CONTRIBUTING.md +45 -0
  8. data/GOVERNANCE.md +55 -0
  9. data/GithubWorkflow.md +78 -0
  10. data/LICENSE +202 -0
  11. data/MAINTAINERS.md +19 -0
  12. data/README.md +73 -0
  13. data/Rakefile +89 -0
  14. data/SECURITY.md +47 -0
  15. data/bin/fluent-binlog-reader +7 -0
  16. data/bin/fluent-ca-generate +6 -0
  17. data/bin/fluent-cap-ctl +7 -0
  18. data/bin/fluent-cat +5 -0
  19. data/bin/fluent-ctl +7 -0
  20. data/bin/fluent-debug +5 -0
  21. data/bin/fluent-gem +9 -0
  22. data/bin/fluent-plugin-config-format +5 -0
  23. data/bin/fluent-plugin-generate +5 -0
  24. data/bin/fluentd +15 -0
  25. data/code-of-conduct.md +3 -0
  26. data/docs/SECURITY_AUDIT.pdf +0 -0
  27. data/example/copy_roundrobin.conf +39 -0
  28. data/example/counter.conf +18 -0
  29. data/example/filter_stdout.conf +22 -0
  30. data/example/in_forward.conf +14 -0
  31. data/example/in_forward_client.conf +37 -0
  32. data/example/in_forward_shared_key.conf +15 -0
  33. data/example/in_forward_tls.conf +14 -0
  34. data/example/in_forward_users.conf +24 -0
  35. data/example/in_forward_workers.conf +21 -0
  36. data/example/in_http.conf +16 -0
  37. data/example/in_out_forward.conf +17 -0
  38. data/example/in_sample_blocks.conf +17 -0
  39. data/example/in_sample_with_compression.conf +23 -0
  40. data/example/in_syslog.conf +15 -0
  41. data/example/in_tail.conf +14 -0
  42. data/example/in_tcp.conf +13 -0
  43. data/example/in_udp.conf +13 -0
  44. data/example/logevents.conf +25 -0
  45. data/example/multi_filters.conf +61 -0
  46. data/example/out_copy.conf +20 -0
  47. data/example/out_exec_filter.conf +42 -0
  48. data/example/out_file.conf +13 -0
  49. data/example/out_forward.conf +35 -0
  50. data/example/out_forward_buf_file.conf +23 -0
  51. data/example/out_forward_client.conf +109 -0
  52. data/example/out_forward_heartbeat_none.conf +16 -0
  53. data/example/out_forward_sd.conf +17 -0
  54. data/example/out_forward_shared_key.conf +36 -0
  55. data/example/out_forward_tls.conf +18 -0
  56. data/example/out_forward_users.conf +65 -0
  57. data/example/out_null.conf +36 -0
  58. data/example/sd.yaml +8 -0
  59. data/example/secondary_file.conf +42 -0
  60. data/example/suppress_config_dump.conf +7 -0
  61. data/example/v0_12_filter.conf +78 -0
  62. data/example/v1_literal_example.conf +36 -0
  63. data/example/worker_section.conf +36 -0
  64. data/fluent.conf +139 -0
  65. data/lib/fluent/agent.rb +168 -0
  66. data/lib/fluent/capability.rb +87 -0
  67. data/lib/fluent/clock.rb +66 -0
  68. data/lib/fluent/command/binlog_reader.rb +244 -0
  69. data/lib/fluent/command/bundler_injection.rb +45 -0
  70. data/lib/fluent/command/ca_generate.rb +184 -0
  71. data/lib/fluent/command/cap_ctl.rb +174 -0
  72. data/lib/fluent/command/cat.rb +365 -0
  73. data/lib/fluent/command/ctl.rb +180 -0
  74. data/lib/fluent/command/debug.rb +103 -0
  75. data/lib/fluent/command/fluentd.rb +385 -0
  76. data/lib/fluent/command/plugin_config_formatter.rb +308 -0
  77. data/lib/fluent/command/plugin_generator.rb +365 -0
  78. data/lib/fluent/compat/call_super_mixin.rb +76 -0
  79. data/lib/fluent/compat/detach_process_mixin.rb +33 -0
  80. data/lib/fluent/compat/exec_util.rb +144 -0
  81. data/lib/fluent/compat/file_util.rb +54 -0
  82. data/lib/fluent/compat/filter.rb +68 -0
  83. data/lib/fluent/compat/formatter.rb +117 -0
  84. data/lib/fluent/compat/formatter_utils.rb +85 -0
  85. data/lib/fluent/compat/handle_tag_and_time_mixin.rb +62 -0
  86. data/lib/fluent/compat/handle_tag_name_mixin.rb +53 -0
  87. data/lib/fluent/compat/input.rb +49 -0
  88. data/lib/fluent/compat/output.rb +721 -0
  89. data/lib/fluent/compat/output_chain.rb +60 -0
  90. data/lib/fluent/compat/parser.rb +310 -0
  91. data/lib/fluent/compat/parser_utils.rb +40 -0
  92. data/lib/fluent/compat/propagate_default.rb +62 -0
  93. data/lib/fluent/compat/record_filter_mixin.rb +34 -0
  94. data/lib/fluent/compat/set_tag_key_mixin.rb +50 -0
  95. data/lib/fluent/compat/set_time_key_mixin.rb +69 -0
  96. data/lib/fluent/compat/socket_util.rb +165 -0
  97. data/lib/fluent/compat/string_util.rb +34 -0
  98. data/lib/fluent/compat/structured_format_mixin.rb +26 -0
  99. data/lib/fluent/compat/type_converter.rb +90 -0
  100. data/lib/fluent/config/basic_parser.rb +122 -0
  101. data/lib/fluent/config/configure_proxy.rb +424 -0
  102. data/lib/fluent/config/dsl.rb +152 -0
  103. data/lib/fluent/config/element.rb +265 -0
  104. data/lib/fluent/config/error.rb +44 -0
  105. data/lib/fluent/config/literal_parser.rb +305 -0
  106. data/lib/fluent/config/parser.rb +119 -0
  107. data/lib/fluent/config/section.rb +272 -0
  108. data/lib/fluent/config/types.rb +257 -0
  109. data/lib/fluent/config/v1_parser.rb +200 -0
  110. data/lib/fluent/config/yaml_parser/fluent_value.rb +47 -0
  111. data/lib/fluent/config/yaml_parser/loader.rb +124 -0
  112. data/lib/fluent/config/yaml_parser/parser.rb +179 -0
  113. data/lib/fluent/config/yaml_parser/section_builder.rb +107 -0
  114. data/lib/fluent/config/yaml_parser.rb +56 -0
  115. data/lib/fluent/config.rb +89 -0
  116. data/lib/fluent/configurable.rb +202 -0
  117. data/lib/fluent/counter/base_socket.rb +44 -0
  118. data/lib/fluent/counter/client.rb +297 -0
  119. data/lib/fluent/counter/error.rb +86 -0
  120. data/lib/fluent/counter/mutex_hash.rb +163 -0
  121. data/lib/fluent/counter/server.rb +273 -0
  122. data/lib/fluent/counter/store.rb +205 -0
  123. data/lib/fluent/counter/validator.rb +145 -0
  124. data/lib/fluent/counter.rb +23 -0
  125. data/lib/fluent/daemon.rb +14 -0
  126. data/lib/fluent/daemonizer.rb +88 -0
  127. data/lib/fluent/engine.rb +269 -0
  128. data/lib/fluent/env.rb +45 -0
  129. data/lib/fluent/error.rb +37 -0
  130. data/lib/fluent/event.rb +369 -0
  131. data/lib/fluent/event_router.rb +315 -0
  132. data/lib/fluent/ext_monitor_require.rb +28 -0
  133. data/lib/fluent/file_wrapper.rb +137 -0
  134. data/lib/fluent/filter.rb +21 -0
  135. data/lib/fluent/fluent_log_event_router.rb +139 -0
  136. data/lib/fluent/formatter.rb +23 -0
  137. data/lib/fluent/input.rb +21 -0
  138. data/lib/fluent/label.rb +46 -0
  139. data/lib/fluent/load.rb +33 -0
  140. data/lib/fluent/log/console_adapter.rb +66 -0
  141. data/lib/fluent/log.rb +775 -0
  142. data/lib/fluent/match.rb +187 -0
  143. data/lib/fluent/mixin.rb +31 -0
  144. data/lib/fluent/msgpack_factory.rb +111 -0
  145. data/lib/fluent/oj_options.rb +61 -0
  146. data/lib/fluent/output.rb +29 -0
  147. data/lib/fluent/output_chain.rb +23 -0
  148. data/lib/fluent/parser.rb +23 -0
  149. data/lib/fluent/plugin/bare_output.rb +88 -0
  150. data/lib/fluent/plugin/base.rb +219 -0
  151. data/lib/fluent/plugin/buf_file.rb +256 -0
  152. data/lib/fluent/plugin/buf_file_single.rb +272 -0
  153. data/lib/fluent/plugin/buf_memory.rb +34 -0
  154. data/lib/fluent/plugin/buffer/chunk.rb +313 -0
  155. data/lib/fluent/plugin/buffer/file_chunk.rb +432 -0
  156. data/lib/fluent/plugin/buffer/file_single_chunk.rb +325 -0
  157. data/lib/fluent/plugin/buffer/memory_chunk.rb +91 -0
  158. data/lib/fluent/plugin/buffer.rb +1063 -0
  159. data/lib/fluent/plugin/compressable.rb +87 -0
  160. data/lib/fluent/plugin/exec_util.rb +22 -0
  161. data/lib/fluent/plugin/extractor.rb +132 -0
  162. data/lib/fluent/plugin/file_util.rb +22 -0
  163. data/lib/fluent/plugin/filter.rb +119 -0
  164. data/lib/fluent/plugin/filter_grep.rb +189 -0
  165. data/lib/fluent/plugin/filter_parser.rb +124 -0
  166. data/lib/fluent/plugin/filter_record_transformer.rb +325 -0
  167. data/lib/fluent/plugin/filter_stdout.rb +53 -0
  168. data/lib/fluent/plugin/formatter.rb +75 -0
  169. data/lib/fluent/plugin/formatter_csv.rb +92 -0
  170. data/lib/fluent/plugin/formatter_hash.rb +35 -0
  171. data/lib/fluent/plugin/formatter_json.rb +62 -0
  172. data/lib/fluent/plugin/formatter_ltsv.rb +44 -0
  173. data/lib/fluent/plugin/formatter_msgpack.rb +33 -0
  174. data/lib/fluent/plugin/formatter_out_file.rb +55 -0
  175. data/lib/fluent/plugin/formatter_single_value.rb +36 -0
  176. data/lib/fluent/plugin/formatter_stdout.rb +76 -0
  177. data/lib/fluent/plugin/formatter_tsv.rb +40 -0
  178. data/lib/fluent/plugin/in_debug_agent.rb +71 -0
  179. data/lib/fluent/plugin/in_dummy.rb +18 -0
  180. data/lib/fluent/plugin/in_exec.rb +124 -0
  181. data/lib/fluent/plugin/in_forward.rb +489 -0
  182. data/lib/fluent/plugin/in_gc_stat.rb +72 -0
  183. data/lib/fluent/plugin/in_http.rb +737 -0
  184. data/lib/fluent/plugin/in_monitor_agent.rb +406 -0
  185. data/lib/fluent/plugin/in_object_space.rb +93 -0
  186. data/lib/fluent/plugin/in_sample.rb +148 -0
  187. data/lib/fluent/plugin/in_syslog.rb +346 -0
  188. data/lib/fluent/plugin/in_tail/compatibility.rb +111 -0
  189. data/lib/fluent/plugin/in_tail/dir_watcher.rb +713 -0
  190. data/lib/fluent/plugin/in_tail/group_watch.rb +204 -0
  191. data/lib/fluent/plugin/in_tail/io_handler.rb +391 -0
  192. data/lib/fluent/plugin/in_tail/line_feeder.rb +230 -0
  193. data/lib/fluent/plugin/in_tail/position_file.rb +269 -0
  194. data/lib/fluent/plugin/in_tail/stat_watcher.rb +52 -0
  195. data/lib/fluent/plugin/in_tail/tail_watcher.rb +276 -0
  196. data/lib/fluent/plugin/in_tail/worker_pool.rb +298 -0
  197. data/lib/fluent/plugin/in_tail.rb +917 -0
  198. data/lib/fluent/plugin/in_tcp.rb +230 -0
  199. data/lib/fluent/plugin/in_udp.rb +101 -0
  200. data/lib/fluent/plugin/in_unix.rb +197 -0
  201. data/lib/fluent/plugin/input.rb +71 -0
  202. data/lib/fluent/plugin/metrics.rb +119 -0
  203. data/lib/fluent/plugin/metrics_local.rb +96 -0
  204. data/lib/fluent/plugin/multi_output.rb +179 -0
  205. data/lib/fluent/plugin/out_buffer.rb +40 -0
  206. data/lib/fluent/plugin/out_copy.rb +120 -0
  207. data/lib/fluent/plugin/out_exec.rb +105 -0
  208. data/lib/fluent/plugin/out_exec_filter.rb +319 -0
  209. data/lib/fluent/plugin/out_file.rb +371 -0
  210. data/lib/fluent/plugin/out_forward/ack_handler.rb +176 -0
  211. data/lib/fluent/plugin/out_forward/connection_manager.rb +131 -0
  212. data/lib/fluent/plugin/out_forward/error.rb +28 -0
  213. data/lib/fluent/plugin/out_forward/failure_detector.rb +84 -0
  214. data/lib/fluent/plugin/out_forward/handshake_protocol.rb +125 -0
  215. data/lib/fluent/plugin/out_forward/load_balancer.rb +114 -0
  216. data/lib/fluent/plugin/out_forward/socket_cache.rb +177 -0
  217. data/lib/fluent/plugin/out_forward.rb +865 -0
  218. data/lib/fluent/plugin/out_http.rb +419 -0
  219. data/lib/fluent/plugin/out_null.rb +74 -0
  220. data/lib/fluent/plugin/out_relabel.rb +32 -0
  221. data/lib/fluent/plugin/out_roundrobin.rb +84 -0
  222. data/lib/fluent/plugin/out_secondary_file.rb +148 -0
  223. data/lib/fluent/plugin/out_stdout.rb +81 -0
  224. data/lib/fluent/plugin/out_stream.rb +130 -0
  225. data/lib/fluent/plugin/output.rb +1688 -0
  226. data/lib/fluent/plugin/owned_by_mixin.rb +41 -0
  227. data/lib/fluent/plugin/parser.rb +274 -0
  228. data/lib/fluent/plugin/parser_apache.rb +28 -0
  229. data/lib/fluent/plugin/parser_apache2.rb +88 -0
  230. data/lib/fluent/plugin/parser_apache_error.rb +26 -0
  231. data/lib/fluent/plugin/parser_csv.rb +124 -0
  232. data/lib/fluent/plugin/parser_json.rb +118 -0
  233. data/lib/fluent/plugin/parser_ltsv.rb +49 -0
  234. data/lib/fluent/plugin/parser_msgpack.rb +72 -0
  235. data/lib/fluent/plugin/parser_multiline.rb +152 -0
  236. data/lib/fluent/plugin/parser_nginx.rb +28 -0
  237. data/lib/fluent/plugin/parser_none.rb +36 -0
  238. data/lib/fluent/plugin/parser_regexp.rb +68 -0
  239. data/lib/fluent/plugin/parser_syslog.rb +543 -0
  240. data/lib/fluent/plugin/parser_tsv.rb +47 -0
  241. data/lib/fluent/plugin/sd_file.rb +157 -0
  242. data/lib/fluent/plugin/sd_srv.rb +135 -0
  243. data/lib/fluent/plugin/sd_static.rb +58 -0
  244. data/lib/fluent/plugin/service_discovery.rb +65 -0
  245. data/lib/fluent/plugin/socket_util.rb +22 -0
  246. data/lib/fluent/plugin/storage.rb +84 -0
  247. data/lib/fluent/plugin/storage_local.rb +166 -0
  248. data/lib/fluent/plugin/string_util.rb +22 -0
  249. data/lib/fluent/plugin.rb +206 -0
  250. data/lib/fluent/plugin_helper/cert_option.rb +199 -0
  251. data/lib/fluent/plugin_helper/child_process.rb +369 -0
  252. data/lib/fluent/plugin_helper/compat_parameters.rb +343 -0
  253. data/lib/fluent/plugin_helper/counter.rb +51 -0
  254. data/lib/fluent/plugin_helper/event_emitter.rb +112 -0
  255. data/lib/fluent/plugin_helper/event_loop.rb +170 -0
  256. data/lib/fluent/plugin_helper/extract.rb +104 -0
  257. data/lib/fluent/plugin_helper/formatter.rb +147 -0
  258. data/lib/fluent/plugin_helper/http_server/app.rb +81 -0
  259. data/lib/fluent/plugin_helper/http_server/methods.rb +35 -0
  260. data/lib/fluent/plugin_helper/http_server/request.rb +53 -0
  261. data/lib/fluent/plugin_helper/http_server/router.rb +54 -0
  262. data/lib/fluent/plugin_helper/http_server/server.rb +116 -0
  263. data/lib/fluent/plugin_helper/http_server/ssl_context_builder.rb +41 -0
  264. data/lib/fluent/plugin_helper/http_server.rb +129 -0
  265. data/lib/fluent/plugin_helper/inject.rb +154 -0
  266. data/lib/fluent/plugin_helper/metrics.rb +136 -0
  267. data/lib/fluent/plugin_helper/parser.rb +147 -0
  268. data/lib/fluent/plugin_helper/record_accessor.rb +207 -0
  269. data/lib/fluent/plugin_helper/retry_state.rb +219 -0
  270. data/lib/fluent/plugin_helper/server.rb +840 -0
  271. data/lib/fluent/plugin_helper/service_discovery/manager.rb +146 -0
  272. data/lib/fluent/plugin_helper/service_discovery/round_robin_balancer.rb +43 -0
  273. data/lib/fluent/plugin_helper/service_discovery.rb +125 -0
  274. data/lib/fluent/plugin_helper/socket.rb +288 -0
  275. data/lib/fluent/plugin_helper/socket_option.rb +98 -0
  276. data/lib/fluent/plugin_helper/storage.rb +349 -0
  277. data/lib/fluent/plugin_helper/thread.rb +180 -0
  278. data/lib/fluent/plugin_helper/timer.rb +92 -0
  279. data/lib/fluent/plugin_helper.rb +75 -0
  280. data/lib/fluent/plugin_id.rb +93 -0
  281. data/lib/fluent/process.rb +22 -0
  282. data/lib/fluent/registry.rb +117 -0
  283. data/lib/fluent/root_agent.rb +468 -0
  284. data/lib/fluent/rpc.rb +95 -0
  285. data/lib/fluent/source_only_buffer_agent.rb +102 -0
  286. data/lib/fluent/static_config_analysis.rb +195 -0
  287. data/lib/fluent/supervisor.rb +1347 -0
  288. data/lib/fluent/system_config.rb +213 -0
  289. data/lib/fluent/test/base.rb +83 -0
  290. data/lib/fluent/test/driver/base.rb +231 -0
  291. data/lib/fluent/test/driver/base_owned.rb +83 -0
  292. data/lib/fluent/test/driver/base_owner.rb +135 -0
  293. data/lib/fluent/test/driver/event_feeder.rb +98 -0
  294. data/lib/fluent/test/driver/filter.rb +61 -0
  295. data/lib/fluent/test/driver/formatter.rb +30 -0
  296. data/lib/fluent/test/driver/input.rb +31 -0
  297. data/lib/fluent/test/driver/multi_output.rb +53 -0
  298. data/lib/fluent/test/driver/output.rb +102 -0
  299. data/lib/fluent/test/driver/parser.rb +30 -0
  300. data/lib/fluent/test/driver/storage.rb +30 -0
  301. data/lib/fluent/test/driver/test_event_router.rb +45 -0
  302. data/lib/fluent/test/filter_test.rb +77 -0
  303. data/lib/fluent/test/formatter_test.rb +65 -0
  304. data/lib/fluent/test/helpers.rb +138 -0
  305. data/lib/fluent/test/input_test.rb +174 -0
  306. data/lib/fluent/test/log.rb +79 -0
  307. data/lib/fluent/test/output_test.rb +156 -0
  308. data/lib/fluent/test/parser_test.rb +70 -0
  309. data/lib/fluent/test/startup_shutdown.rb +44 -0
  310. data/lib/fluent/test.rb +61 -0
  311. data/lib/fluent/time.rb +513 -0
  312. data/lib/fluent/timezone.rb +171 -0
  313. data/lib/fluent/tls.rb +105 -0
  314. data/lib/fluent/unique_id.rb +39 -0
  315. data/lib/fluent/variable_store.rb +40 -0
  316. data/lib/fluent/version.rb +21 -0
  317. data/lib/fluent/win32api.rb +38 -0
  318. data/lib/fluent/winsvc.rb +130 -0
  319. data/templates/new_gem/.gitignore +8 -0
  320. data/templates/new_gem/Gemfile +3 -0
  321. data/templates/new_gem/README.md.erb +43 -0
  322. data/templates/new_gem/Rakefile +13 -0
  323. data/templates/new_gem/fluent-plugin.gemspec.erb +28 -0
  324. data/templates/new_gem/lib/fluent/plugin/filter.rb.erb +14 -0
  325. data/templates/new_gem/lib/fluent/plugin/formatter.rb.erb +14 -0
  326. data/templates/new_gem/lib/fluent/plugin/input.rb.erb +11 -0
  327. data/templates/new_gem/lib/fluent/plugin/output.rb.erb +11 -0
  328. data/templates/new_gem/lib/fluent/plugin/parser.rb.erb +15 -0
  329. data/templates/new_gem/lib/fluent/plugin/storage.rb.erb +40 -0
  330. data/templates/new_gem/test/helper.rb.erb +7 -0
  331. data/templates/new_gem/test/plugin/test_filter.rb.erb +18 -0
  332. data/templates/new_gem/test/plugin/test_formatter.rb.erb +18 -0
  333. data/templates/new_gem/test/plugin/test_input.rb.erb +18 -0
  334. data/templates/new_gem/test/plugin/test_output.rb.erb +18 -0
  335. data/templates/new_gem/test/plugin/test_parser.rb.erb +18 -0
  336. data/templates/new_gem/test/plugin/test_storage.rb.erb +18 -0
  337. data/templates/plugin_config_formatter/param.md-compact.erb +25 -0
  338. data/templates/plugin_config_formatter/param.md-table.erb +10 -0
  339. data/templates/plugin_config_formatter/param.md.erb +34 -0
  340. data/templates/plugin_config_formatter/section.md.erb +12 -0
  341. metadata +996 -0
@@ -0,0 +1,917 @@
1
+ #
2
+ # Fluentd
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'cool.io'
18
+
19
+ require 'fluent/plugin/input'
20
+ require 'fluent/config/error'
21
+ require 'fluent/event'
22
+ require 'fluent/plugin/buffer'
23
+ require 'fluent/ext_monitor_require'
24
+ require 'fluent/plugin/parser_multiline'
25
+ require 'fluent/variable_store'
26
+ require 'fluent/capability'
27
+ require 'fluent/plugin/in_tail/position_file'
28
+ require 'fluent/plugin/in_tail/group_watch'
29
+ require 'fluent/plugin/in_tail/stat_watcher'
30
+ require 'fluent/plugin/in_tail/dir_watcher'
31
+ require 'fluent/plugin/in_tail/io_handler'
32
+ require 'fluent/plugin/in_tail/tail_watcher'
33
+ require 'fluent/plugin/in_tail/line_feeder'
34
+ require 'fluent/plugin/in_tail/compatibility'
35
+ require 'fluent/plugin/in_tail/worker_pool'
36
+ require 'fluent/file_wrapper'
37
+
38
+ module Fluent::Plugin
39
+ class TailInput < Fluent::Plugin::Input
40
+ include GroupWatch
41
+ include Compatibility
42
+ include DirWatcher
43
+
44
+ Fluent::Plugin.register_input('tail', self)
45
+
46
+ helpers :timer, :event_loop, :parser, :compat_parameters
47
+
48
+ RESERVED_CHARS = ['/', '*', '%'].freeze
49
+ MetricsInfo = Struct.new(:opened, :closed, :rotated, :throttled, :tracked)
50
+
51
+ class WatcherSetupError < StandardError
52
+ def initialize(msg)
53
+ @message = msg
54
+ end
55
+
56
+ def to_s
57
+ @message
58
+ end
59
+ end
60
+
61
+ def initialize
62
+ super
63
+ @paths = []
64
+ @tails = {}
65
+ @tails_rotate_wait = {}
66
+ @pf_file = nil
67
+ @watcher_refresh_monitor = Monitor.new
68
+ @pf = nil
69
+ @ignore_list = []
70
+ @shutdown_start_time = nil
71
+ @metrics = nil
72
+ @startup = true
73
+ @capability = Fluent::Capability.new(:current_process)
74
+ initialize_dir_watchers
75
+ end
76
+
77
+ desc 'The paths to read. Multiple paths can be specified, separated by comma.'
78
+ config_param :path, :string
79
+ desc 'path delimiter used for splitting path config'
80
+ config_param :path_delimiter, :string, default: ','
81
+ desc 'Choose using glob patterns. Adding capabilities to handle [] and ?, and {}.'
82
+ config_param :glob_policy, :enum, list: [:backward_compatible, :extended, :always], default: :backward_compatible
83
+ desc 'The tag of the event.'
84
+ config_param :tag, :string
85
+ desc 'The paths to exclude the files from watcher list.'
86
+ config_param :exclude_path, :array, default: []
87
+ desc 'Specify interval to keep reference to old file when rotate a file.'
88
+ config_param :rotate_wait, :time, default: 5
89
+ desc 'Fluentd will record the position it last read into this file.'
90
+ config_param :pos_file, :string, default: nil
91
+ desc 'The cleanup interval of pos file'
92
+ config_param :pos_file_compaction_interval, :time, default: nil
93
+ desc 'Start to read the logs from the head of file, not bottom.'
94
+ config_param :read_from_head, :bool, default: false
95
+ # When the program deletes log file and re-creates log file with same filename after passed refresh_interval,
96
+ # in_tail may raise a pos_file related error. This is a known issue but there is no such program on production.
97
+ # If we find such program / application, we will fix the problem.
98
+ desc 'The interval of refreshing the list of watch file.'
99
+ config_param :refresh_interval, :time, default: 60
100
+ desc 'The number of reading lines at each IO.'
101
+ config_param :read_lines_limit, :integer, default: 1000
102
+ desc 'The number of threads used to parse tailed files. Set to 1 to keep parsing synchronous; 2 or more enables worker parsing. The default is 1.'
103
+ config_param :num_threads, :integer, default: 1
104
+ desc 'The number of reading bytes per second'
105
+ config_param :read_bytes_limit_per_second, :size, default: -1
106
+ desc 'The interval of flushing the buffer for multiline format'
107
+ config_param :multiline_flush_interval, :time, default: nil
108
+ desc 'Enable the option to emit unmatched lines.'
109
+ config_param :emit_unmatched_lines, :bool, default: false
110
+ desc 'Enable the additional watch timer.'
111
+ config_param :enable_watch_timer, :bool, default: true
112
+ desc 'Enable stat watchers for individual files, using inotify where available.'
113
+ config_param :enable_stat_watcher, :bool, default: true
114
+ desc 'Watch directories to discover files before refresh_interval. May use inotify regardless of enable_stat_watcher.'
115
+ config_param :enable_dir_watcher, :bool, default: false
116
+ desc 'Maximum number of directories watched at once. When exceeded, rotate the watched subset at each periodic refresh.'
117
+ config_param :dir_watcher_limit, :integer, default: 100
118
+ desc 'The encoding of the input.'
119
+ config_param :encoding, :string, default: nil
120
+ desc "The original encoding of the input. If set, in_tail tries to encode string from this to 'encoding'. Must be set with 'encoding'. "
121
+ config_param :from_encoding, :string, default: nil
122
+ desc 'Add the log path being tailed to records. Specify the field name to be used.'
123
+ config_param :path_key, :string, default: nil
124
+ desc 'Open and close the file on every update instead of leaving it open until it gets rotated.'
125
+ config_param :open_on_every_update, :bool, default: false
126
+ desc 'Limit the watching files that the modification time is within the specified time range (when use \'*\' in path).'
127
+ config_param :limit_recently_modified, :time, default: nil
128
+ desc 'Enable the option to skip the refresh of watching list on startup.'
129
+ config_param :skip_refresh_on_startup, :bool, default: false
130
+ desc 'Ignore repeated permission error logs'
131
+ config_param :ignore_repeated_permission_error, :bool, default: false
132
+ desc 'Format path with the specified timezone'
133
+ config_param :path_timezone, :string, default: nil
134
+ desc 'Follow inodes instead of following file names. Guarantees more stable delivery and allows to use * in path pattern with rotating files'
135
+ config_param :follow_inodes, :bool, default: false
136
+ desc 'Maximum length of line. The longer line is just skipped.'
137
+ config_param :max_line_size, :size, default: nil
138
+
139
+ config_section :parse, required: false, multi: true, init: true, param_name: :parser_configs do
140
+ config_argument :usage, :string, default: 'in_tail_parser'
141
+ end
142
+
143
+ attr_reader :paths
144
+
145
+ def configure(conf)
146
+ @variable_store = Fluent::VariableStore.fetch_or_build(:in_tail)
147
+ compat_parameters_convert(conf, :parser)
148
+ parser_config = conf.elements('parse').first
149
+ unless parser_config
150
+ raise Fluent::ConfigError, "<parse> section is required."
151
+ end
152
+
153
+ (1..Fluent::Plugin::MultilineParser::FORMAT_MAX_NUM).each do |n|
154
+ parser_config["format#{n}"] = conf["format#{n}"] if conf["format#{n}"]
155
+ end
156
+
157
+ parser_config['unmatched_lines'] = conf['emit_unmatched_lines']
158
+
159
+ super
160
+
161
+ if @dir_watcher_limit <= 0
162
+ raise Fluent::ConfigError, "dir_watcher_limit must be greater than zero"
163
+ end
164
+
165
+ if !@enable_watch_timer && !@enable_stat_watcher
166
+ raise Fluent::ConfigError, "either of enable_watch_timer or enable_stat_watcher must be true"
167
+ end
168
+ raise Fluent::ConfigError, "'num_threads' must be greater than 0" unless @num_threads.positive?
169
+
170
+ if @enable_dir_watcher && !@enable_stat_watcher
171
+ log.info(
172
+ 'enable_stat_watcher is disabled for files,' +
173
+ ' but enable_dir_watcher still uses directory stat watchers' +
174
+ ' and may use inotify'
175
+ )
176
+ end
177
+
178
+ if @glob_policy == :always && @path_delimiter == ','
179
+ raise Fluent::ConfigError, "cannot use glob_policy as always with the default path_delimiter: `,\""
180
+ end
181
+
182
+ if @glob_policy == :extended && /\{.*,.*\}/.match?(@path) && extended_glob_pattern(@path)
183
+ raise Fluent::ConfigError, "cannot include curly braces with glob patterns in `#{@path}\". Use glob_policy always instead."
184
+ end
185
+
186
+ if RESERVED_CHARS.include?(@path_delimiter)
187
+ rc = RESERVED_CHARS.join(', ')
188
+ raise Fluent::ConfigError, "#{rc} are reserved words: #{@path_delimiter}"
189
+ end
190
+
191
+ @paths = @path.split(@path_delimiter).map(&:strip).uniq
192
+ if @paths.empty?
193
+ raise Fluent::ConfigError, "tail: 'path' parameter is required on tail input"
194
+ end
195
+ if @path_timezone
196
+ Fluent::Timezone.validate!(@path_timezone)
197
+ @path_formatters = @paths.map{|path| [path, Fluent::Timezone.formatter(@path_timezone, path)]}.to_h
198
+ @exclude_path_formatters = @exclude_path.map{|path| [path, Fluent::Timezone.formatter(@path_timezone, path)]}.to_h
199
+ end
200
+ check_dir_permission unless Fluent.windows?
201
+
202
+ # TODO: Use plugin_root_dir and storage plugin to store positions if available
203
+ if @pos_file
204
+ if @variable_store.key?(@pos_file) && !called_in_test?
205
+ plugin_id_using_this_path = @variable_store[@pos_file]
206
+ raise Fluent::ConfigError, "Other 'in_tail' plugin already use same pos_file path: plugin_id = #{plugin_id_using_this_path}, pos_file path = #{@pos_file}"
207
+ end
208
+ @variable_store[@pos_file] = self.plugin_id
209
+ else
210
+ if @follow_inodes
211
+ raise Fluent::ConfigError, "Can't follow inodes without pos_file configuration parameter"
212
+ end
213
+ log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
214
+ log.warn "this parameter is highly recommended to save the position to resume tailing."
215
+ end
216
+
217
+ configure_tag
218
+ configure_encoding
219
+
220
+ @multiline_mode = parser_config["@type"].include?("multiline")
221
+ @file_perm = system_config.file_permission || Fluent::DEFAULT_FILE_PERMISSION
222
+ @dir_perm = system_config.dir_permission || Fluent::DEFAULT_DIR_PERMISSION
223
+ # parser is already created by parser helper
224
+ @parser = parser_create(usage: parser_config['usage'] || @parser_configs.first.usage)
225
+ if @read_bytes_limit_per_second > 0
226
+ if !@enable_watch_timer
227
+ raise Fluent::ConfigError, "Need to enable watch timer when using log throttling feature"
228
+ end
229
+ min_bytes = TailWatcher::IOHandler::BYTES_TO_READ
230
+ if @read_bytes_limit_per_second < min_bytes
231
+ log.warn "Should specify greater equal than #{min_bytes}. Use #{min_bytes} for read_bytes_limit_per_second"
232
+ @read_bytes_limit_per_second = min_bytes
233
+ end
234
+ end
235
+
236
+ opened_file_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_opened_total", help_text: "Total number of opened files")
237
+ closed_file_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_closed_total", help_text: "Total number of closed files")
238
+ rotated_file_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_rotated_total", help_text: "Total number of rotated files")
239
+ throttling_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_throttled_total", help_text: "Total number of times throttling occurs per file when throttling enabled")
240
+ # The metrics for currently tracking files. Since the value may decrease, it cannot be represented using the counter type, so 'prefer_gauge: true' is used instead.
241
+ tracked_file_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_tracked_count", help_text: "Number of tracked files", prefer_gauge: true)
242
+
243
+ @metrics = MetricsInfo.new(opened_file_metrics, closed_file_metrics, rotated_file_metrics, throttling_metrics, tracked_file_metrics)
244
+ end
245
+
246
+ def check_dir_permission
247
+ expand_paths_raw.select { |path|
248
+ not File.exist?(path)
249
+ }.each { |path|
250
+ inaccessible_dir = Pathname.new(File.expand_path(path))
251
+ .ascend
252
+ .reverse_each
253
+ .find { |p| p.directory? && !p.executable? }
254
+ if inaccessible_dir
255
+ log.warn "Skip #{path} because '#{inaccessible_dir}' lacks execute permission."
256
+ end
257
+ }
258
+ end
259
+
260
+ def configure_tag
261
+ if @tag.index('*')
262
+ @tag_prefix, @tag_suffix = @tag.split('*')
263
+ @tag_prefix ||= ''
264
+ @tag_suffix ||= ''
265
+ else
266
+ @tag_prefix = nil
267
+ @tag_suffix = nil
268
+ end
269
+ end
270
+
271
+ def configure_encoding
272
+ unless @encoding
273
+ if @from_encoding
274
+ raise Fluent::ConfigError, "tail: 'from_encoding' parameter must be specified with 'encoding' parameter."
275
+ end
276
+ end
277
+
278
+ @encoding = parse_encoding_param(@encoding) if @encoding
279
+ @from_encoding = parse_encoding_param(@from_encoding) if @from_encoding
280
+ if @encoding && (@encoding == @from_encoding)
281
+ log.warn "'encoding' and 'from_encoding' are same encoding. No effect"
282
+ end
283
+ end
284
+
285
+ def parse_encoding_param(encoding_name)
286
+ begin
287
+ Encoding.find(encoding_name) if encoding_name
288
+ rescue ArgumentError => e
289
+ raise Fluent::ConfigError, e.message
290
+ end
291
+ end
292
+
293
+ def start
294
+ super
295
+
296
+ @line_feeder = build_line_feeder
297
+ configure_worker_mode if @num_threads > 1
298
+
299
+ if @pos_file
300
+ pos_file_dir = File.dirname(@pos_file)
301
+ FileUtils.mkdir_p(pos_file_dir, mode: @dir_perm) unless Dir.exist?(pos_file_dir)
302
+ @pf_file = File.open(@pos_file, File::RDWR|File::CREAT|File::BINARY, @file_perm)
303
+ @pf_file.sync = true
304
+ @pf = PositionFile.load(@pf_file, @follow_inodes, expand_paths, logger: log)
305
+
306
+ if @pos_file_compaction_interval
307
+ timer_execute(:in_tail_refresh_compact_pos_file, @pos_file_compaction_interval) do
308
+ log.info('Clean up the pos file')
309
+ @pf.try_compact
310
+ end
311
+ end
312
+ end
313
+
314
+ refresh_watchers unless @skip_refresh_on_startup
315
+ update_dir_watchers
316
+ timer_execute(:in_tail_refresh_watchers, @refresh_interval, &method(:refresh_watchers_on_timer))
317
+ end
318
+
319
+ def stop
320
+ if @variable_store
321
+ @variable_store.delete(@pos_file)
322
+ end
323
+
324
+ super
325
+ end
326
+
327
+ def shutdown
328
+ @shutdown_start_time = Fluent::Clock.now
329
+ # Initial reads run as part of a refresh. Signal their timeout before
330
+ # waiting for the refresh, so a large file cannot delay shutdown forever.
331
+ (@tails.values + @tails_rotate_wait.keys).uniq.each do |tw|
332
+ tw.ready_to_shutdown(@shutdown_start_time)
333
+ end
334
+ # A refresh or a rotation handling may yield during file operations. Let it
335
+ # finish before detaching watchers or closing the position file.
336
+ @watcher_refresh_monitor.synchronize {}
337
+ # The event loop is still running at this point, so the watchers added to
338
+ # detect new files must be detached before the position file is closed:
339
+ # a change noticed by them would ask for refreshing the watchers, and
340
+ # #refresh_watchers writes the position file.
341
+ detach_dir_watchers
342
+ detach_refresh_timer
343
+ # during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
344
+ stop_watchers(existence_path, immediate: true, remove_watcher: false)
345
+ @tails_rotate_wait.dup.each do |tw, rotation|
346
+ event_loop_detach(rotation[:timer]) if @worker_pool
347
+ detach_watcher(tw, rotation[:ino], false)
348
+ end
349
+ shutdown_worker_pool
350
+ @pf_file.close if @pf_file && !@worker_pool
351
+
352
+ super
353
+ end
354
+
355
+ def close
356
+ super
357
+ @worker_completion_watcher&.close
358
+ # close file handles after all threads stopped (in #close of thread plugin helper)
359
+ # It may be because we need to wait IOHandler.ready_to_shutdown()
360
+ close_watcher_handles
361
+ @pf_file.close if @pf_file && !@pf_file.closed?
362
+ end
363
+
364
+ def have_read_capability?
365
+ @capability.have_capability?(:effective, :dac_read_search) ||
366
+ @capability.have_capability?(:effective, :dac_override)
367
+ end
368
+
369
+ # TailInput builds its LineFeeder in start, after the configure of a plugin
370
+ # which inherits TailInput is done, so that the parameters set by that
371
+ # plugin are used.
372
+ def build_line_feeder
373
+ LineFeeder.new(
374
+ parser: @parser,
375
+ router_provider: method(:router),
376
+ log: log,
377
+ tag: @tag,
378
+ tag_prefix: @tag_prefix,
379
+ tag_suffix: @tag_suffix,
380
+ path_key: @path_key,
381
+ emit_unmatched_lines: @emit_unmatched_lines,
382
+ multiline_mode: @multiline_mode,
383
+ parse_handler: method(@multiline_mode ? :parse_multilines : :parse_singleline),
384
+ convert_handler: method(:convert_line_to_event),
385
+ flush_handler: method(:flush_buffer),
386
+ )
387
+ end
388
+
389
+ def setup_watcher(target_info, pe)
390
+ # A plugin may build a watcher before start. The LineFeeder of that
391
+ # moment has the same parameters, because the configure of every plugin
392
+ # is done before the watchers are built.
393
+ @line_feeder ||= build_line_feeder
394
+ file_feed = @line_feeder.new_file_feed(flush_interval: @multiline_flush_interval)
395
+ read_from_head = !@startup || @read_from_head
396
+ tw = TailWatcher.new(target_info, pe, log, read_from_head, @follow_inodes, method(:update_watcher), file_feed, method(:io_handler), @metrics)
397
+ tw.ready_to_shutdown(@shutdown_start_time) if @shutdown_start_time
398
+
399
+ if @enable_watch_timer
400
+ tt = TimerTrigger.new(1, log) { tw.on_notify }
401
+ tw.register_watcher(tt)
402
+ end
403
+
404
+ if @enable_stat_watcher
405
+ tt = StatWatcher.new(target_info.path, log) { tw.on_notify }
406
+ tw.register_watcher(tt)
407
+ end
408
+
409
+ tw.watchers.each do |watcher|
410
+ event_loop_attach(watcher)
411
+ end
412
+
413
+ tw.group_watcher = add_path_to_group_watcher(target_info.path, tw)
414
+
415
+ tw
416
+ rescue => e
417
+ if tw
418
+ tw.watchers.each do |watcher|
419
+ event_loop_detach(watcher)
420
+ end
421
+
422
+ tw.detach(@shutdown_start_time)
423
+ tw.close
424
+ end
425
+ raise e
426
+ end
427
+
428
+ def construct_watcher(target_info)
429
+ path = target_info.path
430
+
431
+ # The file might be rotated or removed after collecting paths, so check inode again here.
432
+ begin
433
+ target_info.ino = Fluent::FileWrapper.stat(path).ino
434
+ rescue Errno::ENOENT, Errno::EACCES
435
+ log.warn "stat() for #{path} failed. Continuing without tailing it."
436
+ return
437
+ end
438
+
439
+ pe = nil
440
+ if @pf
441
+ pe = @pf[target_info]
442
+ pe.update(target_info.ino, 0) if @read_from_head && pe.read_inode.zero?
443
+ end
444
+
445
+ begin
446
+ tw = setup_watcher(target_info, pe)
447
+ rescue WatcherSetupError => e
448
+ log.warn "Skip #{path} because unexpected setup error happens: #{e}"
449
+ return
450
+ end
451
+
452
+ @tails[path] = tw
453
+ tw.ready_to_shutdown(@shutdown_start_time) if @shutdown_start_time
454
+ tw.on_notify
455
+ end
456
+
457
+ def start_watchers(targets_info)
458
+ targets_info.each_value {|target_info|
459
+ construct_watcher(target_info)
460
+ break if before_shutdown?
461
+ }
462
+ end
463
+
464
+ def stop_watchers(targets_info, immediate: false, unwatched: false, remove_watcher: true)
465
+ targets_info.each_value { |target_info|
466
+ if remove_watcher
467
+ tw = @tails.delete(target_info.path)
468
+ else
469
+ tw = @tails[target_info.path]
470
+ end
471
+ if tw
472
+ tw.unwatched = unwatched
473
+ if immediate
474
+ detach_watcher(tw, target_info.ino, false)
475
+ else
476
+ detach_watcher_after_rotate_wait(tw, target_info.ino)
477
+ end
478
+ end
479
+ }
480
+ end
481
+
482
+ def close_watcher_handles
483
+ if @worker_pool
484
+ close_worker_watcher_handles
485
+ return
486
+ end
487
+
488
+ @tails.keys.each do |path|
489
+ tw = @tails.delete(path)
490
+ if tw
491
+ tw.close
492
+ end
493
+ end
494
+ @tails_rotate_wait.keys.each do |tw|
495
+ tw.close
496
+ end
497
+ end
498
+
499
+ def update_watcher(tail_watcher, pe, new_inode)
500
+ @watcher_refresh_monitor.synchronize do
501
+ return if @shutdown_start_time
502
+
503
+ update_watcher_raw(tail_watcher, pe, new_inode)
504
+ end
505
+ end
506
+
507
+ # refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
508
+ def update_watcher_raw(tail_watcher, pe, new_inode)
509
+ # TODO we should use another callback for this.
510
+ # To suppress impact to existing logics, limit the case to `@follow_inodes`.
511
+ # We may not need `@follow_inodes` condition.
512
+ if @follow_inodes && new_inode.nil?
513
+ # nil inode means the file disappeared, so we only need to stop it.
514
+ @tails.delete(tail_watcher.path)
515
+ detach_watcher_after_rotate_wait(tail_watcher, pe.read_inode)
516
+ return
517
+ end
518
+
519
+ path = tail_watcher.path
520
+
521
+ log.info("detected rotation of #{path}; waiting #{@rotate_wait} seconds")
522
+
523
+ if @pf
524
+ pe_inode = pe.read_inode
525
+ target_info_from_position_entry = TargetInfo.new(path, pe_inode)
526
+ unless pe_inode == @pf[target_info_from_position_entry].read_inode
527
+ log.warn "Skip update_watcher because watcher has been already updated by other inotify event",
528
+ path: path, inode: pe.read_inode, inode_in_pos_file: @pf[target_info_from_position_entry].read_inode
529
+ return
530
+ end
531
+ end
532
+
533
+ new_target_info = TargetInfo.new(path, new_inode)
534
+
535
+ if @follow_inodes
536
+ new_position_entry = @pf[new_target_info]
537
+ # If `refresh_watcher` find the new file before, this will not be zero.
538
+ # In this case, only we have to do is detaching the current tail_watcher.
539
+ if new_position_entry.read_inode == 0
540
+ @tails[path] = setup_watcher(new_target_info, new_position_entry)
541
+ @tails[path].ready_to_shutdown(@shutdown_start_time) if @shutdown_start_time
542
+ @tails[path].on_notify
543
+ end
544
+ else
545
+ @tails[path] = setup_watcher(new_target_info, pe)
546
+ @tails[path].ready_to_shutdown(@shutdown_start_time) if @shutdown_start_time
547
+ @tails[path].on_notify
548
+ end
549
+
550
+ detach_watcher_after_rotate_wait(tail_watcher, pe.read_inode)
551
+ end
552
+
553
+ def detach_watcher(tw, ino, close_io = true)
554
+ if @follow_inodes && tw.ino != ino
555
+ log.warn("detach_watcher could be detaching an unexpected tail_watcher with a different ino.",
556
+ path: tw.path, actual_ino_in_tw: tw.ino, expect_ino_to_close: ino)
557
+ end
558
+ tw.watchers.each do |watcher|
559
+ event_loop_detach(watcher)
560
+ end
561
+ tw.detach(@shutdown_start_time)
562
+
563
+ # The final read in detach may itself submit a worker batch.
564
+ defer_worker_completion = tw.pending? || @worker_waiting_watchers&.include?(tw)
565
+ if close_io && defer_worker_completion
566
+ tw.defer_close
567
+ @worker_detached_watchers << tw unless @worker_detached_watchers.include?(tw)
568
+ end
569
+ tw.close if close_io
570
+
571
+ # A watcher waiting for `rotate_wait` cannot read once its path leaves the group watcher.
572
+ tw.group_watcher&.delete(tw.path, tw)
573
+
574
+ if @pf && tw.unwatched && (@follow_inodes || !@tails[tw.path])
575
+ target_info = TargetInfo.new(tw.path, ino)
576
+ if defer_worker_completion
577
+ @worker_deferred_unwatch[tw] = target_info
578
+ else
579
+ @pf.unwatch(target_info)
580
+ end
581
+ end
582
+ end
583
+
584
+ def throttling_is_enabled?(tw)
585
+ return true if @read_bytes_limit_per_second > 0
586
+ return true if tw.group_watcher && tw.group_watcher.limit >= 0
587
+ false
588
+ end
589
+
590
+ def detach_watcher_after_rotate_wait(tw, ino)
591
+ # Call event_loop_attach/event_loop_detach is high-cost for short-live object.
592
+ # If this has a problem with large number of files, use @_event_loop directly instead of timer_execute.
593
+ if @open_on_every_update
594
+ # Detach now because it's already closed, waiting it doesn't make sense.
595
+ detach_watcher(tw, ino)
596
+ return
597
+ end
598
+
599
+ return if @tails_rotate_wait[tw]
600
+
601
+ if throttling_is_enabled?(tw)
602
+ # When the throttling feature is enabled, it might not reach EOF yet.
603
+ # Should ensure to read all contents before closing it, with keeping throttling.
604
+ start_time_to_wait = Fluent::Clock.now
605
+ timer = timer_execute(:in_tail_close_watcher, 1, repeat: true) do
606
+ # Without the watch timer, nothing else notifies a watcher whose path has been rotated away.
607
+ unless @enable_watch_timer
608
+ begin
609
+ tw.read_more
610
+ rescue => e
611
+ log.error e.to_s
612
+ log.error_backtrace
613
+ end
614
+ end
615
+ elapsed = Fluent::Clock.now - start_time_to_wait
616
+ if tw.eof? && elapsed >= @rotate_wait
617
+ timer.detach
618
+ @tails_rotate_wait.delete(tw)
619
+ detach_watcher(tw, ino)
620
+ end
621
+ end
622
+ @tails_rotate_wait[tw] = { ino: ino, timer: timer }
623
+ else
624
+ # when the throttling feature isn't enabled, just wait @rotate_wait
625
+ timer = timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
626
+ @tails_rotate_wait.delete(tw)
627
+ detach_watcher(tw, ino)
628
+ end
629
+ @tails_rotate_wait[tw] = { ino: ino, timer: timer }
630
+ end
631
+ end
632
+
633
+ def statistics
634
+ stats = super
635
+
636
+ stats = {
637
+ 'input' => stats["input"].merge({
638
+ 'opened_file_count' => @metrics.opened.get,
639
+ 'closed_file_count' => @metrics.closed.get,
640
+ 'rotated_file_count' => @metrics.rotated.get,
641
+ 'throttled_log_count' => @metrics.throttled.get,
642
+ 'tracked_file_count' => @metrics.tracked.get,
643
+ })
644
+ }
645
+ if @worker_pool
646
+ stats['input'].merge!(
647
+ 'worker_pending_batch_count' => @worker_metrics[:pending_batches].get,
648
+ 'worker_pending_bytes' => @worker_metrics[:pending_bytes].get,
649
+ 'worker_parse_error_count' => @worker_metrics[:parse_errors].get,
650
+ )
651
+ end
652
+ stats
653
+ end
654
+
655
+ private
656
+
657
+ def io_handler(watcher, path)
658
+ opts = {
659
+ path: path,
660
+ log: log,
661
+ read_lines_limit: @read_lines_limit,
662
+ read_bytes_limit_per_second: @read_bytes_limit_per_second,
663
+ open_on_every_update: @open_on_every_update,
664
+ metrics: @metrics,
665
+ max_line_size: @max_line_size,
666
+ }
667
+ unless @encoding.nil?
668
+ if @from_encoding.nil?
669
+ opts[:encoding] = @encoding
670
+ else
671
+ opts[:encoding] = @from_encoding
672
+ opts[:encoding_to_convert] = @encoding
673
+ end
674
+ end
675
+
676
+ TailWatcher::IOHandler.new(
677
+ watcher,
678
+ **opts,
679
+ &method(:receive_lines)
680
+ )
681
+ end
682
+ end
683
+
684
+ # Reopen TailInput to keep worker-mode setup and lifecycle helpers grouped
685
+ # separately from the synchronous file-watching implementation above.
686
+ class TailInput
687
+ WORKER_COMPATIBILITY = WorkerCompatibility.new(self)
688
+ WORKER_PARSER_TYPES = %w[
689
+ none regexp json csv tsv ltsv msgpack apache apache2 apache_error nginx syslog
690
+ ].freeze
691
+ WORKER_QUEUE_BYTES = 64 * 1024 * 1024
692
+ WorkerBatch = Struct.new(:watcher, :lines)
693
+
694
+ private
695
+
696
+ def configure_worker_mode
697
+ incompatibility_reasons = worker_incompatibility_reasons
698
+ @worker_compatible = incompatibility_reasons.empty?
699
+ if @worker_compatible
700
+ setup_worker_pool
701
+ log.info "in_tail worker parsing enabled with #{@num_threads} threads"
702
+ else
703
+ log.warn "in_tail worker parsing is unavailable; using synchronous parsing (#{incompatibility_reasons.join(', ')})"
704
+ end
705
+ end
706
+
707
+ def worker_incompatibility_reasons
708
+ reasons = WORKER_COMPATIBILITY.incompatible_hooks(self).map { |hook| "overridden hook: #{hook}" }
709
+ reasons << 'multiline parsing is enabled' if @multiline_mode
710
+ reasons << 'open_on_every_update is enabled' if @open_on_every_update
711
+ parser_type = @parser_configs.first[:@type]
712
+ reasons << "unsupported parser: #{parser_type}" unless WORKER_PARSER_TYPES.include?(parser_type)
713
+ parser_usage = @parser_configs.first.usage
714
+ reasons << 'the configured parser instance was replaced' unless @parser.equal?(@_parsers[parser_usage])
715
+ reasons
716
+ end
717
+
718
+ def create_worker_parsers(num_threads)
719
+ return [] unless @worker_compatible
720
+ raise ArgumentError, 'worker count must be positive' unless num_threads.positive?
721
+
722
+ parser_config = @parser_configs.first.corresponding_config_element
723
+ Array.new(num_threads) do |index|
724
+ usage = "__in_tail_worker_#{object_id}_#{index}"
725
+ parser_create(usage: usage, conf: parser_config)
726
+ end
727
+ end
728
+
729
+ def setup_worker_pool
730
+ @worker_metrics = {
731
+ pending_batches: metrics_create(namespace: 'fluentd', subsystem: 'input', name: 'worker_pending_batches',
732
+ help_text: 'Number of worker batches awaiting completion or acknowledgment',
733
+ prefer_gauge: true),
734
+ pending_bytes: metrics_create(namespace: 'fluentd', subsystem: 'input', name: 'worker_pending_bytes',
735
+ help_text: 'Estimated bytes accounted for by pending worker batches',
736
+ prefer_gauge: true),
737
+ parse_errors: metrics_create(namespace: 'fluentd', subsystem: 'input', name: 'worker_parse_errors_total',
738
+ help_text: 'Total number of worker parse errors'),
739
+ }
740
+ @worker_parsers = create_worker_parsers(@num_threads)
741
+ @worker_line_feeders = @worker_parsers.map { |parser| build_worker_line_feeder(parser) }
742
+ @worker_waiting_watchers = []
743
+ @worker_deferred_unwatch = {}
744
+ @worker_detached_watchers = []
745
+ @worker_completion_mutex = Mutex.new
746
+ @worker_completions_stopped = false
747
+ @worker_completion_watcher = WorkerCompletionWatcher.new(log: log) { process_worker_completions }
748
+ event_loop_attach(@worker_completion_watcher)
749
+ @worker_pool = WorkerPool.new(
750
+ num_threads: @num_threads,
751
+ task_limit: @num_threads * 2,
752
+ byte_limit: WORKER_QUEUE_BYTES,
753
+ thread_create: ->(title, &block) { Thread.new { Thread.current.name = title.to_s if Thread.current.respond_to?(:name=); block.call } },
754
+ notify: @worker_completion_watcher.method(:signal)
755
+ ) do |index, batch|
756
+ @worker_line_feeders[index].parse(batch.lines.map(&:dup), batch.watcher)
757
+ end
758
+ @worker_pool.start
759
+ rescue
760
+ event_loop_detach(@worker_completion_watcher) if @worker_completion_watcher&.attached?
761
+ @worker_completion_watcher&.close
762
+ raise
763
+ end
764
+
765
+ def build_worker_line_feeder(parser)
766
+ LineFeeder.new(
767
+ parser: parser,
768
+ router_provider: method(:router),
769
+ log: log,
770
+ tag: @tag,
771
+ tag_prefix: @tag_prefix,
772
+ tag_suffix: @tag_suffix,
773
+ path_key: @path_key,
774
+ emit_unmatched_lines: @emit_unmatched_lines,
775
+ multiline_mode: false
776
+ )
777
+ end
778
+
779
+ def async_receive_lines(lines, watcher)
780
+ return @line_feeder.feed_lines(lines, watcher) unless @worker_pool.statistics[:state] == :running
781
+
782
+ bytes = lines.sum(&:bytesize) * 3
783
+ batch = WorkerBatch.new(watcher, lines)
784
+ if @worker_pool.submit(watcher, batch, bytes: bytes)
785
+ update_worker_metrics
786
+ @worker_waiting_watchers.delete(watcher)
787
+ TailWatcher::IOHandler::ASYNC_PENDING
788
+ else
789
+ @worker_waiting_watchers << watcher unless @worker_waiting_watchers.include?(watcher)
790
+ false
791
+ end
792
+ end
793
+
794
+ def process_worker_completions
795
+ @worker_completion_mutex.synchronize do
796
+ drain_worker_completions(resume: true) unless @worker_completions_stopped
797
+ end
798
+ end
799
+
800
+ def drain_worker_completions(resume:)
801
+ while (result = @worker_pool.next_result)
802
+ begin
803
+ complete_worker_result(result, resume: resume)
804
+ rescue => error
805
+ log.error 'unexpected error while completing in_tail worker batch', error: error
806
+ log.error_backtrace
807
+ end
808
+ end
809
+
810
+ return unless resume
811
+
812
+ waiting = @worker_waiting_watchers
813
+ @worker_waiting_watchers = []
814
+ waiting.each do |watcher|
815
+ begin
816
+ watcher.detached? ? watcher.read_more : watcher.on_notify
817
+ rescue => error
818
+ log.error 'unexpected error while retrying in_tail worker batch', error: error
819
+ log.error_backtrace
820
+ ensure
821
+ finish_worker_deferred_close(watcher)
822
+ end
823
+ end
824
+ end
825
+
826
+ def complete_worker_result(result, resume:)
827
+ batch = result.task.payload
828
+ emitted = false
829
+ begin
830
+ if result.error
831
+ @worker_metrics[:parse_errors].inc
832
+ if resume
833
+ log.warn 'worker parsing failed; retrying synchronously', path: batch.watcher.path, error: result.error
834
+ emitted = @line_feeder.feed_lines(batch.lines, batch.watcher)
835
+ else
836
+ log.warn 'worker parsing failed during shutdown; leaving position unchanged', path: batch.watcher.path, error: result.error
837
+ end
838
+ else
839
+ emitted = @line_feeder.emit(result.value, batch.watcher)
840
+ end
841
+ ensure
842
+ begin
843
+ @worker_pool.acknowledge(result)
844
+ update_worker_metrics
845
+ ensure
846
+ begin
847
+ batch.watcher.complete_async(emitted, resume: resume)
848
+ ensure
849
+ finish_worker_deferred_close(batch.watcher)
850
+ end
851
+ end
852
+ end
853
+ end
854
+
855
+ def finish_worker_deferred_close(watcher)
856
+ return if watcher.pending? || @worker_waiting_watchers.include?(watcher)
857
+
858
+ # Match synchronous detach on failed emit or a throttled final read:
859
+ # close the handle, leaving uncommitted data for a future watch.
860
+ watcher.close_if_ready(force: watcher.detached?)
861
+ @worker_detached_watchers.delete(watcher)
862
+
863
+ target_info = @worker_deferred_unwatch.delete(watcher)
864
+ @pf.unwatch(target_info) if target_info && @pf
865
+ end
866
+
867
+ def update_worker_metrics
868
+ statistics = @worker_pool.statistics
869
+ @worker_metrics[:pending_batches].set(statistics[:tasks])
870
+ @worker_metrics[:pending_bytes].set(statistics[:bytes])
871
+ end
872
+
873
+ def close_worker_watcher_handles
874
+ watchers = (@tails.values + @tails_rotate_wait.keys + @worker_detached_watchers + @worker_deferred_unwatch.keys).uniq
875
+ watchers.each(&:force_close)
876
+ @tails.clear
877
+ @worker_detached_watchers.clear
878
+ @worker_deferred_unwatch.each_value do |target_info|
879
+ @pf.unwatch(target_info) if @pf
880
+ end
881
+ @worker_deferred_unwatch.clear
882
+ end
883
+
884
+ def shutdown_worker_pool
885
+ return unless @worker_pool
886
+
887
+ deadline = Fluent::Clock.now + TailWatcher::IOHandler::SHUTDOWN_TIMEOUT
888
+ while worker_work_pending?
889
+ break if Fluent::Clock.now >= deadline
890
+ sleep 0.01
891
+ end
892
+ if worker_work_pending?
893
+ log.warn 'in_tail worker pool did not drain before shutdown timeout'
894
+ end
895
+ event_loop_detach(@worker_completion_watcher)
896
+ # Detach alone does not wait for a callback already running. Serialize
897
+ # the handover so every result is handled before output shutdown starts.
898
+ @worker_completion_mutex.synchronize do
899
+ @worker_completions_stopped = true
900
+ @worker_pool.stop
901
+ end
902
+ unless @worker_pool.join(timeout: TailWatcher::IOHandler::SHUTDOWN_TIMEOUT)
903
+ log.warn 'in_tail worker threads did not stop before timeout'
904
+ @worker_pool.kill
905
+ end
906
+ @worker_completion_mutex.synchronize do
907
+ drain_worker_completions(resume: false)
908
+ end
909
+ end
910
+
911
+ def worker_work_pending?
912
+ @worker_completion_mutex.synchronize do
913
+ @worker_pool.statistics[:tasks].positive? || !@worker_waiting_watchers.empty?
914
+ end
915
+ end
916
+ end
917
+ end