fluentd-hubspot 0.14.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (396) hide show
  1. data/.github/ISSUE_TEMPLATE.md +6 -0
  2. data/.gitignore +28 -0
  3. data/.travis.yml +51 -0
  4. data/AUTHORS +2 -0
  5. data/CONTRIBUTING.md +42 -0
  6. data/COPYING +14 -0
  7. data/ChangeLog +593 -0
  8. data/Gemfile +9 -0
  9. data/README.md +76 -0
  10. data/Rakefile +74 -0
  11. data/Vagrantfile +17 -0
  12. data/appveyor.yml +43 -0
  13. data/bin/fluent-binlog-reader +7 -0
  14. data/bin/fluent-debug +5 -0
  15. data/bin/fluent-plugin-config-format +5 -0
  16. data/bin/fluent-plugin-generate +5 -0
  17. data/code-of-conduct.md +3 -0
  18. data/example/copy_roundrobin.conf +39 -0
  19. data/example/filter_stdout.conf +22 -0
  20. data/example/in_dummy_blocks.conf +17 -0
  21. data/example/in_dummy_with_compression.conf +23 -0
  22. data/example/in_forward.conf +14 -0
  23. data/example/in_forward_client.conf +37 -0
  24. data/example/in_forward_shared_key.conf +15 -0
  25. data/example/in_forward_tls.conf +14 -0
  26. data/example/in_forward_users.conf +24 -0
  27. data/example/in_forward_workers.conf +21 -0
  28. data/example/in_http.conf +14 -0
  29. data/example/in_out_forward.conf +17 -0
  30. data/example/in_syslog.conf +15 -0
  31. data/example/in_tail.conf +14 -0
  32. data/example/in_tcp.conf +13 -0
  33. data/example/in_udp.conf +13 -0
  34. data/example/logevents.conf +25 -0
  35. data/example/multi_filters.conf +61 -0
  36. data/example/out_copy.conf +20 -0
  37. data/example/out_exec_filter.conf +42 -0
  38. data/example/out_file.conf +13 -0
  39. data/example/out_forward.conf +35 -0
  40. data/example/out_forward_buf_file.conf +23 -0
  41. data/example/out_forward_client.conf +109 -0
  42. data/example/out_forward_heartbeat_none.conf +16 -0
  43. data/example/out_forward_shared_key.conf +36 -0
  44. data/example/out_forward_tls.conf +18 -0
  45. data/example/out_forward_users.conf +65 -0
  46. data/example/out_null.conf +36 -0
  47. data/example/secondary_file.conf +41 -0
  48. data/example/suppress_config_dump.conf +7 -0
  49. data/example/v0_12_filter.conf +78 -0
  50. data/example/v1_literal_example.conf +36 -0
  51. data/fluent.conf +139 -0
  52. data/fluentd.gemspec +51 -0
  53. data/lib/fluent/agent.rb +163 -0
  54. data/lib/fluent/clock.rb +62 -0
  55. data/lib/fluent/command/binlog_reader.rb +234 -0
  56. data/lib/fluent/command/bundler_injection.rb +45 -0
  57. data/lib/fluent/command/cat.rb +330 -0
  58. data/lib/fluent/command/debug.rb +102 -0
  59. data/lib/fluent/command/fluentd.rb +301 -0
  60. data/lib/fluent/command/plugin_config_formatter.rb +258 -0
  61. data/lib/fluent/command/plugin_generator.rb +301 -0
  62. data/lib/fluent/compat/call_super_mixin.rb +67 -0
  63. data/lib/fluent/compat/detach_process_mixin.rb +25 -0
  64. data/lib/fluent/compat/exec_util.rb +129 -0
  65. data/lib/fluent/compat/file_util.rb +54 -0
  66. data/lib/fluent/compat/filter.rb +68 -0
  67. data/lib/fluent/compat/formatter.rb +111 -0
  68. data/lib/fluent/compat/formatter_utils.rb +85 -0
  69. data/lib/fluent/compat/handle_tag_and_time_mixin.rb +62 -0
  70. data/lib/fluent/compat/handle_tag_name_mixin.rb +53 -0
  71. data/lib/fluent/compat/input.rb +59 -0
  72. data/lib/fluent/compat/output.rb +728 -0
  73. data/lib/fluent/compat/output_chain.rb +60 -0
  74. data/lib/fluent/compat/parser.rb +310 -0
  75. data/lib/fluent/compat/parser_utils.rb +40 -0
  76. data/lib/fluent/compat/propagate_default.rb +62 -0
  77. data/lib/fluent/compat/record_filter_mixin.rb +34 -0
  78. data/lib/fluent/compat/set_tag_key_mixin.rb +50 -0
  79. data/lib/fluent/compat/set_time_key_mixin.rb +69 -0
  80. data/lib/fluent/compat/socket_util.rb +165 -0
  81. data/lib/fluent/compat/string_util.rb +34 -0
  82. data/lib/fluent/compat/structured_format_mixin.rb +26 -0
  83. data/lib/fluent/compat/type_converter.rb +90 -0
  84. data/lib/fluent/config.rb +56 -0
  85. data/lib/fluent/config/basic_parser.rb +123 -0
  86. data/lib/fluent/config/configure_proxy.rb +418 -0
  87. data/lib/fluent/config/dsl.rb +149 -0
  88. data/lib/fluent/config/element.rb +218 -0
  89. data/lib/fluent/config/error.rb +26 -0
  90. data/lib/fluent/config/literal_parser.rb +251 -0
  91. data/lib/fluent/config/parser.rb +107 -0
  92. data/lib/fluent/config/section.rb +223 -0
  93. data/lib/fluent/config/types.rb +136 -0
  94. data/lib/fluent/config/v1_parser.rb +190 -0
  95. data/lib/fluent/configurable.rb +200 -0
  96. data/lib/fluent/daemon.rb +15 -0
  97. data/lib/fluent/engine.rb +266 -0
  98. data/lib/fluent/env.rb +28 -0
  99. data/lib/fluent/error.rb +30 -0
  100. data/lib/fluent/event.rb +334 -0
  101. data/lib/fluent/event_router.rb +269 -0
  102. data/lib/fluent/filter.rb +21 -0
  103. data/lib/fluent/formatter.rb +23 -0
  104. data/lib/fluent/input.rb +21 -0
  105. data/lib/fluent/label.rb +46 -0
  106. data/lib/fluent/load.rb +35 -0
  107. data/lib/fluent/log.rb +546 -0
  108. data/lib/fluent/match.rb +178 -0
  109. data/lib/fluent/mixin.rb +31 -0
  110. data/lib/fluent/msgpack_factory.rb +62 -0
  111. data/lib/fluent/output.rb +29 -0
  112. data/lib/fluent/output_chain.rb +23 -0
  113. data/lib/fluent/parser.rb +23 -0
  114. data/lib/fluent/plugin.rb +183 -0
  115. data/lib/fluent/plugin/bare_output.rb +63 -0
  116. data/lib/fluent/plugin/base.rb +165 -0
  117. data/lib/fluent/plugin/buf_file.rb +184 -0
  118. data/lib/fluent/plugin/buf_memory.rb +34 -0
  119. data/lib/fluent/plugin/buffer.rb +617 -0
  120. data/lib/fluent/plugin/buffer/chunk.rb +221 -0
  121. data/lib/fluent/plugin/buffer/file_chunk.rb +364 -0
  122. data/lib/fluent/plugin/buffer/memory_chunk.rb +90 -0
  123. data/lib/fluent/plugin/compressable.rb +92 -0
  124. data/lib/fluent/plugin/exec_util.rb +22 -0
  125. data/lib/fluent/plugin/file_util.rb +22 -0
  126. data/lib/fluent/plugin/file_wrapper.rb +120 -0
  127. data/lib/fluent/plugin/filter.rb +93 -0
  128. data/lib/fluent/plugin/filter_grep.rb +75 -0
  129. data/lib/fluent/plugin/filter_parser.rb +119 -0
  130. data/lib/fluent/plugin/filter_record_transformer.rb +322 -0
  131. data/lib/fluent/plugin/filter_stdout.rb +53 -0
  132. data/lib/fluent/plugin/formatter.rb +50 -0
  133. data/lib/fluent/plugin/formatter_csv.rb +52 -0
  134. data/lib/fluent/plugin/formatter_hash.rb +33 -0
  135. data/lib/fluent/plugin/formatter_json.rb +55 -0
  136. data/lib/fluent/plugin/formatter_ltsv.rb +42 -0
  137. data/lib/fluent/plugin/formatter_msgpack.rb +33 -0
  138. data/lib/fluent/plugin/formatter_out_file.rb +51 -0
  139. data/lib/fluent/plugin/formatter_single_value.rb +34 -0
  140. data/lib/fluent/plugin/formatter_stdout.rb +75 -0
  141. data/lib/fluent/plugin/formatter_tsv.rb +34 -0
  142. data/lib/fluent/plugin/in_debug_agent.rb +64 -0
  143. data/lib/fluent/plugin/in_dummy.rb +139 -0
  144. data/lib/fluent/plugin/in_exec.rb +108 -0
  145. data/lib/fluent/plugin/in_forward.rb +455 -0
  146. data/lib/fluent/plugin/in_gc_stat.rb +56 -0
  147. data/lib/fluent/plugin/in_http.rb +433 -0
  148. data/lib/fluent/plugin/in_monitor_agent.rb +448 -0
  149. data/lib/fluent/plugin/in_object_space.rb +93 -0
  150. data/lib/fluent/plugin/in_syslog.rb +209 -0
  151. data/lib/fluent/plugin/in_tail.rb +905 -0
  152. data/lib/fluent/plugin/in_tcp.rb +85 -0
  153. data/lib/fluent/plugin/in_udp.rb +81 -0
  154. data/lib/fluent/plugin/in_unix.rb +201 -0
  155. data/lib/fluent/plugin/input.rb +37 -0
  156. data/lib/fluent/plugin/multi_output.rb +157 -0
  157. data/lib/fluent/plugin/out_copy.rb +46 -0
  158. data/lib/fluent/plugin/out_exec.rb +105 -0
  159. data/lib/fluent/plugin/out_exec_filter.rb +317 -0
  160. data/lib/fluent/plugin/out_file.rb +302 -0
  161. data/lib/fluent/plugin/out_forward.rb +912 -0
  162. data/lib/fluent/plugin/out_null.rb +74 -0
  163. data/lib/fluent/plugin/out_relabel.rb +32 -0
  164. data/lib/fluent/plugin/out_roundrobin.rb +84 -0
  165. data/lib/fluent/plugin/out_secondary_file.rb +133 -0
  166. data/lib/fluent/plugin/out_stdout.rb +75 -0
  167. data/lib/fluent/plugin/out_stream.rb +130 -0
  168. data/lib/fluent/plugin/output.rb +1291 -0
  169. data/lib/fluent/plugin/owned_by_mixin.rb +42 -0
  170. data/lib/fluent/plugin/parser.rb +191 -0
  171. data/lib/fluent/plugin/parser_apache.rb +28 -0
  172. data/lib/fluent/plugin/parser_apache2.rb +84 -0
  173. data/lib/fluent/plugin/parser_apache_error.rb +26 -0
  174. data/lib/fluent/plugin/parser_csv.rb +39 -0
  175. data/lib/fluent/plugin/parser_json.rb +81 -0
  176. data/lib/fluent/plugin/parser_ltsv.rb +42 -0
  177. data/lib/fluent/plugin/parser_msgpack.rb +50 -0
  178. data/lib/fluent/plugin/parser_multiline.rb +105 -0
  179. data/lib/fluent/plugin/parser_nginx.rb +28 -0
  180. data/lib/fluent/plugin/parser_none.rb +36 -0
  181. data/lib/fluent/plugin/parser_regexp.rb +63 -0
  182. data/lib/fluent/plugin/parser_syslog.rb +121 -0
  183. data/lib/fluent/plugin/parser_tsv.rb +42 -0
  184. data/lib/fluent/plugin/socket_util.rb +22 -0
  185. data/lib/fluent/plugin/storage.rb +84 -0
  186. data/lib/fluent/plugin/storage_local.rb +159 -0
  187. data/lib/fluent/plugin/string_util.rb +22 -0
  188. data/lib/fluent/plugin_helper.rb +70 -0
  189. data/lib/fluent/plugin_helper/cert_option.rb +159 -0
  190. data/lib/fluent/plugin_helper/child_process.rb +364 -0
  191. data/lib/fluent/plugin_helper/compat_parameters.rb +331 -0
  192. data/lib/fluent/plugin_helper/event_emitter.rb +93 -0
  193. data/lib/fluent/plugin_helper/event_loop.rb +161 -0
  194. data/lib/fluent/plugin_helper/extract.rb +104 -0
  195. data/lib/fluent/plugin_helper/formatter.rb +147 -0
  196. data/lib/fluent/plugin_helper/inject.rb +151 -0
  197. data/lib/fluent/plugin_helper/parser.rb +147 -0
  198. data/lib/fluent/plugin_helper/retry_state.rb +201 -0
  199. data/lib/fluent/plugin_helper/server.rb +738 -0
  200. data/lib/fluent/plugin_helper/socket.rb +241 -0
  201. data/lib/fluent/plugin_helper/socket_option.rb +69 -0
  202. data/lib/fluent/plugin_helper/storage.rb +349 -0
  203. data/lib/fluent/plugin_helper/thread.rb +179 -0
  204. data/lib/fluent/plugin_helper/timer.rb +91 -0
  205. data/lib/fluent/plugin_id.rb +80 -0
  206. data/lib/fluent/process.rb +22 -0
  207. data/lib/fluent/registry.rb +116 -0
  208. data/lib/fluent/root_agent.rb +323 -0
  209. data/lib/fluent/rpc.rb +94 -0
  210. data/lib/fluent/supervisor.rb +741 -0
  211. data/lib/fluent/system_config.rb +159 -0
  212. data/lib/fluent/test.rb +58 -0
  213. data/lib/fluent/test/base.rb +78 -0
  214. data/lib/fluent/test/driver/base.rb +224 -0
  215. data/lib/fluent/test/driver/base_owned.rb +70 -0
  216. data/lib/fluent/test/driver/base_owner.rb +135 -0
  217. data/lib/fluent/test/driver/event_feeder.rb +98 -0
  218. data/lib/fluent/test/driver/filter.rb +57 -0
  219. data/lib/fluent/test/driver/formatter.rb +30 -0
  220. data/lib/fluent/test/driver/input.rb +31 -0
  221. data/lib/fluent/test/driver/multi_output.rb +53 -0
  222. data/lib/fluent/test/driver/output.rb +102 -0
  223. data/lib/fluent/test/driver/parser.rb +30 -0
  224. data/lib/fluent/test/driver/test_event_router.rb +45 -0
  225. data/lib/fluent/test/filter_test.rb +77 -0
  226. data/lib/fluent/test/formatter_test.rb +65 -0
  227. data/lib/fluent/test/helpers.rb +134 -0
  228. data/lib/fluent/test/input_test.rb +174 -0
  229. data/lib/fluent/test/log.rb +79 -0
  230. data/lib/fluent/test/output_test.rb +156 -0
  231. data/lib/fluent/test/parser_test.rb +70 -0
  232. data/lib/fluent/test/startup_shutdown.rb +46 -0
  233. data/lib/fluent/time.rb +412 -0
  234. data/lib/fluent/timezone.rb +133 -0
  235. data/lib/fluent/unique_id.rb +39 -0
  236. data/lib/fluent/version.rb +21 -0
  237. data/lib/fluent/winsvc.rb +71 -0
  238. data/templates/new_gem/Gemfile +3 -0
  239. data/templates/new_gem/README.md.erb +43 -0
  240. data/templates/new_gem/Rakefile +13 -0
  241. data/templates/new_gem/fluent-plugin.gemspec.erb +27 -0
  242. data/templates/new_gem/lib/fluent/plugin/filter.rb.erb +14 -0
  243. data/templates/new_gem/lib/fluent/plugin/formatter.rb.erb +14 -0
  244. data/templates/new_gem/lib/fluent/plugin/input.rb.erb +11 -0
  245. data/templates/new_gem/lib/fluent/plugin/output.rb.erb +11 -0
  246. data/templates/new_gem/lib/fluent/plugin/parser.rb.erb +15 -0
  247. data/templates/new_gem/test/helper.rb.erb +8 -0
  248. data/templates/new_gem/test/plugin/test_filter.rb.erb +18 -0
  249. data/templates/new_gem/test/plugin/test_formatter.rb.erb +18 -0
  250. data/templates/new_gem/test/plugin/test_input.rb.erb +18 -0
  251. data/templates/new_gem/test/plugin/test_output.rb.erb +18 -0
  252. data/templates/new_gem/test/plugin/test_parser.rb.erb +18 -0
  253. data/templates/plugin_config_formatter/param.md-compact.erb +25 -0
  254. data/templates/plugin_config_formatter/param.md.erb +34 -0
  255. data/templates/plugin_config_formatter/section.md.erb +12 -0
  256. data/test/command/test_binlog_reader.rb +346 -0
  257. data/test/command/test_fluentd.rb +618 -0
  258. data/test/command/test_plugin_config_formatter.rb +275 -0
  259. data/test/command/test_plugin_generator.rb +66 -0
  260. data/test/compat/test_calls_super.rb +166 -0
  261. data/test/compat/test_parser.rb +92 -0
  262. data/test/config/assertions.rb +42 -0
  263. data/test/config/test_config_parser.rb +513 -0
  264. data/test/config/test_configurable.rb +1587 -0
  265. data/test/config/test_configure_proxy.rb +566 -0
  266. data/test/config/test_dsl.rb +415 -0
  267. data/test/config/test_element.rb +403 -0
  268. data/test/config/test_literal_parser.rb +297 -0
  269. data/test/config/test_section.rb +184 -0
  270. data/test/config/test_system_config.rb +168 -0
  271. data/test/config/test_types.rb +191 -0
  272. data/test/helper.rb +153 -0
  273. data/test/plugin/data/2010/01/20100102-030405.log +0 -0
  274. data/test/plugin/data/2010/01/20100102-030406.log +0 -0
  275. data/test/plugin/data/2010/01/20100102.log +0 -0
  276. data/test/plugin/data/log/bar +0 -0
  277. data/test/plugin/data/log/foo/bar.log +0 -0
  278. data/test/plugin/data/log/foo/bar2 +0 -0
  279. data/test/plugin/data/log/test.log +0 -0
  280. data/test/plugin/test_bare_output.rb +118 -0
  281. data/test/plugin/test_base.rb +115 -0
  282. data/test/plugin/test_buf_file.rb +843 -0
  283. data/test/plugin/test_buf_memory.rb +42 -0
  284. data/test/plugin/test_buffer.rb +1220 -0
  285. data/test/plugin/test_buffer_chunk.rb +198 -0
  286. data/test/plugin/test_buffer_file_chunk.rb +844 -0
  287. data/test/plugin/test_buffer_memory_chunk.rb +338 -0
  288. data/test/plugin/test_compressable.rb +84 -0
  289. data/test/plugin/test_file_util.rb +96 -0
  290. data/test/plugin/test_filter.rb +357 -0
  291. data/test/plugin/test_filter_grep.rb +119 -0
  292. data/test/plugin/test_filter_parser.rb +700 -0
  293. data/test/plugin/test_filter_record_transformer.rb +556 -0
  294. data/test/plugin/test_filter_stdout.rb +202 -0
  295. data/test/plugin/test_formatter_csv.rb +111 -0
  296. data/test/plugin/test_formatter_hash.rb +35 -0
  297. data/test/plugin/test_formatter_json.rb +51 -0
  298. data/test/plugin/test_formatter_ltsv.rb +59 -0
  299. data/test/plugin/test_formatter_msgpack.rb +28 -0
  300. data/test/plugin/test_formatter_out_file.rb +95 -0
  301. data/test/plugin/test_formatter_single_value.rb +38 -0
  302. data/test/plugin/test_in_debug_agent.rb +28 -0
  303. data/test/plugin/test_in_dummy.rb +192 -0
  304. data/test/plugin/test_in_exec.rb +245 -0
  305. data/test/plugin/test_in_forward.rb +1120 -0
  306. data/test/plugin/test_in_gc_stat.rb +39 -0
  307. data/test/plugin/test_in_http.rb +588 -0
  308. data/test/plugin/test_in_monitor_agent.rb +516 -0
  309. data/test/plugin/test_in_object_space.rb +64 -0
  310. data/test/plugin/test_in_syslog.rb +271 -0
  311. data/test/plugin/test_in_tail.rb +1216 -0
  312. data/test/plugin/test_in_tcp.rb +118 -0
  313. data/test/plugin/test_in_udp.rb +152 -0
  314. data/test/plugin/test_in_unix.rb +126 -0
  315. data/test/plugin/test_input.rb +126 -0
  316. data/test/plugin/test_multi_output.rb +180 -0
  317. data/test/plugin/test_out_copy.rb +160 -0
  318. data/test/plugin/test_out_exec.rb +310 -0
  319. data/test/plugin/test_out_exec_filter.rb +613 -0
  320. data/test/plugin/test_out_file.rb +873 -0
  321. data/test/plugin/test_out_forward.rb +685 -0
  322. data/test/plugin/test_out_null.rb +105 -0
  323. data/test/plugin/test_out_relabel.rb +28 -0
  324. data/test/plugin/test_out_roundrobin.rb +146 -0
  325. data/test/plugin/test_out_secondary_file.rb +442 -0
  326. data/test/plugin/test_out_stdout.rb +170 -0
  327. data/test/plugin/test_out_stream.rb +93 -0
  328. data/test/plugin/test_output.rb +870 -0
  329. data/test/plugin/test_output_as_buffered.rb +1932 -0
  330. data/test/plugin/test_output_as_buffered_compress.rb +165 -0
  331. data/test/plugin/test_output_as_buffered_overflow.rb +250 -0
  332. data/test/plugin/test_output_as_buffered_retries.rb +839 -0
  333. data/test/plugin/test_output_as_buffered_secondary.rb +877 -0
  334. data/test/plugin/test_output_as_standard.rb +374 -0
  335. data/test/plugin/test_owned_by.rb +35 -0
  336. data/test/plugin/test_parser.rb +359 -0
  337. data/test/plugin/test_parser_apache.rb +42 -0
  338. data/test/plugin/test_parser_apache2.rb +46 -0
  339. data/test/plugin/test_parser_apache_error.rb +45 -0
  340. data/test/plugin/test_parser_csv.rb +103 -0
  341. data/test/plugin/test_parser_json.rb +114 -0
  342. data/test/plugin/test_parser_labeled_tsv.rb +128 -0
  343. data/test/plugin/test_parser_multiline.rb +100 -0
  344. data/test/plugin/test_parser_nginx.rb +48 -0
  345. data/test/plugin/test_parser_none.rb +52 -0
  346. data/test/plugin/test_parser_regexp.rb +281 -0
  347. data/test/plugin/test_parser_syslog.rb +242 -0
  348. data/test/plugin/test_parser_tsv.rb +122 -0
  349. data/test/plugin/test_storage.rb +167 -0
  350. data/test/plugin/test_storage_local.rb +335 -0
  351. data/test/plugin/test_string_util.rb +26 -0
  352. data/test/plugin_helper/test_child_process.rb +794 -0
  353. data/test/plugin_helper/test_compat_parameters.rb +331 -0
  354. data/test/plugin_helper/test_event_emitter.rb +51 -0
  355. data/test/plugin_helper/test_event_loop.rb +52 -0
  356. data/test/plugin_helper/test_extract.rb +194 -0
  357. data/test/plugin_helper/test_formatter.rb +255 -0
  358. data/test/plugin_helper/test_inject.rb +519 -0
  359. data/test/plugin_helper/test_parser.rb +264 -0
  360. data/test/plugin_helper/test_retry_state.rb +422 -0
  361. data/test/plugin_helper/test_server.rb +1677 -0
  362. data/test/plugin_helper/test_storage.rb +542 -0
  363. data/test/plugin_helper/test_thread.rb +164 -0
  364. data/test/plugin_helper/test_timer.rb +132 -0
  365. data/test/scripts/exec_script.rb +32 -0
  366. data/test/scripts/fluent/plugin/formatter1/formatter_test1.rb +7 -0
  367. data/test/scripts/fluent/plugin/formatter2/formatter_test2.rb +7 -0
  368. data/test/scripts/fluent/plugin/formatter_known.rb +8 -0
  369. data/test/scripts/fluent/plugin/out_test.rb +81 -0
  370. data/test/scripts/fluent/plugin/out_test2.rb +80 -0
  371. data/test/scripts/fluent/plugin/parser_known.rb +4 -0
  372. data/test/test_clock.rb +164 -0
  373. data/test/test_config.rb +179 -0
  374. data/test/test_configdsl.rb +148 -0
  375. data/test/test_event.rb +515 -0
  376. data/test/test_event_router.rb +331 -0
  377. data/test/test_event_time.rb +186 -0
  378. data/test/test_filter.rb +121 -0
  379. data/test/test_formatter.rb +312 -0
  380. data/test/test_input.rb +31 -0
  381. data/test/test_log.rb +828 -0
  382. data/test/test_match.rb +137 -0
  383. data/test/test_mixin.rb +351 -0
  384. data/test/test_output.rb +273 -0
  385. data/test/test_plugin.rb +251 -0
  386. data/test/test_plugin_classes.rb +253 -0
  387. data/test/test_plugin_helper.rb +81 -0
  388. data/test/test_plugin_id.rb +101 -0
  389. data/test/test_process.rb +14 -0
  390. data/test/test_root_agent.rb +611 -0
  391. data/test/test_supervisor.rb +373 -0
  392. data/test/test_test_drivers.rb +135 -0
  393. data/test/test_time_formatter.rb +282 -0
  394. data/test/test_time_parser.rb +211 -0
  395. data/test/test_unique_id.rb +47 -0
  396. metadata +898 -0
@@ -0,0 +1,302 @@
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 'fileutils'
18
+ require 'zlib'
19
+ require 'time'
20
+
21
+ require 'fluent/plugin/output'
22
+ require 'fluent/config/error'
23
+ # TODO remove ...
24
+ require 'fluent/plugin/file_util'
25
+
26
+ module Fluent::Plugin
27
+ class FileOutput < Output
28
+ Fluent::Plugin.register_output('file', self)
29
+
30
+ helpers :formatter, :inject, :compat_parameters
31
+
32
+ SUPPORTED_COMPRESS = [:text, :gz, :gzip]
33
+ SUPPORTED_COMPRESS_MAP = {
34
+ text: nil,
35
+ gz: :gzip,
36
+ gzip: :gzip,
37
+ }
38
+
39
+ FILE_PERMISSION = 0644
40
+ DIR_PERMISSION = 0755
41
+
42
+ DEFAULT_TIMEKEY = 60 * 60 * 24
43
+
44
+ desc "The Path of the file."
45
+ config_param :path, :string
46
+
47
+ desc "Specify to add file suffix for bare file path or not."
48
+ config_param :add_path_suffix, :bool, default: true
49
+ desc "The file suffix added to bare file path."
50
+ config_param :path_suffix, :string, default: '.log'
51
+ desc "The flushed chunk is appended to existence file or not."
52
+ config_param :append, :bool, default: false
53
+ desc "Compress flushed file."
54
+ config_param :compress, :enum, list: SUPPORTED_COMPRESS, default: :text
55
+ desc "Execute compression again even when buffer chunk is already compressed."
56
+ config_param :recompress, :bool, default: false
57
+ desc "Create symlink to temporary buffered file when buffer_type is file (disabled on Windows)."
58
+ config_param :symlink_path, :string, default: nil
59
+
60
+ config_section :format do
61
+ config_set_default :@type, 'out_file'
62
+ end
63
+
64
+ config_section :buffer do
65
+ config_set_default :@type, 'file'
66
+ config_set_default :chunk_keys, ['time']
67
+ config_set_default :timekey, DEFAULT_TIMEKEY
68
+ end
69
+
70
+ attr_accessor :last_written_path # for tests
71
+
72
+ module SymlinkBufferMixin
73
+ def symlink_path=(path)
74
+ @_symlink_path = path
75
+ end
76
+
77
+ def generate_chunk(metadata)
78
+ chunk = super
79
+ # "symlink" feature is to link from symlink_path to the latest file chunk. Records with latest
80
+ # timekey will be appended into that file chunk. On the other side, resumed file chunks might NOT
81
+ # have timekey, especially in the cases that resumed file chunks are generated by Fluentd v0.12.
82
+ # These chunks will be enqueued immediately, and will be flushed soon.
83
+ latest_metadata = metadata_list.select{|m| m.timekey }.sort_by(&:timekey).last
84
+ if chunk.metadata == latest_metadata
85
+ FileUtils.ln_sf(chunk.path, @_symlink_path)
86
+ end
87
+ chunk
88
+ end
89
+ end
90
+
91
+ def configure(conf)
92
+ compat_parameters_convert(conf, :formatter, :buffer, :inject, default_chunk_key: "time")
93
+
94
+ configured_time_slice_format = conf['time_slice_format']
95
+
96
+ if conf.elements(name: 'buffer').empty?
97
+ conf.add_element('buffer', 'time')
98
+ end
99
+ buffer_conf = conf.elements(name: 'buffer').first
100
+ # Fluent::PluginId#configure is not called yet, so we can't use #plugin_root_dir here.
101
+ if !buffer_conf.has_key?('path') && !(conf['@id'] && system_config.root_dir)
102
+ # v0.14 file buffer handles path as directory if '*' is missing
103
+ # 'dummy_path' is not to raise configuration error for 'path' in file buffer plugin,
104
+ # but raise it in this plugin.
105
+ buffer_conf['path'] = conf['path'] || '/tmp/dummy_path'
106
+ end
107
+
108
+ super
109
+
110
+ @compress_method = SUPPORTED_COMPRESS_MAP[@compress]
111
+
112
+ if @path.include?('*') && !@buffer_config.timekey
113
+ raise Fluent::ConfigError, "path including '*' must be used with buffer chunk key 'time'"
114
+ end
115
+
116
+ path_suffix = @add_path_suffix ? @path_suffix : ''
117
+ path_timekey = if @chunk_key_time
118
+ @as_secondary ? @primary_instance.buffer_config.timekey : @buffer_config.timekey
119
+ else
120
+ nil
121
+ end
122
+ @path_template = generate_path_template(@path, path_timekey, @append, @compress_method, path_suffix: path_suffix, time_slice_format: configured_time_slice_format)
123
+
124
+ if @as_secondary
125
+ # When this plugin is configured as secondary & primary plugin has tag key, but this plugin may not have it.
126
+ # Increment placeholder can make another output file per chunk tag/keys even if original path doesn't include it.
127
+ placeholder_validators(:path, @path_template).select{|v| v.type == :time }.each do |v|
128
+ v.validate!
129
+ end
130
+ else
131
+ placeholder_validate!(:path, @path_template)
132
+
133
+ max_tag_index = get_placeholders_tag(@path_template).max || 1
134
+ max_tag_index = 1 if max_tag_index < 1
135
+ dummy_tag = (['a'] * max_tag_index).join('.')
136
+ dummy_record_keys = get_placeholders_keys(@path_template) || ['message']
137
+ dummy_record = Hash[dummy_record_keys.zip(['data'] * dummy_record_keys.size)]
138
+
139
+ test_meta1 = metadata_for_test(dummy_tag, Fluent::Engine.now, dummy_record)
140
+ test_path = extract_placeholders(@path_template, test_meta1)
141
+ unless ::Fluent::FileUtil.writable_p?(test_path)
142
+ raise Fluent::ConfigError, "out_file: `#{test_path}` is not writable"
143
+ end
144
+ end
145
+
146
+ @formatter = formatter_create
147
+
148
+ if @symlink_path && @buffer.respond_to?(:path)
149
+ if @as_secondary
150
+ raise Fluent::ConfigError, "symlink_path option is unavailable in <secondary>: consider to use secondary_file plugin"
151
+ end
152
+ if Fluent.windows?
153
+ log.warn "symlink_path is unavailable on Windows platform. disabled."
154
+ @symlink_path = nil
155
+ else
156
+ @buffer.extend SymlinkBufferMixin
157
+ @buffer.symlink_path = @symlink_path
158
+ end
159
+ end
160
+
161
+ @dir_perm = system_config.dir_permission || DIR_PERMISSION
162
+ @file_perm = system_config.file_permission || FILE_PERMISSION
163
+ @need_lock = system_config.workers > 1
164
+ end
165
+
166
+ def multi_workers_ready?
167
+ true
168
+ end
169
+
170
+ def format(tag, time, record)
171
+ r = inject_values_to_record(tag, time, record)
172
+ @formatter.format(tag, time, r)
173
+ end
174
+
175
+ def write(chunk)
176
+ path = extract_placeholders(@path_template, chunk.metadata)
177
+ FileUtils.mkdir_p File.dirname(path), mode: @dir_perm
178
+
179
+ writer = case
180
+ when @compress_method.nil?
181
+ method(:write_without_compression)
182
+ when @compress_method == :gzip
183
+ if @buffer.compress != :gzip || @recompress
184
+ method(:write_gzip_with_compression)
185
+ else
186
+ method(:write_gzip_from_gzipped_chunk)
187
+ end
188
+ else
189
+ raise "BUG: unknown compression method #{@compress_method}"
190
+ end
191
+
192
+ if @append
193
+ writer.call(path, chunk)
194
+ else
195
+ find_filepath_available(path, with_lock: @need_lock) do |actual_path|
196
+ writer.call(actual_path, chunk)
197
+ path = actual_path
198
+ end
199
+ end
200
+
201
+ @last_written_path = path
202
+ end
203
+
204
+ def write_without_compression(path, chunk)
205
+ File.open(path, "ab", @file_perm) do |f|
206
+ chunk.write_to(f)
207
+ end
208
+ end
209
+
210
+ def write_gzip_with_compression(path, chunk)
211
+ File.open(path, "ab", @file_perm) do |f|
212
+ gz = Zlib::GzipWriter.new(f)
213
+ chunk.write_to(gz, compressed: :text)
214
+ gz.close
215
+ end
216
+ end
217
+
218
+ def write_gzip_from_gzipped_chunk(path, chunk)
219
+ File.open(path, "ab", @file_perm) do |f|
220
+ chunk.write_to(f, compressed: :gzip)
221
+ end
222
+ end
223
+
224
+ def timekey_to_timeformat(timekey)
225
+ case timekey
226
+ when nil then ''
227
+ when 0...60 then '%Y%m%d%H%M%S' # 60 exclusive
228
+ when 60...3600 then '%Y%m%d%H%M'
229
+ when 3600...86400 then '%Y%m%d%H'
230
+ else '%Y%m%d'
231
+ end
232
+ end
233
+
234
+ def compression_suffix(compress)
235
+ case compress
236
+ when :gzip then '.gz'
237
+ when nil then ''
238
+ else
239
+ raise ArgumentError, "unknown compression type #{compress}"
240
+ end
241
+ end
242
+
243
+ # /path/to/dir/file.* -> /path/to/dir/file.%Y%m%d
244
+ # /path/to/dir/file.*.data -> /path/to/dir/file.%Y%m%d.data
245
+ # /path/to/dir/file -> /path/to/dir/file.%Y%m%d.log
246
+ # %Y%m%d -> %Y%m%d_** (non append)
247
+ # + .gz (gzipped)
248
+ ## TODO: remove time_slice_format when end of support of compat_parameters
249
+ def generate_path_template(original, timekey, append, compress, path_suffix: '', time_slice_format: nil)
250
+ comp_suffix = compression_suffix(compress)
251
+ index_placeholder = append ? '' : '_**'
252
+ if original.index('*')
253
+ raise "BUG: configuration error must be raised for path including '*' without timekey" unless timekey
254
+ time_placeholders_part = time_slice_format || timekey_to_timeformat(timekey)
255
+ original.gsub('*', time_placeholders_part + index_placeholder) + comp_suffix
256
+ else
257
+ if timekey
258
+ if time_slice_format
259
+ "#{original}.#{time_slice_format}#{index_placeholder}#{path_suffix}#{comp_suffix}"
260
+ else
261
+ time_placeholders = timekey_to_timeformat(timekey)
262
+ if time_placeholders.scan(/../).any?{|ph| original.include?(ph) }
263
+ raise Fluent::ConfigError, "insufficient timestamp placeholders in path" if time_placeholders.scan(/../).any?{|ph| !original.include?(ph) }
264
+ "#{original}#{index_placeholder}#{path_suffix}#{comp_suffix}"
265
+ else
266
+ "#{original}.#{time_placeholders}#{index_placeholder}#{path_suffix}#{comp_suffix}"
267
+ end
268
+ end
269
+ else
270
+ "#{original}#{index_placeholder}#{path_suffix}#{comp_suffix}"
271
+ end
272
+ end
273
+ end
274
+
275
+ def find_filepath_available(path_with_placeholder, with_lock: false) # for non-append
276
+ raise "BUG: index placeholder not found in path: #{path_with_placeholder}" unless path_with_placeholder.index('_**')
277
+ i = 0
278
+ dir_path = locked = nil
279
+ while true
280
+ path = path_with_placeholder.sub('_**', "_#{i}")
281
+ i += 1
282
+ next if File.exist?(path)
283
+
284
+ if with_lock
285
+ dir_path = path + '.lock'
286
+ locked = Dir.mkdir(dir_path) rescue false
287
+ next unless locked
288
+ # ensure that other worker doesn't create a file (and release lock)
289
+ # between previous File.exist? and Dir.mkdir
290
+ next if File.exist?(path)
291
+ end
292
+
293
+ break
294
+ end
295
+ yield path
296
+ ensure
297
+ if dir_path && locked && Dir.exist?(dir_path)
298
+ Dir.rmdir(dir_path) rescue nil
299
+ end
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,912 @@
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/output'
18
+ require 'fluent/config/error'
19
+ require 'fluent/clock'
20
+ require 'base64'
21
+
22
+ require 'fluent/compat/socket_util'
23
+
24
+ module Fluent::Plugin
25
+ class ForwardOutput < Output
26
+ class Error < StandardError; end
27
+ class NoNodesAvailable < Error; end
28
+ class ConnectionClosedError < Error; end
29
+
30
+ Fluent::Plugin.register_output('forward', self)
31
+
32
+ helpers :socket, :server, :timer, :thread, :compat_parameters
33
+
34
+ LISTEN_PORT = 24224
35
+
36
+ desc 'The transport protocol.'
37
+ config_param :transport, :enum, list: [:tcp, :tls], default: :tcp
38
+ # TODO: TLS session cache/tickets
39
+ # TODO: Connection keepalive
40
+
41
+ desc 'The timeout time when sending event logs.'
42
+ config_param :send_timeout, :time, default: 60
43
+ # TODO: add linger_timeout, recv_timeout
44
+
45
+ desc 'The protocol to use for heartbeats (default is the same with "transport").'
46
+ config_param :heartbeat_type, :enum, list: [:transport, :tcp, :udp, :none], default: :transport
47
+ desc 'The interval of the heartbeat packer.'
48
+ config_param :heartbeat_interval, :time, default: 1
49
+ desc 'The wait time before accepting a server fault recovery.'
50
+ config_param :recover_wait, :time, default: 10
51
+ desc 'The hard timeout used to detect server failure.'
52
+ config_param :hard_timeout, :time, default: 60
53
+ desc 'The threshold parameter used to detect server faults.'
54
+ config_param :phi_threshold, :integer, default: 16
55
+ desc 'Use the "Phi accrual failure detector" to detect server failure.'
56
+ config_param :phi_failure_detector, :bool, default: true
57
+
58
+ desc 'Change the protocol to at-least-once.'
59
+ config_param :require_ack_response, :bool, default: false # require in_forward to respond with ack
60
+
61
+ ## The reason of default value of :ack_response_timeout:
62
+ # Linux default tcp_syn_retries is 5 (in many environment)
63
+ # 3 + 6 + 12 + 24 + 48 + 96 -> 189 (sec)
64
+ desc 'This option is used when require_ack_response is true.'
65
+ config_param :ack_response_timeout, :time, default: 190
66
+
67
+ desc 'The interval while reading data from server'
68
+ config_param :read_interval_msec, :integer, default: 50 # 50ms
69
+ desc 'Reading data size from server'
70
+ config_param :read_length, :size, default: 512 # 512bytes
71
+
72
+ desc 'Set TTL to expire DNS cache in seconds.'
73
+ config_param :expire_dns_cache, :time, default: nil # 0 means disable cache
74
+ desc 'Enable client-side DNS round robin.'
75
+ config_param :dns_round_robin, :bool, default: false # heartbeat_type 'udp' is not available for this
76
+
77
+ desc 'Ignore DNS resolution and errors at startup time.'
78
+ config_param :ignore_network_errors_at_startup, :bool, default: false
79
+
80
+ desc 'Compress buffered data.'
81
+ config_param :compress, :enum, list: [:text, :gzip], default: :text
82
+
83
+ desc 'The default version of TLS transport.'
84
+ config_param :tls_version, :enum, list: Fluent::PluginHelper::Socket::TLS_SUPPORTED_VERSIONS, default: Fluent::PluginHelper::Socket::TLS_DEFAULT_VERSION
85
+ desc 'The cipher configuration of TLS transport.'
86
+ config_param :tls_ciphers, :string, default: Fluent::PluginHelper::Socket::CIPHERS_DEFAULT
87
+ desc 'Skip all verification of certificates or not.'
88
+ config_param :tls_insecure_mode, :bool, default: false
89
+ desc 'Allow self signed certificates or not.'
90
+ config_param :tls_allow_self_signed_cert, :bool, default: false
91
+ desc 'Verify hostname of servers and certificates or not in TLS transport.'
92
+ config_param :tls_verify_hostname, :bool, default: true
93
+ desc 'The additional CA certificate path for TLS.'
94
+ config_param :tls_cert_path, :array, value_type: :string, default: nil
95
+
96
+ config_section :security, required: false, multi: false do
97
+ desc 'The hostname'
98
+ config_param :self_hostname, :string
99
+ desc 'Shared key for authentication'
100
+ config_param :shared_key, :string, secret: true
101
+ end
102
+
103
+ config_section :server, param_name: :servers do
104
+ desc "The IP address or host name of the server."
105
+ config_param :host, :string
106
+ desc "The name of the server. Used for logging and certificate verification in TLS transport (when host is address)."
107
+ config_param :name, :string, default: nil
108
+ desc "The port number of the host."
109
+ config_param :port, :integer, default: LISTEN_PORT
110
+ desc "The shared key per server."
111
+ config_param :shared_key, :string, default: nil, secret: true
112
+ desc "The username for authentication."
113
+ config_param :username, :string, default: ''
114
+ desc "The password for authentication."
115
+ config_param :password, :string, default: '', secret: true
116
+ desc "Marks a node as the standby node for an Active-Standby model between Fluentd nodes."
117
+ config_param :standby, :bool, default: false
118
+ desc "The load balancing weight."
119
+ config_param :weight, :integer, default: 60
120
+ end
121
+
122
+ attr_reader :nodes
123
+
124
+ config_param :port, :integer, default: LISTEN_PORT, obsoleted: "User <server> section instead."
125
+ config_param :host, :string, default: nil, obsoleted: "Use <server> section instead."
126
+
127
+ config_section :buffer do
128
+ config_set_default :chunk_keys, ["tag"]
129
+ end
130
+
131
+ attr_reader :read_interval, :recover_sample_size
132
+
133
+ def initialize
134
+ super
135
+
136
+ @nodes = [] #=> [Node]
137
+ @loop = nil
138
+ @thread = nil
139
+
140
+ @usock = nil
141
+ @sock_ack_waiting = nil
142
+ @sock_ack_waiting_mutex = nil
143
+ end
144
+
145
+ def configure(conf)
146
+ compat_parameters_convert(conf, :buffer, default_chunk_key: 'tag')
147
+
148
+ super
149
+
150
+ unless @chunk_key_tag
151
+ raise Fluent::ConfigError, "buffer chunk key must include 'tag' for forward output"
152
+ end
153
+
154
+ @read_interval = @read_interval_msec / 1000.0
155
+ @recover_sample_size = @recover_wait / @heartbeat_interval
156
+
157
+ if @heartbeat_type == :tcp
158
+ log.warn "'heartbeat_type tcp' is deprecated. use 'transport' instead."
159
+ @heartbeat_type = :transport
160
+ end
161
+
162
+ if @dns_round_robin
163
+ if @heartbeat_type == :udp
164
+ raise Fluent::ConfigError, "forward output heartbeat type must be 'transport' or 'none' to use dns_round_robin option"
165
+ end
166
+ end
167
+
168
+ if @transport == :tls
169
+ if @tls_cert_path && !@tls_cert_path.empty?
170
+ @tls_cert_path.each do |path|
171
+ raise Fluent::ConfigError, "specified cert path does not exist:#{path}" unless File.exist?(path)
172
+ raise Fluent::ConfigError, "specified cert path is not readable:#{path}" unless File.readable?(path)
173
+ end
174
+ end
175
+
176
+ if @tls_insecure_mode
177
+ log.warn "TLS transport is configured in insecure way"
178
+ @tls_verify_hostname = false
179
+ @tls_allow_self_signed_cert = true
180
+ end
181
+ end
182
+
183
+ @servers.each do |server|
184
+ failure = FailureDetector.new(@heartbeat_interval, @hard_timeout, Time.now.to_i.to_f)
185
+ name = server.name || "#{server.host}:#{server.port}"
186
+
187
+ log.info "adding forwarding server '#{name}'", host: server.host, port: server.port, weight: server.weight, plugin_id: plugin_id
188
+ if @heartbeat_type == :none
189
+ @nodes << NoneHeartbeatNode.new(self, server, failure: failure)
190
+ else
191
+ node = Node.new(self, server, failure: failure)
192
+ begin
193
+ node.validate_host_resolution!
194
+ rescue => e
195
+ raise unless @ignore_network_errors_at_startup
196
+ log.warn "failed to resolve node name when configured", server: (server.name || server.host), error: e
197
+ node.disable!
198
+ end
199
+ @nodes << node
200
+ end
201
+ end
202
+
203
+ unless @as_secondary
204
+ if @compress == :gzip && @buffer.compress == :text
205
+ @buffer.compress = :gzip
206
+ elsif @compress == :text && @buffer.compress == :gzip
207
+ log.info "buffer is compressed. If you also want to save the bandwidth of a network, Add `compress` configuration in <match>"
208
+ end
209
+ end
210
+
211
+ if @nodes.empty?
212
+ raise Fluent::ConfigError, "forward output plugin requires at least one <server> is required"
213
+ end
214
+
215
+ raise Fluent::ConfigError, "ack_response_timeout must be a positive integer" if @ack_response_timeout < 1
216
+ end
217
+
218
+ def multi_workers_ready?
219
+ true
220
+ end
221
+
222
+ def prefer_delayed_commit
223
+ @require_ack_response
224
+ end
225
+
226
+ def start
227
+ super
228
+
229
+ # Output#start sets @delayed_commit_timeout by @buffer_config.delayed_commit_timeout
230
+ # But it should be overwritten by ack_response_timeout to rollback chunks after timeout
231
+ if @ack_response_timeout && @delayed_commit_timeout != @ack_response_timeout
232
+ log.info "delayed_commit_timeout is overwritten by ack_response_timeout"
233
+ @delayed_commit_timeout = @ack_response_timeout + 2 # minimum ack_reader IO.select interval is 1s
234
+ end
235
+
236
+ @rand_seed = Random.new.seed
237
+ rebuild_weight_array
238
+ @rr = 0
239
+
240
+ unless @heartbeat_type == :none
241
+ if @heartbeat_type == :udp
242
+ @usock = socket_create_udp(@nodes.first.host, @nodes.first.port, nonblock: true)
243
+ server_create_udp(:out_forward_heartbeat_receiver, 0, socket: @usock, max_bytes: @read_length) do |data, sock|
244
+ sockaddr = Socket.pack_sockaddr_in(sock.remote_port, sock.remote_host)
245
+ on_heartbeat(sockaddr, data)
246
+ end
247
+ end
248
+ timer_execute(:out_forward_heartbeat_request, @heartbeat_interval, &method(:on_timer))
249
+ end
250
+
251
+ if @require_ack_response
252
+ @sock_ack_waiting_mutex = Mutex.new
253
+ @sock_ack_waiting = []
254
+ thread_create(:out_forward_receiving_ack, &method(:ack_reader))
255
+ end
256
+ end
257
+
258
+ def close
259
+ if @usock
260
+ # close socket and ignore errors: this socket will not be used anyway.
261
+ @usock.close rescue nil
262
+ end
263
+ super
264
+ end
265
+
266
+ def write(chunk)
267
+ return if chunk.empty?
268
+ tag = chunk.metadata.tag
269
+ select_a_healthy_node{|node| node.send_data(tag, chunk) }
270
+ end
271
+
272
+ ACKWaitingSockInfo = Struct.new(:sock, :chunk_id, :chunk_id_base64, :node, :time, :timeout) do
273
+ def expired?(now)
274
+ time + timeout < now
275
+ end
276
+ end
277
+
278
+ def try_write(chunk)
279
+ log.trace "writing a chunk to destination", chunk_id: dump_unique_id_hex(chunk.unique_id)
280
+ if chunk.empty?
281
+ commit_write(chunk.unique_id)
282
+ return
283
+ end
284
+ tag = chunk.metadata.tag
285
+ sock, node = select_a_healthy_node{|n| n.send_data(tag, chunk) }
286
+ chunk_id_base64 = Base64.encode64(chunk.unique_id)
287
+ current_time = Fluent::Clock.now
288
+ info = ACKWaitingSockInfo.new(sock, chunk.unique_id, chunk_id_base64, node, current_time, @ack_response_timeout)
289
+ @sock_ack_waiting_mutex.synchronize do
290
+ @sock_ack_waiting << info
291
+ end
292
+ end
293
+
294
+ def select_a_healthy_node
295
+ error = nil
296
+
297
+ wlen = @weight_array.length
298
+ wlen.times do
299
+ @rr = (@rr + 1) % wlen
300
+ node = @weight_array[@rr]
301
+ next unless node.available?
302
+
303
+ begin
304
+ ret = yield node
305
+ return ret, node
306
+ rescue
307
+ # for load balancing during detecting crashed servers
308
+ error = $! # use the latest error
309
+ end
310
+ end
311
+
312
+ raise error if error
313
+ raise NoNodesAvailable, "no nodes are available"
314
+ end
315
+
316
+ def create_transfer_socket(host, port, hostname, &block)
317
+ case @transport
318
+ when :tls
319
+ socket_create_tls(
320
+ host, port,
321
+ version: @tls_version,
322
+ ciphers: @tls_ciphers,
323
+ insecure: @tls_insecure_mode,
324
+ verify_fqdn: @tls_verify_hostname,
325
+ fqdn: hostname,
326
+ allow_self_signed_cert: @tls_allow_self_signed_cert,
327
+ cert_paths: @tls_cert_path,
328
+ linger_timeout: @send_timeout,
329
+ send_timeout: @send_timeout,
330
+ recv_timeout: @ack_response_timeout,
331
+ &block
332
+ )
333
+ when :tcp
334
+ socket_create_tcp(
335
+ host, port,
336
+ linger_timeout: @send_timeout,
337
+ send_timeout: @send_timeout,
338
+ recv_timeout: @ack_response_timeout,
339
+ &block
340
+ )
341
+ else
342
+ raise "BUG: unknown transport protocol #{@transport}"
343
+ end
344
+ end
345
+
346
+ # MessagePack FixArray length is 3
347
+ FORWARD_HEADER = [0x93].pack('C').freeze
348
+ def forward_header
349
+ FORWARD_HEADER
350
+ end
351
+
352
+ private
353
+
354
+ def rebuild_weight_array
355
+ standby_nodes, regular_nodes = @nodes.partition {|n|
356
+ n.standby?
357
+ }
358
+
359
+ lost_weight = 0
360
+ regular_nodes.each {|n|
361
+ unless n.available?
362
+ lost_weight += n.weight
363
+ end
364
+ }
365
+ log.debug "rebuilding weight array", lost_weight: lost_weight
366
+
367
+ if lost_weight > 0
368
+ standby_nodes.each {|n|
369
+ if n.available?
370
+ regular_nodes << n
371
+ log.warn "using standby node #{n.host}:#{n.port}", weight: n.weight
372
+ lost_weight -= n.weight
373
+ break if lost_weight <= 0
374
+ end
375
+ }
376
+ end
377
+
378
+ weight_array = []
379
+ gcd = regular_nodes.map {|n| n.weight }.inject(0) {|r,w| r.gcd(w) }
380
+ regular_nodes.each {|n|
381
+ (n.weight / gcd).times {
382
+ weight_array << n
383
+ }
384
+ }
385
+
386
+ # for load balancing during detecting crashed servers
387
+ coe = (regular_nodes.size * 6) / weight_array.size
388
+ weight_array *= coe if coe > 1
389
+
390
+ r = Random.new(@rand_seed)
391
+ weight_array.sort_by! { r.rand }
392
+
393
+ @weight_array = weight_array
394
+ end
395
+
396
+ def on_timer
397
+ @nodes.each {|n|
398
+ if n.tick
399
+ rebuild_weight_array
400
+ end
401
+ begin
402
+ log.trace "sending heartbeat", host: n.host, port: n.port, heartbeat_type: @heartbeat_type
403
+ n.usock = @usock if @usock
404
+ n.send_heartbeat
405
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR, Errno::ECONNREFUSED
406
+ log.debug "failed to send heartbeat packet", host: n.host, port: n.port, heartbeat_type: @heartbeat_type, error: $!
407
+ end
408
+ }
409
+ end
410
+
411
+ def on_heartbeat(sockaddr, msg)
412
+ if node = @nodes.find {|n| n.sockaddr == sockaddr }
413
+ # log.trace "heartbeat arrived", name: node.name, host: node.host, port: node.port
414
+ if node.heartbeat
415
+ rebuild_weight_array
416
+ end
417
+ end
418
+ end
419
+
420
+ # return chunk id to be committed
421
+ def read_ack_from_sock(sock, unpacker)
422
+ begin
423
+ raw_data = sock.recv(@read_length)
424
+ rescue Errno::ECONNRESET
425
+ raw_data = ""
426
+ end
427
+ info = @sock_ack_waiting_mutex.synchronize{ @sock_ack_waiting.find{|i| i.sock == sock } }
428
+
429
+ # When connection is closed by remote host, socket is ready to read and #recv returns an empty string that means EOF.
430
+ # If this happens we assume the data wasn't delivered and retry it.
431
+ if raw_data.empty?
432
+ log.warn "destination node closed the connection. regard it as unavailable.", host: info.node.host, port: info.node.port
433
+ info.node.disable!
434
+ return nil
435
+ else
436
+ unpacker.feed(raw_data)
437
+ res = unpacker.read
438
+ log.trace "getting response from destination", host: info.node.host, port: info.node.port, chunk_id: dump_unique_id_hex(info.chunk_id), response: res
439
+ if res['ack'] != info.chunk_id_base64
440
+ # Some errors may have occured when ack and chunk id is different, so send the chunk again.
441
+ log.warn "ack in response and chunk id in sent data are different", chunk_id: dump_unique_id_hex(info.chunk_id), ack: res['ack']
442
+ rollback_write(info.chunk_id)
443
+ return nil
444
+ else
445
+ log.trace "got a correct ack response", chunk_id: dump_unique_id_hex(info.chunk_id)
446
+ end
447
+ return info.chunk_id
448
+ end
449
+ rescue => e
450
+ log.error "unexpected error while receiving ack message", error: e
451
+ log.error_backtrace
452
+ ensure
453
+ @sock_ack_waiting_mutex.synchronize do
454
+ @sock_ack_waiting.delete(info)
455
+ end
456
+ end
457
+
458
+ def ack_reader
459
+ select_interval = if @delayed_commit_timeout > 3
460
+ 1
461
+ else
462
+ @delayed_commit_timeout / 3.0
463
+ end
464
+
465
+ unpacker = Fluent::Engine.msgpack_unpacker
466
+
467
+ while thread_current_running?
468
+ now = Fluent::Clock.now
469
+ sockets = []
470
+ begin
471
+ @sock_ack_waiting_mutex.synchronize do
472
+ new_list = []
473
+ @sock_ack_waiting.each do |info|
474
+ if info.expired?(now)
475
+ # There are 2 types of cases when no response has been received from socket:
476
+ # (1) the node does not support sending responses
477
+ # (2) the node does support sending response but responses have not arrived for some reasons.
478
+ log.warn "no response from node. regard it as unavailable.", host: info.node.host, port: info.node.port
479
+ info.node.disable!
480
+ info.sock.close rescue nil
481
+ rollback_write(info.chunk_id)
482
+ else
483
+ sockets << info.sock
484
+ new_list << info
485
+ end
486
+ end
487
+ @sock_ack_waiting = new_list
488
+ end
489
+
490
+ readable_sockets, _, _ = IO.select(sockets, nil, nil, select_interval)
491
+ next unless readable_sockets
492
+
493
+ readable_sockets.each do |sock|
494
+ chunk_id = read_ack_from_sock(sock, unpacker)
495
+ commit_write(chunk_id)
496
+ end
497
+ rescue => e
498
+ log.error "unexpected error while receiving ack", error: e
499
+ log.error_backtrace
500
+ end
501
+ end
502
+ end
503
+
504
+ class Node
505
+ def initialize(sender, server, failure:)
506
+ @sender = sender
507
+ @log = sender.log
508
+ @compress = sender.compress
509
+
510
+ @name = server.name
511
+ @host = server.host
512
+ @port = server.port
513
+ @weight = server.weight
514
+ @standby = server.standby
515
+ @failure = failure
516
+ @available = true
517
+ @state = nil
518
+
519
+ # @hostname is used for certificate verification & TLS SNI
520
+ host_is_hostname = !(IPAddr.new(@host) rescue false)
521
+ @hostname = case
522
+ when host_is_hostname then @host
523
+ when @name then @name
524
+ else nil
525
+ end
526
+
527
+ @usock = nil
528
+
529
+ @username = server.username
530
+ @password = server.password
531
+ @shared_key = server.shared_key || (sender.security && sender.security.shared_key) || ""
532
+ @shared_key_salt = generate_salt
533
+ @shared_key_nonce = ""
534
+
535
+ @unpacker = Fluent::Engine.msgpack_unpacker
536
+
537
+ @resolved_host = nil
538
+ @resolved_time = 0
539
+ @resolved_once = false
540
+ end
541
+
542
+ attr_accessor :usock
543
+
544
+ attr_reader :name, :host, :port, :weight, :standby, :state
545
+ attr_reader :sockaddr # used by on_heartbeat
546
+ attr_reader :failure, :available # for test
547
+
548
+ def validate_host_resolution!
549
+ resolved_host
550
+ end
551
+
552
+ def available?
553
+ @available
554
+ end
555
+
556
+ def disable!
557
+ @available = false
558
+ end
559
+
560
+ def standby?
561
+ @standby
562
+ end
563
+
564
+ def establish_connection(sock)
565
+ while available? && @state != :established
566
+ begin
567
+ # TODO: On Ruby 2.2 or earlier, read_nonblock doesn't work expectedly.
568
+ # We need rewrite around here using new socket/server plugin helper.
569
+ buf = sock.read_nonblock(@sender.read_length)
570
+ if buf.empty?
571
+ sleep @sender.read_interval
572
+ next
573
+ end
574
+ @unpacker.feed_each(buf) do |data|
575
+ on_read(sock, data)
576
+ end
577
+ rescue IO::WaitReadable
578
+ # If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN, it is extended by IO::WaitReadable.
579
+ # So IO::WaitReadable can be used to rescue the exceptions for retrying read_nonblock.
580
+ # http://docs.ruby-lang.org/en/2.3.0/IO.html#method-i-read_nonblock
581
+ sleep @sender.read_interval unless @state == :established
582
+ rescue SystemCallError => e
583
+ @log.warn "disconnected by error", host: @host, port: @port, error: e
584
+ disable!
585
+ break
586
+ rescue EOFError
587
+ @log.warn "disconnected", host: @host, port: @port
588
+ disable!
589
+ break
590
+ end
591
+ end
592
+ end
593
+
594
+ def send_data_actual(sock, tag, chunk)
595
+ @state = @sender.security ? :helo : :established
596
+ if @state != :established
597
+ establish_connection(sock)
598
+ end
599
+
600
+ unless available?
601
+ raise ConnectionClosedError, "failed to establish connection with node #{@name}"
602
+ end
603
+
604
+ option = { 'size' => chunk.size, 'compressed' => @compress }
605
+ option['chunk'] = Base64.encode64(chunk.unique_id) if @sender.require_ack_response
606
+
607
+ # https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1#packedforward-mode
608
+ # out_forward always uses str32 type for entries.
609
+ # str16 can store only 64kbytes, and it should be much smaller than buffer chunk size.
610
+
611
+ tag = tag.dup.force_encoding(Encoding::UTF_8)
612
+
613
+ sock.write @sender.forward_header # array, size=3
614
+ sock.write tag.to_msgpack # 1. tag: String (str)
615
+ chunk.open(compressed: @compress) do |chunk_io|
616
+ entries = [0xdb, chunk_io.size].pack('CN')
617
+ sock.write entries.force_encoding(Encoding::UTF_8) # 2. entries: String (str32)
618
+ IO.copy_stream(chunk_io, sock) # writeRawBody(packed_es)
619
+ end
620
+ sock.write option.to_msgpack # 3. option: Hash(map)
621
+
622
+ # TODO: use bin32 for non-utf8 content(entries) when old msgpack-ruby (0.5.x or earlier) not supported
623
+ end
624
+
625
+ def send_data(tag, chunk)
626
+ sock = @sender.create_transfer_socket(resolved_host, port, @hostname)
627
+ begin
628
+ send_data_actual(sock, tag, chunk)
629
+ rescue
630
+ sock.close rescue nil
631
+ raise
632
+ end
633
+
634
+ if @sender.require_ack_response
635
+ return sock # to read ACK from socket
636
+ end
637
+
638
+ sock.close_write rescue nil
639
+ sock.close rescue nil
640
+ heartbeat(false)
641
+ nil
642
+ end
643
+
644
+ # FORWARD_TCP_HEARTBEAT_DATA = FORWARD_HEADER + ''.to_msgpack + [].to_msgpack
645
+ def send_heartbeat
646
+ begin
647
+ dest_addr = resolved_host
648
+ @resolved_once = true
649
+ rescue ::SocketError => e
650
+ if !@resolved_once && @sender.ignore_network_errors_at_startup
651
+ @log.warn "failed to resolve node name in heartbeating", server: @name || @host, error: e
652
+ return
653
+ end
654
+ raise
655
+ end
656
+
657
+ case @sender.heartbeat_type
658
+ when :transport
659
+ @sender.create_transfer_socket(dest_addr, port, @hostname) do |sock|
660
+ ## don't send any data to not cause a compatibility problem
661
+ # sock.write FORWARD_TCP_HEARTBEAT_DATA
662
+
663
+ # successful tcp connection establishment is considered as valid heartbeat
664
+ heartbeat(true)
665
+ end
666
+ when :udp
667
+ @usock.send "\0", 0, Socket.pack_sockaddr_in(@port, resolved_host)
668
+ when :none # :none doesn't use this class
669
+ raise "BUG: heartbeat_type none must not use Node"
670
+ else
671
+ raise "BUG: unknown heartbeat_type '#{@sender.heartbeat_type}'"
672
+ end
673
+ end
674
+
675
+ def resolved_host
676
+ case @sender.expire_dns_cache
677
+ when 0
678
+ # cache is disabled
679
+ resolve_dns!
680
+
681
+ when nil
682
+ # persistent cache
683
+ @resolved_host ||= resolve_dns!
684
+
685
+ else
686
+ now = Fluent::Engine.now
687
+ rh = @resolved_host
688
+ if !rh || now - @resolved_time >= @sender.expire_dns_cache
689
+ rh = @resolved_host = resolve_dns!
690
+ @resolved_time = now
691
+ end
692
+ rh
693
+ end
694
+ end
695
+
696
+ def resolve_dns!
697
+ addrinfo_list = Socket.getaddrinfo(@host, @port, nil, Socket::SOCK_STREAM)
698
+ addrinfo = @sender.dns_round_robin ? addrinfo_list.sample : addrinfo_list.first
699
+ @sockaddr = Socket.pack_sockaddr_in(addrinfo[1], addrinfo[3]) # used by on_heartbeat
700
+ addrinfo[3]
701
+ end
702
+ private :resolve_dns!
703
+
704
+ def tick
705
+ now = Time.now.to_f
706
+ if !@available
707
+ if @failure.hard_timeout?(now)
708
+ @failure.clear
709
+ end
710
+ return nil
711
+ end
712
+
713
+ if @failure.hard_timeout?(now)
714
+ @log.warn "detached forwarding server '#{@name}'", host: @host, port: @port, hard_timeout: true
715
+ @available = false
716
+ @resolved_host = nil # expire cached host
717
+ @failure.clear
718
+ return true
719
+ end
720
+
721
+ if @sender.phi_failure_detector
722
+ phi = @failure.phi(now)
723
+ if phi > @sender.phi_threshold
724
+ @log.warn "detached forwarding server '#{@name}'", host: @host, port: @port, phi: phi, phi_threshold: @sender.phi_threshold
725
+ @available = false
726
+ @resolved_host = nil # expire cached host
727
+ @failure.clear
728
+ return true
729
+ end
730
+ end
731
+ false
732
+ end
733
+
734
+ def heartbeat(detect=true)
735
+ now = Time.now.to_f
736
+ @failure.add(now)
737
+ if detect && !@available && @failure.sample_size > @sender.recover_sample_size
738
+ @available = true
739
+ @log.warn "recovered forwarding server '#{@name}'", host: @host, port: @port
740
+ true
741
+ else
742
+ nil
743
+ end
744
+ end
745
+
746
+ def generate_salt
747
+ SecureRandom.hex(16)
748
+ end
749
+
750
+ def check_helo(message)
751
+ @log.debug "checking helo"
752
+ # ['HELO', options(hash)]
753
+ unless message.size == 2 && message[0] == 'HELO'
754
+ return false
755
+ end
756
+ opts = message[1] || {}
757
+ # make shared_key_check failed (instead of error) if protocol version mismatch exist
758
+ @shared_key_nonce = opts['nonce'] || ''
759
+ @authentication = opts['auth'] || ''
760
+ true
761
+ end
762
+
763
+ def generate_ping
764
+ @log.debug "generating ping"
765
+ # ['PING', self_hostname, sharedkey\_salt, sha512\_hex(sharedkey\_salt + self_hostname + nonce + shared_key),
766
+ # username || '', sha512\_hex(auth\_salt + username + password) || '']
767
+ shared_key_hexdigest = Digest::SHA512.new.update(@shared_key_salt)
768
+ .update(@sender.security.self_hostname)
769
+ .update(@shared_key_nonce)
770
+ .update(@shared_key)
771
+ .hexdigest
772
+ ping = ['PING', @sender.security.self_hostname, @shared_key_salt, shared_key_hexdigest]
773
+ if !@authentication.empty?
774
+ password_hexdigest = Digest::SHA512.new.update(@authentication).update(@username).update(@password).hexdigest
775
+ ping.push(@username, password_hexdigest)
776
+ else
777
+ ping.push('','')
778
+ end
779
+ ping
780
+ end
781
+
782
+ def check_pong(message)
783
+ @log.debug "checking pong"
784
+ # ['PONG', bool(authentication result), 'reason if authentication failed',
785
+ # self_hostname, sha512\_hex(salt + self_hostname + nonce + sharedkey)]
786
+ unless message.size == 5 && message[0] == 'PONG'
787
+ return false, 'invalid format for PONG message'
788
+ end
789
+ _pong, auth_result, reason, hostname, shared_key_hexdigest = message
790
+
791
+ unless auth_result
792
+ return false, 'authentication failed: ' + reason
793
+ end
794
+
795
+ if hostname == @sender.security.self_hostname
796
+ return false, 'same hostname between input and output: invalid configuration'
797
+ end
798
+
799
+ clientside = Digest::SHA512.new.update(@shared_key_salt).update(hostname).update(@shared_key_nonce).update(@shared_key).hexdigest
800
+ unless shared_key_hexdigest == clientside
801
+ return false, 'shared key mismatch'
802
+ end
803
+
804
+ return true, nil
805
+ end
806
+
807
+ def on_read(sock, data)
808
+ @log.trace __callee__
809
+
810
+ case @state
811
+ when :helo
812
+ unless check_helo(data)
813
+ @log.warn "received invalid helo message from #{@name}"
814
+ disable! # shutdown
815
+ return
816
+ end
817
+ sock.write(generate_ping.to_msgpack)
818
+ @state = :pingpong
819
+ when :pingpong
820
+ succeeded, reason = check_pong(data)
821
+ unless succeeded
822
+ @log.warn "connection refused to #{@name}: #{reason}"
823
+ disable! # shutdown
824
+ return
825
+ end
826
+ @state = :established
827
+ @log.debug "connection established", host: @host, port: @port
828
+ else
829
+ raise "BUG: unknown session state: #{@state}"
830
+ end
831
+ end
832
+ end
833
+
834
+ # Override Node to disable heartbeat
835
+ class NoneHeartbeatNode < Node
836
+ def available?
837
+ true
838
+ end
839
+
840
+ def tick
841
+ false
842
+ end
843
+
844
+ def heartbeat(detect=true)
845
+ true
846
+ end
847
+ end
848
+
849
+ class FailureDetector
850
+ PHI_FACTOR = 1.0 / Math.log(10.0)
851
+ SAMPLE_SIZE = 1000
852
+
853
+ def initialize(heartbeat_interval, hard_timeout, init_last)
854
+ @heartbeat_interval = heartbeat_interval
855
+ @last = init_last
856
+ @hard_timeout = hard_timeout
857
+
858
+ # microsec
859
+ @init_gap = (heartbeat_interval * 1e6).to_i
860
+ @window = [@init_gap]
861
+ end
862
+
863
+ def hard_timeout?(now)
864
+ now - @last > @hard_timeout
865
+ end
866
+
867
+ def add(now)
868
+ if @window.empty?
869
+ @window << @init_gap
870
+ @last = now
871
+ else
872
+ gap = now - @last
873
+ @window << (gap * 1e6).to_i
874
+ @window.shift if @window.length > SAMPLE_SIZE
875
+ @last = now
876
+ end
877
+ end
878
+
879
+ def phi(now)
880
+ size = @window.size
881
+ return 0.0 if size == 0
882
+
883
+ # Calculate weighted moving average
884
+ mean_usec = 0
885
+ fact = 0
886
+ @window.each_with_index {|gap,i|
887
+ mean_usec += gap * (1+i)
888
+ fact += (1+i)
889
+ }
890
+ mean_usec = mean_usec / fact
891
+
892
+ # Normalize arrive intervals into 1sec
893
+ mean = (mean_usec.to_f / 1e6) - @heartbeat_interval + 1
894
+
895
+ # Calculate phi of the phi accrual failure detector
896
+ t = now - @last - @heartbeat_interval + 1
897
+ phi = PHI_FACTOR * t / mean
898
+
899
+ return phi
900
+ end
901
+
902
+ def sample_size
903
+ @window.size
904
+ end
905
+
906
+ def clear
907
+ @window.clear
908
+ @last = 0
909
+ end
910
+ end
911
+ end
912
+ end