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,1688 @@
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 'fluent/env'
18
+ require 'fluent/error'
19
+ require 'fluent/plugin/base'
20
+ require 'fluent/plugin/buffer'
21
+ require 'fluent/plugin_helper/record_accessor'
22
+ require 'fluent/msgpack_factory'
23
+ require 'fluent/log'
24
+ require 'fluent/plugin_id'
25
+ require 'fluent/plugin_helper'
26
+ require 'fluent/timezone'
27
+ require 'fluent/unique_id'
28
+ require 'fluent/clock'
29
+ require 'fluent/ext_monitor_require'
30
+
31
+ require 'json'
32
+ require 'time'
33
+
34
+ module Fluent
35
+ module Plugin
36
+ class Output < Base
37
+ include PluginId
38
+ include PluginLoggerMixin
39
+ include PluginHelper::Mixin
40
+ include UniqueId::Mixin
41
+
42
+ helpers_internal :thread, :retry_state, :metrics
43
+
44
+ CHUNK_KEY_PATTERN = /^[-_.@a-zA-Z0-9]+$/
45
+ CHUNK_KEY_PLACEHOLDER_PATTERN = /\$\{([-_.@$a-zA-Z0-9]+)\}/
46
+ CHUNK_TAG_PLACEHOLDER_PATTERN = /\$\{(tag(?:\[-?\d+\])?)\}/
47
+ CHUNK_ID_PLACEHOLDER_PATTERN = /\$\{chunk_id\}/
48
+
49
+ # Matches absolute paths, including the POSIX root, Windows drive roots, and UNC paths (e.g., \\host\share).
50
+ # It also accounts for forward slashes used as directory separators in Windows.
51
+ ABSOLUTE_PATH_PATTERN = %r{\A[/\\]}
52
+ # Matches Windows drive specifications.
53
+ # A trailing separator after the colon is not required, because a drive-relative path like C:Windows can still escape a relative path template.
54
+ WINDOWS_DRIVE_PATTERN = %r{\A[a-zA-Z]:}
55
+ # Matches .. only when it acts as an independent path component (e.g., ../etc or foo/..),
56
+ # ignoring dots embedded within strings like app..web or a..b/c.
57
+ # It uses zero-width negative lookarounds to avoid consuming characters.
58
+ # This ensures that adjacent components like /a/../../b are accurately detected and counted without missing any.
59
+ PARENT_DIRECTORY_PATTERN = %r{(?<![^/\\])\.\.(?![^/\\])}
60
+
61
+ INVALID_PATH_COMPONENT_PATTERN =
62
+ Regexp.union(ABSOLUTE_PATH_PATTERN, WINDOWS_DRIVE_PATTERN, PARENT_DIRECTORY_PATTERN)
63
+
64
+ CHUNKING_FIELD_WARN_NUM = 4
65
+
66
+ config_param :time_as_integer, :bool, default: false
67
+ desc 'The threshold to show slow flush logs'
68
+ config_param :slow_flush_log_threshold, :float, default: 20.0
69
+
70
+ # `<buffer>` and `<secondary>` sections are available only when '#format' and '#write' are implemented
71
+ config_section :buffer, param_name: :buffer_config, init: true, required: false, multi: false, final: true do
72
+ config_argument :chunk_keys, :array, value_type: :string, default: []
73
+ config_param :@type, :string, default: 'memory', alias: :type
74
+
75
+ config_param :timekey, :time, default: nil # range size to be used: `time.to_i / @timekey`
76
+ config_param :timekey_wait, :time, default: 600
77
+ # These are for #extract_placeholders
78
+ config_param :timekey_use_utc, :bool, default: false # default is localtime
79
+ config_param :timekey_zone, :string, default: Time.now.strftime('%z') # e.g., "-0700" or "Asia/Tokyo"
80
+
81
+ desc 'If true, plugin will try to flush buffer just before shutdown.'
82
+ config_param :flush_at_shutdown, :bool, default: nil # change default by buffer_plugin.persistent?
83
+
84
+ desc 'How to enqueue chunks to be flushed. "interval" flushes per flush_interval, "immediate" flushes just after event arrival.'
85
+ config_param :flush_mode, :enum, list: [:default, :lazy, :interval, :immediate], default: :default
86
+ config_param :flush_interval, :time, default: 60, desc: 'The interval between buffer chunk flushes.'
87
+
88
+ config_param :flush_thread_count, :integer, default: 1, desc: 'The number of threads to flush the buffer.'
89
+
90
+ config_param :flush_thread_interval, :float, default: 1.0, desc: 'Seconds to sleep between checks for buffer flushes in flush threads.'
91
+ config_param :flush_thread_burst_interval, :float, default: 1.0, desc: 'Seconds to sleep between flushes when many buffer chunks are queued.'
92
+
93
+ config_param :delayed_commit_timeout, :time, default: 60, desc: 'Seconds of timeout for buffer chunks to be committed by plugins later.'
94
+
95
+ config_param :overflow_action, :enum, list: [:throw_exception, :block, :drop_oldest_chunk], default: :throw_exception, desc: 'The action when the size of buffer exceeds the limit.'
96
+
97
+ config_param :retry_forever, :bool, default: false, desc: 'If true, plugin will ignore retry_timeout and retry_max_times options and retry flushing forever.'
98
+ config_param :retry_timeout, :time, default: 72 * 60 * 60, desc: 'The maximum seconds to retry to flush while failing, until plugin discards buffer chunks.'
99
+ # 72hours == 17 times with exponential backoff (not to change default behavior)
100
+ config_param :retry_max_times, :integer, default: nil, desc: 'The maximum number of times to retry to flush while failing.'
101
+
102
+ config_param :retry_secondary_threshold, :float, default: 0.8, desc: 'ratio of retry_timeout to switch to use secondary while failing.'
103
+ # exponential backoff sequence will be initialized at the time of this threshold
104
+
105
+ desc 'How to wait next retry to flush buffer.'
106
+ config_param :retry_type, :enum, list: [:exponential_backoff, :periodic], default: :exponential_backoff
107
+ ### Periodic -> fixed :retry_wait
108
+ ### Exponential backoff: k is number of retry times
109
+ # c: constant factor, @retry_wait
110
+ # b: base factor, @retry_exponential_backoff_base
111
+ # k: times
112
+ # total retry time: c + c * b^1 + (...) + c*b^k = c*b^(k+1) - 1
113
+ config_param :retry_wait, :time, default: 1, desc: 'Seconds to wait before next retry to flush, or constant factor of exponential backoff.'
114
+ config_param :retry_exponential_backoff_base, :float, default: 2, desc: 'The base number of exponential backoff for retries.'
115
+ config_param :retry_max_interval, :time, default: nil, desc: 'The maximum interval seconds for exponential backoff between retries while failing.'
116
+
117
+ config_param :retry_randomize, :bool, default: true, desc: 'If true, output plugin will retry after randomized interval not to do burst retries.'
118
+ end
119
+
120
+ config_section :secondary, param_name: :secondary_config, required: false, multi: false, final: true do
121
+ config_param :@type, :string, default: nil, alias: :type
122
+ config_section :buffer, required: false, multi: false do
123
+ # dummy to detect invalid specification for here
124
+ end
125
+ config_section :secondary, required: false, multi: false do
126
+ # dummy to detect invalid specification for here
127
+ end
128
+ end
129
+
130
+ def process(tag, es)
131
+ raise NotImplementedError, "BUG: output plugins MUST implement this method"
132
+ end
133
+
134
+ def write(chunk)
135
+ raise NotImplementedError, "BUG: output plugins MUST implement this method"
136
+ end
137
+
138
+ def try_write(chunk)
139
+ raise NotImplementedError, "BUG: output plugins MUST implement this method"
140
+ end
141
+
142
+ def format(tag, time, record)
143
+ # standard msgpack_event_stream chunk will be used if this method is not implemented in plugin subclass
144
+ raise NotImplementedError, "BUG: output plugins MUST implement this method"
145
+ end
146
+
147
+ def formatted_to_msgpack_binary?
148
+ # To indicate custom format method (#format) returns msgpack binary or not.
149
+ # If #format returns msgpack binary, override this method to return true.
150
+ false
151
+ end
152
+
153
+ # Compatibility for existing plugins
154
+ def formatted_to_msgpack_binary
155
+ formatted_to_msgpack_binary?
156
+ end
157
+
158
+ def prefer_buffered_processing
159
+ # override this method to return false only when all of these are true:
160
+ # * plugin has both implementation for buffered and non-buffered methods
161
+ # * plugin is expected to work as non-buffered plugin if no `<buffer>` sections specified
162
+ true
163
+ end
164
+
165
+ def prefer_delayed_commit
166
+ # override this method to decide which is used of `write` or `try_write` if both are implemented
167
+ true
168
+ end
169
+
170
+ def multi_workers_ready?
171
+ false
172
+ end
173
+
174
+ # Internal states
175
+ FlushThreadState = Struct.new(:thread, :next_clock, :mutex, :cond_var)
176
+ DequeuedChunkInfo = Struct.new(:chunk_id, :time, :timeout) do
177
+ def expired?
178
+ time + timeout < Time.now
179
+ end
180
+ end
181
+
182
+ attr_reader :as_secondary, :delayed_commit, :delayed_commit_timeout, :timekey_zone
183
+
184
+ # for tests
185
+ attr_reader :buffer, :retry, :secondary, :chunk_keys, :chunk_key_accessors, :chunk_key_time, :chunk_key_tag
186
+ attr_accessor :output_enqueue_thread_waiting, :dequeued_chunks, :dequeued_chunks_mutex
187
+ # output_enqueue_thread_waiting: for test of output.rb itself
188
+ attr_accessor :retry_for_error_chunk # if true, error flush will be retried even if under_plugin_development is true
189
+
190
+ def initialize
191
+ super
192
+ @counter_mutex = Mutex.new
193
+ @flush_thread_mutex = Mutex.new
194
+ @buffering = false
195
+ @delayed_commit = false
196
+ @as_secondary = false
197
+ @primary_instance = nil
198
+
199
+ # TODO: well organized counters
200
+ @num_errors_metrics = nil
201
+ @emit_count_metrics = nil
202
+ @emit_records_metrics = nil
203
+ @emit_size_metrics = nil
204
+ @write_count_metrics = nil
205
+ @write_secondary_count_metrics = nil
206
+ @rollback_count_metrics = nil
207
+ @flush_time_count_metrics = nil
208
+ @slow_flush_count_metrics = nil
209
+ @drop_oldest_chunk_count_metrics = nil
210
+ @enable_size_metrics = false
211
+
212
+ # How to process events is decided here at once, but it will be decided in delayed way on #configure & #start
213
+ if implement?(:synchronous)
214
+ if implement?(:buffered) || implement?(:delayed_commit)
215
+ @buffering = nil # do #configure or #start to determine this for full-featured plugins
216
+ else
217
+ @buffering = false
218
+ end
219
+ else
220
+ @buffering = true
221
+ end
222
+ @custom_format = implement?(:custom_format)
223
+ @enable_msgpack_streamer = false # decided later
224
+
225
+ @buffer = nil
226
+ @secondary = nil
227
+ @retry = nil
228
+ @dequeued_chunks = nil
229
+ @dequeued_chunks_mutex = nil
230
+ @output_enqueue_thread = nil
231
+ @output_flush_threads = nil
232
+ @output_flush_thread_current_position = 0
233
+
234
+ @simple_chunking = nil
235
+ @chunk_keys = @chunk_key_accessors = @chunk_key_time = @chunk_key_tag = nil
236
+ @flush_mode = nil
237
+ @timekey_zone = nil
238
+
239
+ @retry_for_error_chunk = false
240
+ end
241
+
242
+ def acts_as_secondary(primary)
243
+ @as_secondary = true
244
+ @primary_instance = primary
245
+ @chunk_keys = @primary_instance.chunk_keys || []
246
+ @chunk_key_tag = @primary_instance.chunk_key_tag || false
247
+ if @primary_instance.chunk_key_time
248
+ @chunk_key_time = @primary_instance.chunk_key_time
249
+ @timekey_zone = @primary_instance.timekey_zone
250
+ @output_time_formatter_cache = {}
251
+ end
252
+ self.context_router = primary.context_router
253
+
254
+ singleton_class.module_eval do
255
+ define_method(:commit_write){ |chunk_id| @primary_instance.commit_write(chunk_id, delayed: delayed_commit, secondary: true) }
256
+ define_method(:rollback_write){ |chunk_id, update_retry: true| @primary_instance.rollback_write(chunk_id, update_retry) }
257
+ end
258
+ end
259
+
260
+ def configure(conf)
261
+ unless implement?(:synchronous) || implement?(:buffered) || implement?(:delayed_commit)
262
+ raise "BUG: output plugin must implement some methods. see developer documents."
263
+ end
264
+
265
+ has_buffer_section = (conf.elements(name: 'buffer').size > 0)
266
+ has_flush_interval = conf.has_key?('flush_interval')
267
+
268
+ super
269
+
270
+ @num_errors_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "num_errors", help_text: "Number of count num errors")
271
+ @emit_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "emit_count", help_text: "Number of count emits")
272
+ @emit_records_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "emit_records", help_text: "Number of emit records")
273
+ @emit_size_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "emit_size", help_text: "Total size of emit events")
274
+ @write_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "write_count", help_text: "Number of writing events")
275
+ @write_secondary_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "write_secondary_count", help_text: "Number of writing events in secondary")
276
+ @rollback_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "rollback_count", help_text: "Number of rollbacking operations")
277
+ @flush_time_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "flush_time_count", help_text: "Count of flush time")
278
+ @slow_flush_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "slow_flush_count", help_text: "Count of slow flush occurred time(s)")
279
+ @drop_oldest_chunk_count_metrics = metrics_create(namespace: "fluentd", subsystem: "output", name: "drop_oldest_chunk_count", help_text: "Number of count that old chunk were discarded with drop_oldest_chunk")
280
+
281
+ if has_buffer_section
282
+ unless implement?(:buffered) || implement?(:delayed_commit)
283
+ raise Fluent::ConfigError, "<buffer> section is configured, but plugin '#{self.class}' doesn't support buffering"
284
+ end
285
+ @buffering = true
286
+ else # no buffer sections
287
+ if implement?(:synchronous)
288
+ if !implement?(:buffered) && !implement?(:delayed_commit)
289
+ if @as_secondary
290
+ raise Fluent::ConfigError, "secondary plugin '#{self.class}' must support buffering, but doesn't."
291
+ end
292
+ @buffering = false
293
+ else
294
+ if @as_secondary
295
+ # secondary plugin always works as buffered plugin without buffer instance
296
+ @buffering = true
297
+ else
298
+ # @buffering.nil? shows that enabling buffering or not will be decided in lazy way in #start
299
+ @buffering = nil
300
+ end
301
+ end
302
+ else # buffered or delayed_commit is supported by `unless` of first line in this method
303
+ @buffering = true
304
+ end
305
+ end
306
+ # Enable to update record size metrics or not
307
+ @enable_size_metrics = !!system_config.enable_size_metrics
308
+
309
+ if @as_secondary
310
+ if !@buffering && !@buffering.nil?
311
+ raise Fluent::ConfigError, "secondary plugin '#{self.class}' must support buffering, but doesn't"
312
+ end
313
+ end
314
+
315
+ if (@buffering || @buffering.nil?) && !@as_secondary
316
+ # When @buffering.nil?, @buffer_config was initialized with default value for all parameters.
317
+ # If so, this configuration MUST success.
318
+ @chunk_keys = @buffer_config.chunk_keys.dup
319
+ @chunk_key_time = !!@chunk_keys.delete('time')
320
+ @chunk_key_tag = !!@chunk_keys.delete('tag')
321
+ if @chunk_keys.any? { |key|
322
+ begin
323
+ k = Fluent::PluginHelper::RecordAccessor::Accessor.parse_parameter(key)
324
+ if k.is_a?(String)
325
+ k !~ CHUNK_KEY_PATTERN
326
+ else
327
+ if key.start_with?('$[')
328
+ raise Fluent::ConfigError, "in chunk_keys: bracket notation is not allowed"
329
+ else
330
+ false
331
+ end
332
+ end
333
+ rescue => e
334
+ raise Fluent::ConfigError, "in chunk_keys: #{e.message}"
335
+ end
336
+ }
337
+ raise Fluent::ConfigError, "chunk_keys specification includes invalid char"
338
+ else
339
+ @chunk_key_accessors = Hash[@chunk_keys.map { |key| [key.to_sym, Fluent::PluginHelper::RecordAccessor::Accessor.new(key)] }]
340
+ end
341
+
342
+ if @chunk_key_time
343
+ raise Fluent::ConfigError, "<buffer ...> argument includes 'time', but timekey is not configured" unless @buffer_config.timekey
344
+ Fluent::Timezone.validate!(@buffer_config.timekey_zone)
345
+ @timekey_zone = @buffer_config.timekey_use_utc ? '+0000' : @buffer_config.timekey_zone
346
+ @timekey = @buffer_config.timekey
347
+ if @timekey <= 0
348
+ raise Fluent::ConfigError, "timekey should be greater than 0. current timekey: #{@timekey}"
349
+ end
350
+ @timekey_use_utc = @buffer_config.timekey_use_utc
351
+ @offset = Fluent::Timezone.utc_offset(@timekey_zone)
352
+ @calculate_offset = @offset.respond_to?(:call) ? @offset : nil
353
+ @output_time_formatter_cache = {}
354
+ end
355
+
356
+ if (@chunk_key_tag ? 1 : 0) + @chunk_keys.size >= CHUNKING_FIELD_WARN_NUM
357
+ log.warn "many chunk keys specified, and it may cause too many chunks on your system."
358
+ end
359
+
360
+ # no chunk keys or only tags (chunking can be done without iterating event stream)
361
+ @simple_chunking = !@chunk_key_time && @chunk_keys.empty?
362
+
363
+ @flush_mode = @buffer_config.flush_mode
364
+ if @flush_mode == :default
365
+ if has_flush_interval
366
+ log.info "'flush_interval' is configured at out side of <buffer>. 'flush_mode' is set to 'interval' to keep existing behaviour"
367
+ @flush_mode = :interval
368
+ else
369
+ @flush_mode = (@chunk_key_time ? :lazy : :interval)
370
+ end
371
+ end
372
+
373
+ buffer_type = @buffer_config[:@type]
374
+ buffer_conf = conf.elements(name: 'buffer').first || Fluent::Config::Element.new('buffer', '', {}, [])
375
+ @buffer = Plugin.new_buffer(buffer_type, parent: self)
376
+ @buffer.configure(buffer_conf)
377
+ keep_buffer_config_compat
378
+ @buffer.enable_update_timekeys if @chunk_key_time
379
+
380
+ @flush_at_shutdown = @buffer_config.flush_at_shutdown
381
+ if @flush_at_shutdown.nil?
382
+ @flush_at_shutdown = if @buffer.persistent?
383
+ false
384
+ else
385
+ true # flush_at_shutdown is true in default for on-memory buffer
386
+ end
387
+ elsif !@flush_at_shutdown && !@buffer.persistent?
388
+ buf_type = Plugin.lookup_type_from_class(@buffer.class)
389
+ log.warn "'flush_at_shutdown' is false, and buffer plugin '#{buf_type}' is not persistent buffer."
390
+ log.warn "your configuration will lose buffered data at shutdown. please confirm your configuration again."
391
+ else
392
+ if Fluent.windows? && @buffer.persistent?
393
+ service_timeout = read_wait_to_kill_service_timeout
394
+ if service_timeout && service_timeout <= 5000 # default value might varies on windows client/server
395
+ log.warn "your WaitToKillServiceTimeout=#{service_timeout} registry configuration seems too short. Recommend to extend the value of 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\WaitToKillServiceTimeout' to prevent buffer corruption from a forced shutdown"
396
+ end
397
+ end
398
+ end
399
+
400
+ if (@flush_mode != :interval) && buffer_conf.has_key?('flush_interval')
401
+ if buffer_conf.has_key?('flush_mode')
402
+ raise Fluent::ConfigError, "'flush_interval' can't be specified when 'flush_mode' is not 'interval' explicitly: '#{@flush_mode}'"
403
+ else
404
+ log.warn "'flush_interval' is ignored because default 'flush_mode' is not 'interval': '#{@flush_mode}'"
405
+ end
406
+ end
407
+
408
+ if @buffer.queued_chunks_limit_size.nil?
409
+ @buffer.queued_chunks_limit_size = @buffer_config.flush_thread_count
410
+ end
411
+ end
412
+
413
+ if @secondary_config
414
+ raise Fluent::ConfigError, "Invalid <secondary> section for non-buffered plugin" unless @buffering
415
+ raise Fluent::ConfigError, "<secondary> section cannot have <buffer> section" if @secondary_config.buffer
416
+ raise Fluent::ConfigError, "<secondary> section cannot have <secondary> section" if @secondary_config.secondary
417
+ if @buffer_config.retry_forever
418
+ log.warn "<secondary> with 'retry_forever', only unrecoverable errors are moved to secondary"
419
+ end
420
+
421
+ secondary_type = @secondary_config[:@type]
422
+ unless secondary_type
423
+ secondary_type = conf['@type'] # primary plugin type
424
+ end
425
+ secondary_conf = conf.elements(name: 'secondary').first
426
+ @secondary = Plugin.new_output(secondary_type)
427
+ unless @secondary.respond_to?(:acts_as_secondary)
428
+ raise Fluent::ConfigError, "Failed to setup secondary plugin in '#{conf['@type']}'. '#{secondary_type}' plugin in not allowed due to non buffered output"
429
+ end
430
+ @secondary.acts_as_secondary(self)
431
+ @secondary.configure(secondary_conf)
432
+ if (@secondary.class.to_s != "Fluent::Plugin::SecondaryFileOutput") &&
433
+ (self.class != @secondary.class) &&
434
+ (@custom_format || @secondary.implement?(:custom_format))
435
+ log.warn "Use different plugin for secondary. Check the plugin works with primary like secondary_file", primary: self.class.to_s, secondary: @secondary.class.to_s
436
+ end
437
+ else
438
+ @secondary = nil
439
+ end
440
+
441
+ self
442
+ end
443
+
444
+ def read_wait_to_kill_service_timeout
445
+ if Fluent.windows?
446
+ begin
447
+ require "win32/registry"
448
+ Win32::Registry::HKEY_LOCAL_MACHINE.open("SYSTEM\\CurrentControlSet\\Control",
449
+ Win32::Registry::KEY_READ) do |reg|
450
+ reg["WaitToKillServiceTimeout"].to_i
451
+ end
452
+ rescue => e
453
+ log.warn "'flush_at_shutdown' is true, but can't check WaitToKillServiceTimeout registry configuration", error: e
454
+ end
455
+ else
456
+ nil # not supported
457
+ end
458
+ end
459
+
460
+ def keep_buffer_config_compat
461
+ # Need this to call `@buffer_config.disable_chunk_backup` just as before,
462
+ # since some plugins may use this option in this way.
463
+ @buffer_config[:disable_chunk_backup] = @buffer.disable_chunk_backup
464
+ end
465
+
466
+ def start
467
+ super
468
+
469
+ if @buffering.nil?
470
+ @buffering = prefer_buffered_processing
471
+ if !@buffering && @buffer
472
+ @buffer.terminate # it's not started, so terminate will be enough
473
+ # At here, this plugin works as non-buffered plugin.
474
+ # Un-assign @buffer not to show buffering metrics (e.g., in_monitor_agent)
475
+ @buffer = nil
476
+ end
477
+ end
478
+
479
+ if @buffering
480
+ m = method(:emit_buffered)
481
+ singleton_class.module_eval do
482
+ define_method(:emit_events, m)
483
+ end
484
+
485
+ @custom_format = implement?(:custom_format)
486
+ @enable_msgpack_streamer = @custom_format ? formatted_to_msgpack_binary : true
487
+ @delayed_commit = if implement?(:buffered) && implement?(:delayed_commit)
488
+ prefer_delayed_commit
489
+ else
490
+ implement?(:delayed_commit)
491
+ end
492
+ @delayed_commit_timeout = @buffer_config.delayed_commit_timeout
493
+ else # !@buffering
494
+ m = method(:emit_sync)
495
+ singleton_class.module_eval do
496
+ define_method(:emit_events, m)
497
+ end
498
+ end
499
+
500
+ if @buffering && !@as_secondary
501
+ @retry = nil
502
+ @retry_mutex = Mutex.new
503
+
504
+ @buffer.start
505
+
506
+ @output_enqueue_thread = nil
507
+ @output_enqueue_thread_running = true
508
+
509
+ @output_flush_threads = []
510
+ @output_flush_threads_mutex = Mutex.new
511
+ @output_flush_threads_running = true
512
+
513
+ # mainly for test: detect enqueue works as code below:
514
+ # @output.interrupt_flushes
515
+ # # emits
516
+ # @output.enqueue_thread_wait
517
+ @output_flush_interrupted = false
518
+ @output_enqueue_thread_mutex = Mutex.new
519
+ @output_enqueue_thread_waiting = false
520
+
521
+ @dequeued_chunks = []
522
+ @dequeued_chunks_mutex = Mutex.new
523
+
524
+ @output_flush_thread_current_position = 0
525
+ @buffer_config.flush_thread_count.times do |i|
526
+ thread_title = "flush_thread_#{i}".to_sym
527
+ thread_state = FlushThreadState.new(nil, nil, Mutex.new, ConditionVariable.new)
528
+ thread = thread_create(thread_title) do
529
+ flush_thread_run(thread_state)
530
+ end
531
+ thread_state.thread = thread
532
+ @output_flush_threads_mutex.synchronize do
533
+ @output_flush_threads << thread_state
534
+ end
535
+ end
536
+
537
+ if !@under_plugin_development && (@flush_mode == :interval || @chunk_key_time)
538
+ @output_enqueue_thread = thread_create(:enqueue_thread, &method(:enqueue_thread_run))
539
+ end
540
+ end
541
+ @secondary.start if @secondary
542
+ end
543
+
544
+ def after_start
545
+ super
546
+ @secondary.after_start if @secondary
547
+ end
548
+
549
+ def stop
550
+ @secondary.stop if @secondary
551
+ @buffer.stop if @buffering && @buffer
552
+
553
+ super
554
+ end
555
+
556
+ def before_shutdown
557
+ @secondary.before_shutdown if @secondary
558
+
559
+ if @buffering && @buffer
560
+ if @flush_at_shutdown
561
+ force_flush
562
+ end
563
+ @buffer.before_shutdown
564
+ # Need to ensure to stop enqueueing ... after #shutdown, we cannot write any data
565
+ @output_enqueue_thread_running = false
566
+ if @output_enqueue_thread && @output_enqueue_thread.alive?
567
+ @output_enqueue_thread.wakeup
568
+ @output_enqueue_thread.join
569
+ end
570
+ end
571
+
572
+ super
573
+ end
574
+
575
+ def shutdown
576
+ @secondary.shutdown if @secondary
577
+ @buffer.shutdown if @buffering && @buffer
578
+
579
+ super
580
+ end
581
+
582
+ def after_shutdown
583
+ try_rollback_all if @buffering && !@as_secondary # rollback regardless with @delayed_commit, because secondary may do it
584
+ @secondary.after_shutdown if @secondary
585
+
586
+ if @buffering && @buffer
587
+ @buffer.after_shutdown
588
+
589
+ @output_flush_threads_running = false
590
+ if @output_flush_threads && !@output_flush_threads.empty?
591
+ @output_flush_threads.each do |state|
592
+ # to wakeup thread and make it to stop by itself
593
+ state.mutex.synchronize {
594
+ if state.thread&.status
595
+ state.next_clock = 0
596
+ state.cond_var.signal
597
+ end
598
+ }
599
+ Thread.pass
600
+ state.thread.join
601
+ end
602
+ end
603
+ end
604
+
605
+ super
606
+ end
607
+
608
+ def close
609
+ @buffer.close if @buffering && @buffer
610
+ @secondary.close if @secondary
611
+
612
+ super
613
+ end
614
+
615
+ def terminate
616
+ @buffer.terminate if @buffering && @buffer
617
+ @secondary.terminate if @secondary
618
+
619
+ super
620
+ end
621
+
622
+ def actual_flush_thread_count
623
+ return 0 unless @buffering
624
+ return @buffer_config.flush_thread_count unless @as_secondary
625
+ @primary_instance.buffer_config.flush_thread_count
626
+ end
627
+
628
+ # Ensures `path` (filename or filepath) processable
629
+ # only by the current thread in the current process.
630
+ # For multiple workers, the lock is shared if `path` is the same value.
631
+ # For multiple threads, the lock is shared by all threads in the same process.
632
+ def synchronize_path(path)
633
+ synchronize_path_in_workers(path) do
634
+ synchronize_in_threads do
635
+ yield
636
+ end
637
+ end
638
+ end
639
+
640
+ def synchronize_path_in_workers(path)
641
+ need_worker_lock = system_config.workers > 1
642
+ if need_worker_lock
643
+ acquire_worker_lock(path) { yield }
644
+ else
645
+ yield
646
+ end
647
+ end
648
+
649
+ def synchronize_in_threads
650
+ need_thread_lock = actual_flush_thread_count > 1
651
+ if need_thread_lock
652
+ @flush_thread_mutex.synchronize { yield }
653
+ else
654
+ yield
655
+ end
656
+ end
657
+
658
+ def support_in_v12_style?(feature)
659
+ # for plugins written in v0.12 styles
660
+ case feature
661
+ when :synchronous then false
662
+ when :buffered then false
663
+ when :delayed_commit then false
664
+ when :custom_format then false
665
+ else
666
+ raise ArgumentError, "unknown feature: #{feature}"
667
+ end
668
+ end
669
+
670
+ def implement?(feature)
671
+ methods_of_plugin = self.class.instance_methods(false)
672
+ case feature
673
+ when :synchronous then methods_of_plugin.include?(:process) || support_in_v12_style?(:synchronous)
674
+ when :buffered then methods_of_plugin.include?(:write) || support_in_v12_style?(:buffered)
675
+ when :delayed_commit then methods_of_plugin.include?(:try_write)
676
+ when :custom_format then methods_of_plugin.include?(:format) || support_in_v12_style?(:custom_format)
677
+ else
678
+ raise ArgumentError, "Unknown feature for output plugin: #{feature}"
679
+ end
680
+ end
681
+
682
+ def placeholder_validate!(name, str)
683
+ placeholder_validators(name, str).each do |v|
684
+ v.validate!
685
+ end
686
+ end
687
+
688
+ def placeholder_validators(name, str, time_key = (@chunk_key_time && @buffer_config.timekey), tag_key = @chunk_key_tag, chunk_keys = @chunk_keys)
689
+ validators = []
690
+
691
+ sec, title, example = get_placeholders_time(str)
692
+ if sec || time_key
693
+ validators << PlaceholderValidator.new(name, str, :time, {sec: sec, title: title, example: example, timekey: time_key})
694
+ end
695
+
696
+ parts = get_placeholders_tag(str)
697
+ if tag_key || !parts.empty?
698
+ validators << PlaceholderValidator.new(name, str, :tag, {parts: parts, tagkey: tag_key})
699
+ end
700
+
701
+ keys = get_placeholders_keys(str)
702
+ if chunk_keys && !chunk_keys.empty? || !keys.empty?
703
+ validators << PlaceholderValidator.new(name, str, :keys, {keys: keys, chunkkeys: chunk_keys})
704
+ end
705
+
706
+ validators
707
+ end
708
+
709
+ class PlaceholderValidator
710
+ attr_reader :name, :string, :type, :argument
711
+
712
+ def initialize(name, str, type, arg)
713
+ @name = name
714
+ @string = str
715
+ @type = type
716
+ raise ArgumentError, "invalid type:#{type}" if @type != :time && @type != :tag && @type != :keys
717
+ @argument = arg
718
+ end
719
+
720
+ def time?
721
+ @type == :time
722
+ end
723
+
724
+ def tag?
725
+ @type == :tag
726
+ end
727
+
728
+ def keys?
729
+ @type == :keys
730
+ end
731
+
732
+ def validate!
733
+ case @type
734
+ when :time then validate_time!
735
+ when :tag then validate_tag!
736
+ when :keys then validate_keys!
737
+ end
738
+ end
739
+
740
+ def validate_time!
741
+ sec = @argument[:sec]
742
+ title = @argument[:title]
743
+ example = @argument[:example]
744
+ timekey = @argument[:timekey]
745
+ if !sec && timekey
746
+ raise Fluent::ConfigError, "Parameter '#{name}: #{string}' doesn't have timestamp placeholders for timekey #{timekey.to_i}"
747
+ end
748
+ if sec && !timekey
749
+ raise Fluent::ConfigError, "Parameter '#{name}: #{string}' has timestamp placeholders, but chunk key 'time' is not configured"
750
+ end
751
+ if sec && timekey && timekey < sec
752
+ raise Fluent::ConfigError, "Parameter '#{name}: #{string}' doesn't have timestamp placeholder for #{title}('#{example}') for timekey #{timekey.to_i}"
753
+ end
754
+ end
755
+
756
+ def validate_tag!
757
+ parts = @argument[:parts]
758
+ tagkey = @argument[:tagkey]
759
+ if tagkey && parts.empty?
760
+ raise Fluent::ConfigError, "Parameter '#{name}: #{string}' doesn't have tag placeholder"
761
+ end
762
+ if !tagkey && !parts.empty?
763
+ raise Fluent::ConfigError, "Parameter '#{name}: #{string}' has tag placeholders, but chunk key 'tag' is not configured"
764
+ end
765
+ end
766
+
767
+ def validate_keys!
768
+ keys = @argument[:keys]
769
+ chunk_keys = @argument[:chunkkeys]
770
+ if (chunk_keys - keys).size > 0
771
+ not_specified = (chunk_keys - keys).sort
772
+ raise Fluent::ConfigError, "Parameter '#{name}: #{string}' doesn't have enough placeholders for keys #{not_specified.join(',')}"
773
+ end
774
+ if (keys - chunk_keys).size > 0
775
+ not_satisfied = (keys - chunk_keys).sort
776
+ raise Fluent::ConfigError, "Parameter '#{name}: #{string}' has placeholders, but chunk keys doesn't have keys #{not_satisfied.join(',')}"
777
+ end
778
+ end
779
+ end
780
+
781
+ TIME_KEY_PLACEHOLDER_THRESHOLDS = [
782
+ [1, :second, '%S'],
783
+ [60, :minute, '%M'],
784
+ [3600, :hour, '%H'],
785
+ [86400, :day, '%d'],
786
+ ]
787
+ TIMESTAMP_CHECK_BASE_TIME = Time.parse("2016-01-01 00:00:00 UTC")
788
+ # it's not validated to use timekey larger than 1 day
789
+ def get_placeholders_time(str)
790
+ base_str = TIMESTAMP_CHECK_BASE_TIME.strftime(str)
791
+ TIME_KEY_PLACEHOLDER_THRESHOLDS.each do |triple|
792
+ sec = triple.first
793
+ return triple if (TIMESTAMP_CHECK_BASE_TIME + sec).strftime(str) != base_str
794
+ end
795
+ nil
796
+ end
797
+
798
+ # -1 means whole tag
799
+ def get_placeholders_tag(str)
800
+ # [["tag"],["tag[0]"]]
801
+ parts = []
802
+ str.scan(CHUNK_TAG_PLACEHOLDER_PATTERN).map(&:first).each do |ph|
803
+ if ph == "tag"
804
+ parts << -1
805
+ elsif ph =~ /^tag\[(-?\d+)\]$/
806
+ parts << $1.to_i
807
+ end
808
+ end
809
+ parts.sort
810
+ end
811
+
812
+ def get_placeholders_keys(str)
813
+ str.scan(CHUNK_KEY_PLACEHOLDER_PATTERN).map(&:first).reject{|s| (s == "tag") || (s == 'chunk_id') }.sort
814
+ end
815
+
816
+ # TODO: optimize this code
817
+ def extract_placeholders(str, chunk)
818
+ metadata = if chunk.is_a?(Fluent::Plugin::Buffer::Chunk)
819
+ chunk_passed = true
820
+ chunk.metadata
821
+ else
822
+ chunk_passed = false
823
+ # For existing plugins. Old plugin passes Chunk.metadata instead of Chunk
824
+ chunk
825
+ end
826
+ if metadata.empty?
827
+ str.sub(CHUNK_ID_PLACEHOLDER_PATTERN) {
828
+ if chunk_passed
829
+ dump_unique_id_hex(chunk.unique_id)
830
+ else
831
+ log.warn "${chunk_id} is not allowed in this plugin. Pass Chunk instead of metadata in extract_placeholders's 2nd argument"
832
+ end
833
+ }
834
+ else
835
+ rvalue = str.dup
836
+ # strftime formatting
837
+ if @chunk_key_time # this section MUST be earlier than rest to use raw 'str'
838
+ @output_time_formatter_cache[str] ||= Fluent::Timezone.formatter(@timekey_zone, str)
839
+ rvalue = @output_time_formatter_cache[str].call(metadata.timekey)
840
+ end
841
+ # ${tag}, ${tag[0]}, ${tag[1]}, ... , ${tag[-2]}, ${tag[-1]}
842
+ if @chunk_key_tag
843
+ if str.include?('${tag}')
844
+ if metadata.tag.match?(INVALID_PATH_COMPONENT_PATTERN)
845
+ raise Fluent::UnrecoverableError, "Invalid path component detected in tag: #{metadata.tag}"
846
+ end
847
+
848
+ rvalue = rvalue.gsub('${tag}', metadata.tag)
849
+ end
850
+ if CHUNK_TAG_PLACEHOLDER_PATTERN.match?(str)
851
+ if metadata.tag.match?(INVALID_PATH_COMPONENT_PATTERN)
852
+ raise Fluent::UnrecoverableError, "Invalid path component detected in tag: #{metadata.tag}"
853
+ end
854
+
855
+ hash = {}
856
+ tag_parts = metadata.tag.split('.')
857
+ tag_parts.each_with_index do |part, i|
858
+ hash["${tag[#{i}]}"] = part
859
+ hash["${tag[#{i-tag_parts.size}]}"] = part
860
+ end
861
+ rvalue = rvalue.gsub(CHUNK_TAG_PLACEHOLDER_PATTERN, hash)
862
+ end
863
+ if rvalue =~ CHUNK_TAG_PLACEHOLDER_PATTERN
864
+ log.warn "tag placeholder '#{$1}' not replaced. tag:#{metadata.tag}, template:#{str}"
865
+ end
866
+ end
867
+
868
+ # First we replace ${chunk_id} with chunk.unique_id (hexlified).
869
+ rvalue = rvalue.sub(CHUNK_ID_PLACEHOLDER_PATTERN) {
870
+ if chunk_passed
871
+ dump_unique_id_hex(chunk.unique_id)
872
+ else
873
+ log.warn "${chunk_id} is not allowed in this plugin. Pass Chunk instead of metadata in extract_placeholders's 2nd argument"
874
+ end
875
+ }
876
+
877
+ # Then, replace other ${chunk_key}s.
878
+ if !@chunk_keys.empty? && metadata.variables
879
+ hash = {'${tag}' => '${tag}'} # not to erase this wrongly
880
+ @chunk_keys.each do |key|
881
+ hash["${#{key}}"] = metadata.variables[key.to_sym]
882
+ end
883
+
884
+ rvalue = rvalue.gsub(CHUNK_KEY_PLACEHOLDER_PATTERN) do |matched|
885
+ replace = hash.fetch(matched) do
886
+ log.warn "chunk key placeholder '#{matched[2..-2]}' not replaced. template:#{str}"
887
+ ''
888
+ end
889
+ if replace.to_s.match?(INVALID_PATH_COMPONENT_PATTERN)
890
+ raise Fluent::UnrecoverableError, "Invalid path component detected in #{matched}: #{replace}"
891
+ end
892
+
893
+ replace
894
+ end
895
+ end
896
+
897
+ # Verifies that the number of parent directory traversals (..) has not increased after variable substitution.
898
+ # While each replaced value is validated individually, this step prevents harmless values from concatenating into a dangerous path (e.g., ${tag}${key}/ becoming ../ if both tag and key are .).
899
+ if rvalue.match?(PARENT_DIRECTORY_PATTERN) &&
900
+ rvalue.scan(PARENT_DIRECTORY_PATTERN).size > str.scan(PARENT_DIRECTORY_PATTERN).size
901
+ raise Fluent::UnrecoverableError, "Invalid path component detected, replaced to: #{rvalue}"
902
+ end
903
+
904
+ if rvalue =~ CHUNK_KEY_PLACEHOLDER_PATTERN
905
+ log.warn "chunk key placeholder '#{$1}' not replaced. template:#{str}"
906
+ end
907
+
908
+ rvalue
909
+ end
910
+ end
911
+
912
+ def emit_events(tag, es)
913
+ # actually this method will be overwritten by #configure
914
+ if @buffering
915
+ emit_buffered(tag, es)
916
+ else
917
+ emit_sync(tag, es)
918
+ end
919
+ end
920
+
921
+ def emit_sync(tag, es)
922
+ @emit_count_metrics.inc
923
+ begin
924
+ process(tag, es)
925
+ @emit_records_metrics.add(es.size)
926
+ @emit_size_metrics.add(es.to_msgpack_stream.bytesize) if @enable_size_metrics
927
+ rescue
928
+ @num_errors_metrics.inc
929
+ raise
930
+ end
931
+ end
932
+
933
+ def emit_buffered(tag, es)
934
+ @emit_count_metrics.inc
935
+ begin
936
+ execute_chunking(tag, es, enqueue: (@flush_mode == :immediate))
937
+ if !@retry && @buffer.queued?(nil, optimistic: true)
938
+ submit_flush_once
939
+ end
940
+ rescue
941
+ # TODO: separate number of errors into emit errors and write/flush errors
942
+ @num_errors_metrics.inc
943
+ raise
944
+ end
945
+ end
946
+
947
+ # TODO: optimize this code
948
+ def metadata(tag, time, record)
949
+ # this arguments are ordered in output plugin's rule
950
+ # Metadata 's argument order is different from this one (timekey, tag, variables)
951
+
952
+ raise ArgumentError, "tag must be a String: #{tag.class}" unless tag.nil? || tag.is_a?(String)
953
+ raise ArgumentError, "time must be a Fluent::EventTime (or Integer): #{time.class}" unless time.nil? || time.is_a?(Fluent::EventTime) || time.is_a?(Integer)
954
+ raise ArgumentError, "record must be a Hash: #{record.class}" unless record.nil? || record.is_a?(Hash)
955
+
956
+ if @chunk_keys.nil? && @chunk_key_time.nil? && @chunk_key_tag.nil?
957
+ # for tests
958
+ return Struct.new(:timekey, :tag, :variables).new
959
+ end
960
+
961
+ timekey = @chunk_key_time ? calculate_timekey(time) : nil
962
+ @_metadata_cache ||= MetadataCache.new
963
+
964
+ # timekey is int from epoch, and `timekey - timekey % 60` is assumed to mach with 0s of each minutes.
965
+ # it's wrong if timezone is configured as one which supports leap second, but it's very rare and
966
+ # we can ignore it (especially in production systems).
967
+ if @chunk_keys.empty?
968
+ return @_metadata_cache.metadata if @_metadata_cache.cached?(timekey: timekey, tag: tag)
969
+
970
+ meta = if @chunk_key_time && @chunk_key_tag
971
+ @buffer.metadata(timekey: timekey, tag: tag)
972
+ elsif @chunk_key_time
973
+ @buffer.metadata(timekey: timekey)
974
+ elsif @chunk_key_tag
975
+ @buffer.metadata(tag: tag)
976
+ else
977
+ @buffer.metadata()
978
+ end
979
+
980
+ @_metadata_cache.update(timekey: timekey, tag: tag, metadata: meta)
981
+ meta
982
+ else
983
+ pairs = Hash[@chunk_key_accessors.map { |k, a| [k, a.call(record)] }]
984
+ return @_metadata_cache.metadata if @_metadata_cache.cached?(timekey: timekey, tag: tag, variables: pairs)
985
+
986
+ meta = @buffer.metadata(timekey: timekey, tag: (@chunk_key_tag ? tag : nil), variables: pairs)
987
+ @_metadata_cache.update(timekey: timekey, tag: tag, variables: pairs, metadata: meta)
988
+ meta
989
+ end
990
+ end
991
+
992
+ def calculate_timekey(time)
993
+ time_int = time.to_i
994
+ return @calculate_timekey_last_timekey if @calculate_timekey_last_time_int == time_int
995
+
996
+ @calculate_timekey_last_time_int = time_int
997
+ if @timekey_use_utc
998
+ @calculate_timekey_last_timekey = (time_int - (time_int % @timekey)).to_i
999
+ else
1000
+ offset = @calculate_offset ? @calculate_offset.call(time) : @offset
1001
+ @calculate_timekey_last_timekey = (time_int - ((time_int + offset) % @timekey)).to_i
1002
+ end
1003
+ end
1004
+
1005
+ def chunk_for_test(tag, time, record)
1006
+ require 'fluent/plugin/buffer/memory_chunk'
1007
+
1008
+ m = metadata(tag, time, record)
1009
+ Fluent::Plugin::Buffer::MemoryChunk.new(m)
1010
+ end
1011
+
1012
+ def execute_chunking(tag, es, enqueue: false)
1013
+ if @simple_chunking
1014
+ handle_stream_simple(tag, es, enqueue: enqueue)
1015
+ elsif @custom_format
1016
+ handle_stream_with_custom_format(tag, es, enqueue: enqueue)
1017
+ else
1018
+ handle_stream_with_standard_format(tag, es, enqueue: enqueue)
1019
+ end
1020
+ end
1021
+
1022
+ def write_guard(&block)
1023
+ begin
1024
+ block.call
1025
+ rescue Fluent::Plugin::Buffer::BufferOverflowError
1026
+ log.warn "failed to write data into buffer by buffer overflow", action: @buffer_config.overflow_action
1027
+ case @buffer_config.overflow_action
1028
+ when :throw_exception
1029
+ raise
1030
+ when :block
1031
+ log.debug "buffer.write is now blocking"
1032
+ until @buffer.storable?
1033
+ if self.stopped?
1034
+ log.error "breaking block behavior to shutdown Fluentd"
1035
+ # to break infinite loop to exit Fluentd process
1036
+ raise
1037
+ end
1038
+ log.trace "sleeping until buffer can store more data"
1039
+ sleep 1
1040
+ end
1041
+ log.debug "retrying buffer.write after blocked operation"
1042
+ retry
1043
+ when :drop_oldest_chunk
1044
+ begin
1045
+ oldest = @buffer.dequeue_chunk
1046
+ if oldest
1047
+ log.warn "dropping oldest chunk to make space after buffer overflow", chunk_id: dump_unique_id_hex(oldest.unique_id)
1048
+ @buffer.purge_chunk(oldest.unique_id)
1049
+ @drop_oldest_chunk_count_metrics.inc
1050
+ else
1051
+ log.error "no queued chunks to be dropped for drop_oldest_chunk"
1052
+ end
1053
+ rescue
1054
+ # ignore any errors
1055
+ end
1056
+ raise unless @buffer.storable?
1057
+ retry
1058
+ else
1059
+ raise "BUG: unknown overflow_action '#{@buffer_config.overflow_action}'"
1060
+ end
1061
+ end
1062
+ end
1063
+
1064
+ FORMAT_MSGPACK_STREAM = ->(e){ e.to_msgpack_stream(packer: Fluent::MessagePackFactory.thread_local_msgpack_packer) }
1065
+ FORMAT_COMPRESSED_MSGPACK_STREAM_GZIP = ->(e){ e.to_compressed_msgpack_stream(packer: Fluent::MessagePackFactory.thread_local_msgpack_packer) }
1066
+ FORMAT_COMPRESSED_MSGPACK_STREAM_ZSTD = ->(e){ e.to_compressed_msgpack_stream(packer: Fluent::MessagePackFactory.thread_local_msgpack_packer, type: :zstd) }
1067
+ FORMAT_MSGPACK_STREAM_TIME_INT = ->(e){ e.to_msgpack_stream(time_int: true, packer: Fluent::MessagePackFactory.thread_local_msgpack_packer) }
1068
+ FORMAT_COMPRESSED_MSGPACK_STREAM_TIME_INT_GZIP = ->(e){ e.to_compressed_msgpack_stream(time_int: true, packer: Fluent::MessagePackFactory.thread_local_msgpack_packer) }
1069
+ FORMAT_COMPRESSED_MSGPACK_STREAM_TIME_INT_ZSTD = ->(e){ e.to_compressed_msgpack_stream(time_int: true, packer: Fluent::MessagePackFactory.thread_local_msgpack_packer, type: :zstd) }
1070
+
1071
+ def generate_format_proc
1072
+ if @buffer && @buffer.compress == :gzip
1073
+ @time_as_integer ? FORMAT_COMPRESSED_MSGPACK_STREAM_TIME_INT_GZIP : FORMAT_COMPRESSED_MSGPACK_STREAM_GZIP
1074
+ elsif @buffer && @buffer.compress == :zstd
1075
+ @time_as_integer ? FORMAT_COMPRESSED_MSGPACK_STREAM_TIME_INT_ZSTD : FORMAT_COMPRESSED_MSGPACK_STREAM_ZSTD
1076
+ else
1077
+ @time_as_integer ? FORMAT_MSGPACK_STREAM_TIME_INT : FORMAT_MSGPACK_STREAM
1078
+ end
1079
+ end
1080
+
1081
+ # metadata_and_data is a Hash of:
1082
+ # (standard format) metadata => event stream
1083
+ # (custom format) metadata => array of formatted event
1084
+ # For standard format, formatting should be done for whole event stream, but
1085
+ # "whole event stream" may be a split of "es" here when it's bigger than chunk_limit_size.
1086
+ # `@buffer.write` will do this splitting.
1087
+ # For custom format, formatting will be done here. Custom formatting always requires
1088
+ # iteration of event stream, and it should be done just once even if total event stream size
1089
+ # is bigger than chunk_limit_size because of performance.
1090
+ def handle_stream_with_custom_format(tag, es, enqueue: false)
1091
+ meta_and_data = Hash.new { |h, k| h[k] = [] }
1092
+ records = 0
1093
+ es.each(unpacker: Fluent::MessagePackFactory.thread_local_msgpack_unpacker) do |time, record|
1094
+ meta = metadata(tag, time, record)
1095
+ res = format(tag, time, record)
1096
+ if res
1097
+ meta_and_data[meta] << res
1098
+ records += 1
1099
+ end
1100
+ end
1101
+ meta_and_data.default_proc = nil
1102
+ write_guard do
1103
+ @buffer.write(meta_and_data, enqueue: enqueue)
1104
+ end
1105
+ @emit_records_metrics.add(es.size)
1106
+ @emit_size_metrics.add(es.to_msgpack_stream.bytesize) if @enable_size_metrics
1107
+ true
1108
+ end
1109
+
1110
+ def handle_stream_with_standard_format(tag, es, enqueue: false)
1111
+ format_proc = generate_format_proc
1112
+ meta_and_data = Hash.new { |h, k| h[k] = MultiEventStream.new }
1113
+ records = 0
1114
+ es.each(unpacker: Fluent::MessagePackFactory.thread_local_msgpack_unpacker) do |time, record|
1115
+ meta = metadata(tag, time, record)
1116
+ meta_and_data[meta].add(time, record)
1117
+ records += 1
1118
+ end
1119
+ meta_and_data.default_proc = nil
1120
+ write_guard do
1121
+ @buffer.write(meta_and_data, format: format_proc, enqueue: enqueue)
1122
+ end
1123
+ @emit_records_metrics.add(es.size)
1124
+ @emit_size_metrics.add(es.to_msgpack_stream.bytesize) if @enable_size_metrics
1125
+ true
1126
+ end
1127
+
1128
+ def handle_stream_simple(tag, es, enqueue: false)
1129
+ format_proc = nil
1130
+ meta = metadata((@chunk_key_tag ? tag : nil), nil, nil)
1131
+ records = es.size
1132
+ if @custom_format
1133
+ records = 0
1134
+ data = []
1135
+ es.each(unpacker: Fluent::MessagePackFactory.thread_local_msgpack_unpacker) do |time, record|
1136
+ res = format(tag, time, record)
1137
+ if res
1138
+ data << res
1139
+ records += 1
1140
+ end
1141
+ end
1142
+ else
1143
+ format_proc = generate_format_proc
1144
+ data = es
1145
+ end
1146
+ write_guard do
1147
+ @buffer.write({meta => data}, format: format_proc, enqueue: enqueue)
1148
+ end
1149
+ @emit_records_metrics.add(es.size)
1150
+ @emit_size_metrics.add(es.to_msgpack_stream.bytesize) if @enable_size_metrics
1151
+ true
1152
+ end
1153
+
1154
+ def commit_write(chunk_id, delayed: @delayed_commit, secondary: false)
1155
+ log.on_trace { log.trace "committing write operation to a chunk", chunk: dump_unique_id_hex(chunk_id), delayed: delayed }
1156
+
1157
+ if delayed
1158
+ @dequeued_chunks_mutex.synchronize do
1159
+ @dequeued_chunks.delete_if{ |info| info.chunk_id == chunk_id }
1160
+ end
1161
+ end
1162
+ @buffer.purge_chunk(chunk_id)
1163
+
1164
+ @retry_mutex.synchronize do
1165
+ if @retry # success to flush chunks in retries
1166
+ if secondary
1167
+ log.warn "retry succeeded by secondary.", chunk_id: dump_unique_id_hex(chunk_id)
1168
+ else
1169
+ log.warn "retry succeeded.", chunk_id: dump_unique_id_hex(chunk_id)
1170
+ end
1171
+ @retry = nil
1172
+ end
1173
+ end
1174
+ end
1175
+
1176
+ # update_retry parameter is for preventing busy loop by async write
1177
+ # We will remove this parameter by re-design retry_state management between threads.
1178
+ def rollback_write(chunk_id, update_retry: true)
1179
+ # This API is to rollback chunks explicitly from plugins.
1180
+ # 3rd party plugins can depend it on automatic rollback of #try_rollback_write
1181
+ @dequeued_chunks_mutex.synchronize do
1182
+ @dequeued_chunks.delete_if{ |info| info.chunk_id == chunk_id }
1183
+ end
1184
+ # returns true if chunk was rollbacked as expected
1185
+ # false if chunk was already flushed and couldn't be rollbacked unexpectedly
1186
+ # in many cases, false can be just ignored
1187
+ if @buffer.takeback_chunk(chunk_id)
1188
+ @rollback_count_metrics.inc
1189
+ if update_retry
1190
+ primary = @as_secondary ? @primary_instance : self
1191
+ primary.update_retry_state(chunk_id, @as_secondary)
1192
+ end
1193
+ true
1194
+ else
1195
+ false
1196
+ end
1197
+ end
1198
+
1199
+ def try_rollback_write
1200
+ @dequeued_chunks_mutex.synchronize do
1201
+ while @dequeued_chunks.first&.expired?
1202
+ info = @dequeued_chunks.shift
1203
+ if @buffer.takeback_chunk(info.chunk_id)
1204
+ @rollback_count_metrics.inc
1205
+ log.warn "failed to flush the buffer chunk, timeout to commit.", chunk_id: dump_unique_id_hex(info.chunk_id), flushed_at: info.time
1206
+ primary = @as_secondary ? @primary_instance : self
1207
+ primary.update_retry_state(info.chunk_id, @as_secondary)
1208
+ end
1209
+ end
1210
+ end
1211
+ end
1212
+
1213
+ def try_rollback_all
1214
+ return unless @dequeued_chunks
1215
+ @dequeued_chunks_mutex.synchronize do
1216
+ until @dequeued_chunks.empty?
1217
+ info = @dequeued_chunks.shift
1218
+ if @buffer.takeback_chunk(info.chunk_id)
1219
+ @rollback_count_metrics.inc
1220
+ log.info "delayed commit for buffer chunks was cancelled in shutdown", chunk_id: dump_unique_id_hex(info.chunk_id)
1221
+ primary = @as_secondary ? @primary_instance : self
1222
+ primary.update_retry_state(info.chunk_id, @as_secondary)
1223
+ end
1224
+ end
1225
+ end
1226
+ end
1227
+
1228
+ def next_flush_time
1229
+ if @buffer.queued?
1230
+ @retry_mutex.synchronize do
1231
+ @retry ? @retry.next_time : Time.now + @buffer_config.flush_thread_burst_interval
1232
+ end
1233
+ else
1234
+ Time.now + @buffer_config.flush_thread_interval
1235
+ end
1236
+ end
1237
+
1238
+ UNRECOVERABLE_ERRORS = [Fluent::UnrecoverableError, TypeError, ArgumentError, NoMethodError, MessagePack::UnpackError, EncodingError, JSON::GeneratorError]
1239
+
1240
+ def try_flush
1241
+ chunk = @buffer.dequeue_chunk
1242
+ return unless chunk
1243
+
1244
+ log.on_trace { log.trace "trying flush for a chunk", chunk: dump_unique_id_hex(chunk.unique_id) }
1245
+
1246
+ output = self
1247
+ using_secondary = false
1248
+ if @retry_mutex.synchronize{ @retry && @retry.secondary? }
1249
+ output = @secondary
1250
+ using_secondary = true
1251
+ end
1252
+
1253
+ if @enable_msgpack_streamer
1254
+ chunk.extend ChunkMessagePackEventStreamer
1255
+ end
1256
+
1257
+ begin
1258
+ chunk_write_start = Fluent::Clock.now
1259
+
1260
+ if output.delayed_commit
1261
+ log.trace "executing delayed write and commit", chunk: dump_unique_id_hex(chunk.unique_id)
1262
+ @write_count_metrics.inc
1263
+ @write_secondary_count_metrics.inc if using_secondary
1264
+ @dequeued_chunks_mutex.synchronize do
1265
+ # delayed_commit_timeout for secondary is configured in <buffer> of primary (<secondary> don't get <buffer>)
1266
+ @dequeued_chunks << DequeuedChunkInfo.new(chunk.unique_id, Time.now, self.delayed_commit_timeout)
1267
+ end
1268
+
1269
+ output.try_write(chunk)
1270
+ check_slow_flush(chunk_write_start)
1271
+ else # output plugin without delayed purge
1272
+ chunk_id = chunk.unique_id
1273
+ dump_chunk_id = dump_unique_id_hex(chunk_id)
1274
+ log.trace "adding write count", instance: self.object_id
1275
+ @write_count_metrics.inc
1276
+ @write_secondary_count_metrics.inc if using_secondary
1277
+ log.trace "executing sync write", chunk: dump_chunk_id
1278
+
1279
+ output.write(chunk)
1280
+ check_slow_flush(chunk_write_start)
1281
+
1282
+ log.trace "write operation done, committing", chunk: dump_chunk_id
1283
+ commit_write(chunk_id, delayed: false, secondary: using_secondary)
1284
+ log.trace "done to commit a chunk", chunk: dump_chunk_id
1285
+ end
1286
+ rescue *UNRECOVERABLE_ERRORS => e
1287
+ if @secondary
1288
+ if using_secondary
1289
+ log.warn "got unrecoverable error in secondary.", error: e
1290
+ log.warn_backtrace
1291
+ backup_chunk(chunk, using_secondary, output.delayed_commit)
1292
+ else
1293
+ if (self.class == @secondary.class)
1294
+ log.warn "got unrecoverable error in primary and secondary type is same as primary. Skip secondary", error: e
1295
+ log.warn_backtrace
1296
+ backup_chunk(chunk, using_secondary, output.delayed_commit)
1297
+ else
1298
+ # Call secondary output directly without retry update.
1299
+ # In this case, delayed commit causes inconsistent state in dequeued chunks so async output in secondary is not allowed for now.
1300
+ if @secondary.delayed_commit
1301
+ log.warn "got unrecoverable error in primary and secondary is async output. Skip secondary for backup", error: e
1302
+ log.warn_backtrace
1303
+ backup_chunk(chunk, using_secondary, output.delayed_commit)
1304
+ else
1305
+ log.warn "got unrecoverable error in primary. Skip retry and flush chunk to secondary", error: e
1306
+ log.warn_backtrace
1307
+ begin
1308
+ @secondary.write(chunk)
1309
+ commit_write(chunk_id, delayed: output.delayed_commit, secondary: true)
1310
+ rescue => e
1311
+ log.warn "got an error in secondary for unrecoverable error", error: e
1312
+ log.warn_backtrace
1313
+ backup_chunk(chunk, using_secondary, output.delayed_commit)
1314
+ end
1315
+ end
1316
+ end
1317
+ end
1318
+ else
1319
+ log.warn "got unrecoverable error in primary and no secondary", error: e
1320
+ log.warn_backtrace
1321
+ backup_chunk(chunk, using_secondary, output.delayed_commit)
1322
+ end
1323
+ rescue => e
1324
+ log.debug "taking back chunk for errors.", chunk: dump_unique_id_hex(chunk.unique_id)
1325
+ if output.delayed_commit
1326
+ @dequeued_chunks_mutex.synchronize do
1327
+ @dequeued_chunks.delete_if{|d| d.chunk_id == chunk.unique_id }
1328
+ end
1329
+ end
1330
+
1331
+ if @buffer.takeback_chunk(chunk.unique_id)
1332
+ @rollback_count_metrics.inc
1333
+ end
1334
+
1335
+ update_retry_state(chunk.unique_id, using_secondary, e)
1336
+
1337
+ raise if @under_plugin_development && !@retry_for_error_chunk
1338
+ end
1339
+ end
1340
+
1341
+ def backup_chunk(chunk, using_secondary, delayed_commit)
1342
+ if @buffer.disable_chunk_backup
1343
+ log.warn "disable_chunk_backup is true. #{dump_unique_id_hex(chunk.unique_id)} chunk is thrown away"
1344
+ else
1345
+ begin
1346
+ @buffer.backup(chunk.unique_id) { |f|
1347
+ chunk.write_to(f)
1348
+ }
1349
+ rescue => e
1350
+ log.warn "failed to back up #{dump_unique_id_hex(chunk.unique_id)} chunk. it is thrown away", error: e
1351
+ log.warn_backtrace
1352
+ end
1353
+ end
1354
+ commit_write(chunk.unique_id, secondary: using_secondary, delayed: delayed_commit)
1355
+ end
1356
+
1357
+ def check_slow_flush(start)
1358
+ elapsed_time = Fluent::Clock.now - start
1359
+ elapsed_millsec = (elapsed_time * 1000).to_i
1360
+ @flush_time_count_metrics.add(elapsed_millsec)
1361
+ if elapsed_time > @slow_flush_log_threshold
1362
+ @slow_flush_count_metrics.inc
1363
+ log.warn "buffer flush took longer time than slow_flush_log_threshold:",
1364
+ elapsed_time: elapsed_time, slow_flush_log_threshold: @slow_flush_log_threshold, plugin_id: self.plugin_id
1365
+ end
1366
+ end
1367
+
1368
+ def update_retry_state(chunk_id, using_secondary, error = nil)
1369
+ @retry_mutex.synchronize do
1370
+ @num_errors_metrics.inc
1371
+ chunk_id_hex = dump_unique_id_hex(chunk_id)
1372
+
1373
+ unless @retry
1374
+ @retry = retry_state(@buffer_config.retry_randomize)
1375
+
1376
+ if @retry.limit?
1377
+ handle_limit_reached(error)
1378
+ elsif error
1379
+ log_retry_error(error, chunk_id_hex, using_secondary)
1380
+ end
1381
+
1382
+ return
1383
+ end
1384
+
1385
+ # @retry exists
1386
+
1387
+ # Ensure that the current time is greater than or equal to @retry.next_time to avoid the situation when
1388
+ # @retry.step is called almost as many times as the number of flush threads in a short time.
1389
+ if Time.now >= @retry.next_time
1390
+ @retry.step
1391
+ else
1392
+ @retry.recalc_next_time # to prevent all flush threads from retrying at the same time
1393
+ end
1394
+
1395
+ if @retry.limit?
1396
+ handle_limit_reached(error)
1397
+ elsif error
1398
+ log_retry_error(error, chunk_id_hex, using_secondary)
1399
+ end
1400
+ end
1401
+ end
1402
+
1403
+ def log_retry_error(error, chunk_id_hex, using_secondary)
1404
+ return unless error
1405
+ if using_secondary
1406
+ msg = "failed to flush the buffer with secondary output."
1407
+ else
1408
+ msg = "failed to flush the buffer."
1409
+ end
1410
+ log.warn(msg, retry_times: @retry.steps, next_retry_time: @retry.next_time.round, chunk: chunk_id_hex, error: error)
1411
+ log.warn_backtrace(error.backtrace)
1412
+ end
1413
+
1414
+ def handle_limit_reached(error)
1415
+ if error
1416
+ records = @buffer.queued_records
1417
+ msg = "Hit limit for retries. dropping all chunks in the buffer queue."
1418
+ log.error msg, retry_times: @retry.steps, records: records, error: error
1419
+ log.error_backtrace error.backtrace
1420
+ end
1421
+ @buffer.clear_queue!
1422
+ log.debug "buffer queue cleared"
1423
+ @retry = nil
1424
+ end
1425
+
1426
+ def retry_state(randomize)
1427
+ if @secondary
1428
+ retry_state_create(
1429
+ :output_retries, @buffer_config.retry_type, @buffer_config.retry_wait, @buffer_config.retry_timeout,
1430
+ forever: @buffer_config.retry_forever, max_steps: @buffer_config.retry_max_times, backoff_base: @buffer_config.retry_exponential_backoff_base,
1431
+ max_interval: @buffer_config.retry_max_interval,
1432
+ secondary: true, secondary_threshold: @buffer_config.retry_secondary_threshold,
1433
+ randomize: randomize
1434
+ )
1435
+ else
1436
+ retry_state_create(
1437
+ :output_retries, @buffer_config.retry_type, @buffer_config.retry_wait, @buffer_config.retry_timeout,
1438
+ forever: @buffer_config.retry_forever, max_steps: @buffer_config.retry_max_times, backoff_base: @buffer_config.retry_exponential_backoff_base,
1439
+ max_interval: @buffer_config.retry_max_interval,
1440
+ randomize: randomize
1441
+ )
1442
+ end
1443
+ end
1444
+
1445
+ def submit_flush_once
1446
+ return unless @buffer_config.flush_thread_count > 0
1447
+ # Without locks: it is rough but enough to select "next" writer selection
1448
+ @output_flush_thread_current_position = (@output_flush_thread_current_position + 1) % @buffer_config.flush_thread_count
1449
+ state = @output_flush_threads[@output_flush_thread_current_position]
1450
+ state.mutex.synchronize {
1451
+ if state.thread&.status # "run"/"sleep"/"aborting" or false(successfully stop) or nil(killed by exception)
1452
+ state.next_clock = 0
1453
+ state.cond_var.signal
1454
+ else
1455
+ log.warn "thread is already dead"
1456
+ end
1457
+ }
1458
+ Thread.pass
1459
+ end
1460
+
1461
+ def force_flush
1462
+ if @buffering
1463
+ @buffer.enqueue_all(true)
1464
+ submit_flush_all
1465
+ end
1466
+ end
1467
+
1468
+ def submit_flush_all
1469
+ return unless @buffer_config.flush_thread_count > 0
1470
+ while !@retry && @buffer.queued?
1471
+ submit_flush_once
1472
+ sleep @buffer_config.flush_thread_burst_interval
1473
+ end
1474
+ end
1475
+
1476
+ # only for tests of output plugin
1477
+ def interrupt_flushes
1478
+ @output_flush_interrupted = true
1479
+ end
1480
+
1481
+ # only for tests of output plugin
1482
+ def enqueue_thread_wait
1483
+ @output_enqueue_thread_mutex.synchronize do
1484
+ @output_flush_interrupted = false
1485
+ @output_enqueue_thread_waiting = true
1486
+ end
1487
+ require 'timeout'
1488
+ Timeout.timeout(10) do
1489
+ Thread.pass while @output_enqueue_thread_waiting
1490
+ end
1491
+ end
1492
+
1493
+ # only for tests of output plugin
1494
+ def flush_thread_wakeup
1495
+ @output_flush_threads.each do |state|
1496
+ state.mutex.synchronize {
1497
+ if state.thread&.status
1498
+ state.next_clock = 0
1499
+ state.cond_var.signal
1500
+ end
1501
+ }
1502
+ Thread.pass
1503
+ end
1504
+ end
1505
+
1506
+ def enqueue_thread_run
1507
+ value_for_interval = nil
1508
+ if @flush_mode == :interval
1509
+ value_for_interval = @buffer_config.flush_interval
1510
+ end
1511
+ if @chunk_key_time
1512
+ if !value_for_interval || @buffer_config.timekey < value_for_interval
1513
+ value_for_interval = [@buffer_config.timekey, @buffer_config.timekey_wait].min
1514
+ end
1515
+ end
1516
+ unless value_for_interval
1517
+ raise "BUG: both of flush_interval and timekey are disabled"
1518
+ end
1519
+ interval = value_for_interval / 11.0
1520
+ if interval < @buffer_config.flush_thread_interval
1521
+ interval = @buffer_config.flush_thread_interval
1522
+ end
1523
+
1524
+ while !self.after_started? && !self.stopped?
1525
+ sleep 0.5
1526
+ end
1527
+ log.debug "enqueue_thread actually running"
1528
+
1529
+ begin
1530
+ while @output_enqueue_thread_running
1531
+ now_int = Time.now.to_i
1532
+ if @output_flush_interrupted
1533
+ sleep interval
1534
+ next
1535
+ end
1536
+
1537
+ @output_enqueue_thread_mutex.lock
1538
+ begin
1539
+ if @flush_mode == :interval
1540
+ flush_interval = @buffer_config.flush_interval.to_i
1541
+ # This block should be done by integer values.
1542
+ # If both of flush_interval & flush_thread_interval are 1s, expected actual flush timing is 1.5s.
1543
+ # If we use integered values for this comparison, expected actual flush timing is 1.0s.
1544
+ @buffer.enqueue_all{ |metadata, chunk| chunk.raw_create_at + flush_interval <= now_int }
1545
+ end
1546
+
1547
+ if @chunk_key_time
1548
+ timekey_unit = @buffer_config.timekey
1549
+ timekey_wait = @buffer_config.timekey_wait
1550
+ current_timekey = now_int - now_int % timekey_unit
1551
+ @buffer.enqueue_all{ |metadata, chunk| metadata.timekey < current_timekey && metadata.timekey + timekey_unit + timekey_wait <= now_int }
1552
+ end
1553
+ rescue => e
1554
+ raise if @under_plugin_development
1555
+ log.error "unexpected error while checking flushed chunks. ignored.", error: e
1556
+ log.error_backtrace
1557
+ ensure
1558
+ @output_enqueue_thread_waiting = false
1559
+ @output_enqueue_thread_mutex.unlock
1560
+ end
1561
+ sleep interval
1562
+ end
1563
+ rescue => e
1564
+ # normal errors are rescued by inner begin-rescue clause.
1565
+ log.error "error on enqueue thread", error: e
1566
+ log.error_backtrace
1567
+ raise
1568
+ end
1569
+ end
1570
+
1571
+ def flush_thread_run(state)
1572
+ flush_thread_interval = @buffer_config.flush_thread_interval
1573
+
1574
+ state.next_clock = Fluent::Clock.now + flush_thread_interval
1575
+
1576
+ while !self.after_started? && !self.stopped?
1577
+ sleep 0.5
1578
+ end
1579
+ log.debug "flush_thread actually running"
1580
+
1581
+ state.mutex.lock
1582
+ begin
1583
+ # This thread don't use `thread_current_running?` because this thread should run in `before_shutdown` phase
1584
+ while @output_flush_threads_running
1585
+ current_clock = Fluent::Clock.now
1586
+ next_retry_time = nil
1587
+
1588
+ @retry_mutex.synchronize do
1589
+ next_retry_time = @retry ? @retry.next_time : nil
1590
+ end
1591
+
1592
+ if state.next_clock > current_clock
1593
+ interval = state.next_clock - current_clock
1594
+ elsif next_retry_time && next_retry_time > Time.now
1595
+ interval = next_retry_time.to_f - Time.now.to_f
1596
+ else
1597
+ state.mutex.unlock
1598
+ begin
1599
+ try_flush
1600
+ # next_flush_time uses flush_thread_interval or flush_thread_burst_interval (or retrying)
1601
+ interval = next_flush_time.to_f - Time.now.to_f
1602
+ # TODO: if secondary && delayed-commit, next_flush_time will be much longer than expected
1603
+ # because @retry still exists (#commit_write is not called yet in #try_flush)
1604
+ # @retry should be cleared if delayed commit is enabled? Or any other solution?
1605
+ state.next_clock = Fluent::Clock.now + interval
1606
+ ensure
1607
+ state.mutex.lock
1608
+ end
1609
+ end
1610
+
1611
+ if @dequeued_chunks_mutex.synchronize{ !@dequeued_chunks.empty? && @dequeued_chunks.first.expired? }
1612
+ unless @output_flush_interrupted
1613
+ state.mutex.unlock
1614
+ begin
1615
+ try_rollback_write
1616
+ ensure
1617
+ state.mutex.lock
1618
+ end
1619
+ end
1620
+ end
1621
+
1622
+ state.cond_var.wait(state.mutex, interval) if interval > 0
1623
+ end
1624
+ rescue => e
1625
+ # normal errors are rescued by output plugins in #try_flush
1626
+ # so this rescue section is for critical & unrecoverable errors
1627
+ log.error "error on output thread", error: e
1628
+ log.error_backtrace
1629
+ raise
1630
+ ensure
1631
+ state.mutex.unlock
1632
+ end
1633
+ end
1634
+
1635
+ BUFFER_STATS_KEYS = {}
1636
+ Fluent::Plugin::Buffer::STATS_KEYS.each { |key|
1637
+ BUFFER_STATS_KEYS[key] = "buffer_#{key}"
1638
+ }
1639
+
1640
+ def statistics
1641
+ stats = {
1642
+ 'emit_records' => @emit_records_metrics.get,
1643
+ 'emit_size' => @emit_size_metrics.get,
1644
+ # Respect original name
1645
+ # https://github.com/fluent/fluentd/blob/45c7b75ba77763eaf87136864d4942c4e0c5bfcd/lib/fluent/plugin/in_monitor_agent.rb#L284
1646
+ 'retry_count' => @num_errors_metrics.get,
1647
+ 'emit_count' => @emit_count_metrics.get,
1648
+ 'write_count' => @write_count_metrics.get,
1649
+ 'write_secondary_count' => @write_secondary_count_metrics.get,
1650
+ 'rollback_count' => @rollback_count_metrics.get,
1651
+ 'slow_flush_count' => @slow_flush_count_metrics.get,
1652
+ 'flush_time_count' => @flush_time_count_metrics.get,
1653
+ 'drop_oldest_chunk_count' => @drop_oldest_chunk_count_metrics.get,
1654
+ }
1655
+
1656
+ if @buffer && @buffer.respond_to?(:statistics)
1657
+ (@buffer.statistics['buffer'] || {}).each do |k, v|
1658
+ stats[BUFFER_STATS_KEYS[k]] = v
1659
+ end
1660
+ end
1661
+
1662
+ { 'output' => stats }
1663
+ end
1664
+ end
1665
+
1666
+ class MetadataCache
1667
+ attr_reader :metadata
1668
+
1669
+ def initialize
1670
+ @timekey = nil
1671
+ @tag = nil
1672
+ @variables = nil
1673
+ @metadata = nil
1674
+ end
1675
+
1676
+ def cached?(timekey:, tag:, variables: nil)
1677
+ @metadata && @timekey == timekey && @tag == tag && @variables == variables
1678
+ end
1679
+
1680
+ def update(timekey:, tag:, variables: nil, metadata:)
1681
+ @timekey = timekey
1682
+ @tag = tag
1683
+ @variables = variables
1684
+ @metadata = metadata
1685
+ end
1686
+ end
1687
+ end
1688
+ end