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,209 @@
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/plugin/input'
18
+ require 'fluent/config/error'
19
+ require 'fluent/plugin/parser'
20
+
21
+ require 'yajl'
22
+
23
+ module Fluent::Plugin
24
+ class SyslogInput < Input
25
+ Fluent::Plugin.register_input('syslog', self)
26
+
27
+ helpers :parser, :compat_parameters, :server
28
+
29
+ DEFAULT_PARSER = 'syslog'
30
+ SYSLOG_REGEXP = /^\<([0-9]+)\>(.*)/
31
+
32
+ FACILITY_MAP = {
33
+ 0 => 'kern',
34
+ 1 => 'user',
35
+ 2 => 'mail',
36
+ 3 => 'daemon',
37
+ 4 => 'auth',
38
+ 5 => 'syslog',
39
+ 6 => 'lpr',
40
+ 7 => 'news',
41
+ 8 => 'uucp',
42
+ 9 => 'cron',
43
+ 10 => 'authpriv',
44
+ 11 => 'ftp',
45
+ 12 => 'ntp',
46
+ 13 => 'audit',
47
+ 14 => 'alert',
48
+ 15 => 'at',
49
+ 16 => 'local0',
50
+ 17 => 'local1',
51
+ 18 => 'local2',
52
+ 19 => 'local3',
53
+ 20 => 'local4',
54
+ 21 => 'local5',
55
+ 22 => 'local6',
56
+ 23 => 'local7'
57
+ }
58
+
59
+ PRIORITY_MAP = {
60
+ 0 => 'emerg',
61
+ 1 => 'alert',
62
+ 2 => 'crit',
63
+ 3 => 'err',
64
+ 4 => 'warn',
65
+ 5 => 'notice',
66
+ 6 => 'info',
67
+ 7 => 'debug'
68
+ }
69
+
70
+ desc 'The port to listen to.'
71
+ config_param :port, :integer, default: 5140
72
+ desc 'The bind address to listen to.'
73
+ config_param :bind, :string, default: '0.0.0.0'
74
+ desc 'The prefix of the tag. The tag itself is generated by the tag prefix, facility level, and priority.'
75
+ config_param :tag, :string
76
+ desc 'The transport protocol used to receive logs.(udp, tcp)'
77
+ config_param :protocol_type, :enum, list: [:tcp, :udp], default: :udp
78
+
79
+ desc 'If true, add source host to event record.'
80
+ config_param :include_source_host, :bool, default: false, deprecated: 'use "source_hostname_key" or "source_address_key" instead.'
81
+ desc 'Specify key of source host when include_source_host is true.'
82
+ config_param :source_host_key, :string, default: 'source_host'.freeze
83
+
84
+ desc 'The field name of hostname of sender.'
85
+ config_param :source_hostname_key, :string, default: nil
86
+ desc 'The field name of source address of sender.'
87
+ config_param :source_address_key, :string, default: nil
88
+ desc 'The field name of the priority.'
89
+ config_param :priority_key, :string, default: nil
90
+ desc 'The field name of the facility.'
91
+ config_param :facility_key, :string, default: nil
92
+
93
+ desc "The max bytes of message"
94
+ config_param :message_length_limit, :size, default: 2048
95
+
96
+ config_param :blocking_timeout, :time, default: 0.5
97
+
98
+ config_section :parse do
99
+ config_set_default :@type, DEFAULT_PARSER
100
+ config_param :with_priority, :bool, default: true
101
+ end
102
+
103
+ def configure(conf)
104
+ compat_parameters_convert(conf, :parser)
105
+
106
+ super
107
+
108
+ @use_default = false
109
+
110
+ @parser = parser_create
111
+ @parser_parse_priority = @parser.respond_to?(:with_priority) && @parser.with_priority
112
+
113
+ if @include_source_host
114
+ if @source_address_key
115
+ raise Fluent::ConfigError, "specify either source_address_key or include_source_host"
116
+ end
117
+ @source_address_key = @source_host_key
118
+ end
119
+ @resolve_name = !!@source_hostname_key
120
+
121
+ @_event_loop_run_timeout = @blocking_timeout
122
+ end
123
+
124
+ def multi_workers_ready?
125
+ true
126
+ end
127
+
128
+ def start
129
+ super
130
+
131
+ log.info "listening syslog socket on #{@bind}:#{@port} with #{@protocol_type}"
132
+ case @protocol_type
133
+ when :udp then start_udp_server
134
+ when :tcp then start_tcp_server
135
+ else
136
+ raise "BUG: invalid protocol_type value:#{@protocol_type}"
137
+ end
138
+ end
139
+
140
+ def start_udp_server
141
+ server_create_udp(:in_syslog_udp_server, @port, bind: @bind, max_bytes: @message_length_limit, resolve_name: @resolve_name) do |data, sock|
142
+ message_handler(data.chomp, sock)
143
+ end
144
+ end
145
+
146
+ def start_tcp_server
147
+ # syslog family add "\n" to each message and this seems only way to split messages in tcp stream
148
+ delimiter = "\n"
149
+ delimiter_size = delimiter.size
150
+ server_create_connection(:in_syslog_tcp_server, @port, bind: @bind, resolve_name: @resolve_name) do |conn|
151
+ buffer = ""
152
+ conn.data do |data|
153
+ buffer << data
154
+ pos = 0
155
+ while idx = buffer.index(delimiter, pos)
156
+ msg = buffer[pos...idx]
157
+ pos = idx + delimiter_size
158
+ message_handler(msg, conn)
159
+ end
160
+ buffer.slice!(0, pos) if pos > 0
161
+ end
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ def message_handler(data, sock)
168
+ pri = nil
169
+ text = data
170
+ unless @parser_parse_priority
171
+ m = SYSLOG_REGEXP.match(data)
172
+ unless m
173
+ log.warn "invalid syslog message: #{data.dump}"
174
+ return
175
+ end
176
+ pri = m[1].to_i
177
+ text = m[2]
178
+ end
179
+
180
+ @parser.parse(text) do |time, record|
181
+ unless time && record
182
+ log.warn "failed to parse message", data: data
183
+ return
184
+ end
185
+
186
+ pri ||= record.delete('pri')
187
+ facility = FACILITY_MAP[pri >> 3]
188
+ priority = PRIORITY_MAP[pri & 0b111]
189
+
190
+ record[@priority_key] = priority if @priority_key
191
+ record[@facility_key] = facility if @facility_key
192
+ record[@source_address_key] = sock.remote_addr if @source_address_key
193
+ record[@source_hostname_key] = sock.remote_host if @source_hostname_key
194
+
195
+ tag = "#{@tag}.#{facility}.#{priority}"
196
+ emit(tag, time, record)
197
+ end
198
+ rescue => e
199
+ log.error "invalid input", data: data, error: e
200
+ log.error_backtrace
201
+ end
202
+
203
+ def emit(tag, time, record)
204
+ router.emit(tag, time, record)
205
+ rescue => e
206
+ log.error "syslog failed to emit", error: e, tag: tag, record: Yajl.dump(record)
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,905 @@
1
+ #
2
+ # Fluentd
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'cool.io'
18
+
19
+ require 'fluent/plugin/input'
20
+ require 'fluent/config/error'
21
+ require 'fluent/event'
22
+ require 'fluent/plugin/buffer'
23
+ require 'fluent/plugin/parser_multiline'
24
+
25
+ if Fluent.windows?
26
+ require_relative 'file_wrapper'
27
+ else
28
+ Fluent::FileWrapper = File
29
+ end
30
+
31
+ module Fluent::Plugin
32
+ class TailInput < Fluent::Plugin::Input
33
+ Fluent::Plugin.register_input('tail', self)
34
+
35
+ helpers :timer, :event_loop, :parser, :compat_parameters
36
+
37
+ FILE_PERMISSION = 0644
38
+
39
+ def initialize
40
+ super
41
+ @paths = []
42
+ @tails = {}
43
+ @pf_file = nil
44
+ @pf = nil
45
+ end
46
+
47
+ desc 'The paths to read. Multiple paths can be specified, separated by comma.'
48
+ config_param :path, :string
49
+ desc 'The tag of the event.'
50
+ config_param :tag, :string
51
+ desc 'The paths to exclude the files from watcher list.'
52
+ config_param :exclude_path, :array, default: []
53
+ desc 'Specify interval to keep reference to old file when rotate a file.'
54
+ config_param :rotate_wait, :time, default: 5
55
+ desc 'Fluentd will record the position it last read into this file.'
56
+ config_param :pos_file, :string, default: nil
57
+ desc 'Start to read the logs from the head of file, not bottom.'
58
+ config_param :read_from_head, :bool, default: false
59
+ # When the program deletes log file and re-creates log file with same filename after passed refresh_interval,
60
+ # in_tail may raise a pos_file related error. This is a known issue but there is no such program on production.
61
+ # If we find such program / application, we will fix the problem.
62
+ desc 'The interval of refreshing the list of watch file.'
63
+ config_param :refresh_interval, :time, default: 60
64
+ desc 'The number of reading lines at each IO.'
65
+ config_param :read_lines_limit, :integer, default: 1000
66
+ desc 'The interval of flushing the buffer for multiline format'
67
+ config_param :multiline_flush_interval, :time, default: nil
68
+ desc 'Enable the option to emit unmatched lines.'
69
+ config_param :emit_unmatched_lines, :bool, default: false
70
+ desc 'Enable the additional watch timer.'
71
+ config_param :enable_watch_timer, :bool, default: true
72
+ desc 'The encoding after conversion of the input.'
73
+ config_param :encoding, :string, default: nil
74
+ desc 'The encoding of the input.'
75
+ config_param :from_encoding, :string, default: nil
76
+ desc 'Add the log path being tailed to records. Specify the field name to be used.'
77
+ config_param :path_key, :string, default: nil
78
+ desc 'Open and close the file on every update instead of leaving it open until it gets rotated.'
79
+ config_param :open_on_every_update, :bool, default: false
80
+ desc 'Limit the watching files that the modification time is within the specified time range (when use \'*\' in path).'
81
+ config_param :limit_recently_modified, :time, default: nil
82
+ desc 'Enable the option to skip the refresh of watching list on startup.'
83
+ config_param :skip_refresh_on_startup, :bool, default: false
84
+
85
+ attr_reader :paths
86
+
87
+ def configure(conf)
88
+ compat_parameters_convert(conf, :parser)
89
+ parser_config = conf.elements('parse').first
90
+ unless parser_config
91
+ raise Fluent::ConfigError, "<parse> section is required."
92
+ end
93
+ unless parser_config["@type"]
94
+ raise Fluent::ConfigError, "parse/@type is required."
95
+ end
96
+
97
+ (1..Fluent::Plugin::MultilineParser::FORMAT_MAX_NUM).each do |n|
98
+ parser_config["format#{n}"] = conf["format#{n}"] if conf["format#{n}"]
99
+ end
100
+
101
+ super
102
+
103
+ @paths = @path.split(',').map {|path| path.strip }
104
+ if @paths.empty?
105
+ raise Fluent::ConfigError, "tail: 'path' parameter is required on tail input"
106
+ end
107
+
108
+ # TODO: Use plugin_root_dir and storage plugin to store positions if available
109
+ unless @pos_file
110
+ $log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
111
+ $log.warn "this parameter is highly recommended to save the position to resume tailing."
112
+ end
113
+
114
+ configure_tag
115
+ configure_encoding
116
+
117
+ @multiline_mode = parser_config["@type"] =~ /multiline/
118
+ @receive_handler = if @multiline_mode
119
+ method(:parse_multilines)
120
+ else
121
+ method(:parse_singleline)
122
+ end
123
+ @file_perm = system_config.file_permission || FILE_PERMISSION
124
+ @parser = parser_create(conf: parser_config)
125
+ end
126
+
127
+ def configure_tag
128
+ if @tag.index('*')
129
+ @tag_prefix, @tag_suffix = @tag.split('*')
130
+ @tag_suffix ||= ''
131
+ else
132
+ @tag_prefix = nil
133
+ @tag_suffix = nil
134
+ end
135
+ end
136
+
137
+ def configure_encoding
138
+ unless @encoding
139
+ if @from_encoding
140
+ raise Fluent::ConfigError, "tail: 'from_encoding' parameter must be specified with 'encoding' parameter."
141
+ end
142
+ end
143
+
144
+ @encoding = parse_encoding_param(@encoding) if @encoding
145
+ @from_encoding = parse_encoding_param(@from_encoding) if @from_encoding
146
+ end
147
+
148
+ def parse_encoding_param(encoding_name)
149
+ begin
150
+ Encoding.find(encoding_name) if encoding_name
151
+ rescue ArgumentError => e
152
+ raise Fluent::ConfigError, e.message
153
+ end
154
+ end
155
+
156
+ def start
157
+ super
158
+
159
+ if @pos_file
160
+ @pf_file = File.open(@pos_file, File::RDWR|File::CREAT|File::BINARY, @file_perm)
161
+ @pf_file.sync = true
162
+ @pf = PositionFile.parse(@pf_file)
163
+ end
164
+
165
+ refresh_watchers unless @skip_refresh_on_startup
166
+ timer_execute(:in_tail_refresh_watchers, @refresh_interval, &method(:refresh_watchers))
167
+ end
168
+
169
+ def shutdown
170
+ # during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
171
+ stop_watchers(@tails.keys, immediate: true, remove_watcher: false)
172
+ @pf_file.close if @pf_file
173
+
174
+ super
175
+ end
176
+
177
+ def close
178
+ super
179
+ # close file handles after all threads stopped (in #close of thread plugin helper)
180
+ close_watcher_handles
181
+ end
182
+
183
+ def expand_paths
184
+ date = Time.now
185
+ paths = []
186
+
187
+ excluded = @exclude_path.map { |path| path = date.strftime(path); path.include?('*') ? Dir.glob(path) : path }.flatten.uniq
188
+ @paths.each { |path|
189
+ path = date.strftime(path)
190
+ if path.include?('*')
191
+ paths += Dir.glob(path).select { |p|
192
+ if File.readable?(p) && !File.directory?(p)
193
+ if @limit_recently_modified && File.mtime(p) < (date - @limit_recently_modified)
194
+ false
195
+ else
196
+ true
197
+ end
198
+ else
199
+ log.warn "#{p} unreadable. It is excluded and would be examined next time."
200
+ false
201
+ end
202
+ }
203
+ else
204
+ # When file is not created yet, Dir.glob returns an empty array. So just add when path is static.
205
+ paths << path
206
+ end
207
+ }
208
+ paths - excluded
209
+ end
210
+
211
+ # in_tail with '*' path doesn't check rotation file equality at refresh phase.
212
+ # So you should not use '*' path when your logs will be rotated by another tool.
213
+ # It will cause log duplication after updated watch files.
214
+ # In such case, you should separate log directory and specify two paths in path parameter.
215
+ # e.g. path /path/to/dir/*,/path/to/rotated_logs/target_file
216
+ def refresh_watchers
217
+ target_paths = expand_paths
218
+ existence_paths = @tails.keys
219
+
220
+ unwatched = existence_paths - target_paths
221
+ added = target_paths - existence_paths
222
+
223
+ stop_watchers(unwatched, immediate: false, unwatched: true) unless unwatched.empty?
224
+ start_watchers(added) unless added.empty?
225
+ end
226
+
227
+ def setup_watcher(path, pe)
228
+ line_buffer_timer_flusher = (@multiline_mode && @multiline_flush_interval) ? TailWatcher::LineBufferTimerFlusher.new(log, @multiline_flush_interval, &method(:flush_buffer)) : nil
229
+ tw = TailWatcher.new(path, @rotate_wait, pe, log, @read_from_head, @enable_watch_timer, @read_lines_limit, method(:update_watcher), line_buffer_timer_flusher, @from_encoding, @encoding, open_on_every_update, &method(:receive_lines))
230
+ tw.attach do |watcher|
231
+ watcher.timer_trigger = timer_execute(:in_tail_timer_trigger, 1, &watcher.method(:on_notify)) if watcher.enable_watch_timer
232
+ event_loop_attach(watcher.stat_trigger)
233
+ end
234
+ tw
235
+ end
236
+
237
+ def start_watchers(paths)
238
+ paths.each { |path|
239
+ pe = nil
240
+ if @pf
241
+ pe = @pf[path]
242
+ if @read_from_head && pe.read_inode.zero?
243
+ begin
244
+ pe.update(Fluent::FileWrapper.stat(path).ino, 0)
245
+ rescue Errno::ENOENT
246
+ $log.warn "#{path} not found. Continuing without tailing it."
247
+ end
248
+ end
249
+ end
250
+
251
+ @tails[path] = setup_watcher(path, pe)
252
+ }
253
+ end
254
+
255
+ def stop_watchers(paths, immediate: false, unwatched: false, remove_watcher: true)
256
+ paths.each { |path|
257
+ tw = remove_watcher ? @tails.delete(path) : @tails[path]
258
+ if tw
259
+ tw.unwatched = unwatched
260
+ if immediate
261
+ detach_watcher(tw, false)
262
+ else
263
+ detach_watcher_after_rotate_wait(tw)
264
+ end
265
+ end
266
+ }
267
+ end
268
+
269
+ def close_watcher_handles
270
+ @tails.keys.each do |path|
271
+ tw = @tails.delete(path)
272
+ if tw
273
+ tw.close
274
+ end
275
+ end
276
+ end
277
+
278
+ # refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
279
+ def update_watcher(path, pe)
280
+ if @pf
281
+ unless pe.read_inode == @pf[path].read_inode
282
+ log.trace "Skip update_watcher because watcher has been already updated by other inotify event"
283
+ return
284
+ end
285
+ end
286
+ rotated_tw = @tails[path]
287
+ @tails[path] = setup_watcher(path, pe)
288
+ detach_watcher_after_rotate_wait(rotated_tw) if rotated_tw
289
+ end
290
+
291
+ # TailWatcher#close is called by another thread at shutdown phase.
292
+ # It causes 'can't modify string; temporarily locked' error in IOHandler
293
+ # so adding close_io argument to avoid this problem.
294
+ # At shutdown, IOHandler's io will be released automatically after detached the event loop
295
+ def detach_watcher(tw, close_io = true)
296
+ tw.detach
297
+ tw.close if close_io
298
+ flush_buffer(tw)
299
+ if tw.unwatched && @pf
300
+ @pf[tw.path].update_pos(PositionFile::UNWATCHED_POSITION)
301
+ end
302
+ end
303
+
304
+ def detach_watcher_after_rotate_wait(tw)
305
+ timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
306
+ detach_watcher(tw)
307
+ end
308
+ end
309
+
310
+ def flush_buffer(tw)
311
+ if lb = tw.line_buffer
312
+ lb.chomp!
313
+ @parser.parse(lb) { |time, record|
314
+ if time && record
315
+ tag = if @tag_prefix || @tag_suffix
316
+ @tag_prefix + tw.tag + @tag_suffix
317
+ else
318
+ @tag
319
+ end
320
+ record[@path_key] ||= tw.path unless @path_key.nil?
321
+ router.emit(tag, time, record)
322
+ else
323
+ log.warn "got incomplete line at shutdown from #{tw.path}: #{lb.inspect}"
324
+ end
325
+ }
326
+ end
327
+ end
328
+
329
+ # @return true if no error or unrecoverable error happens in emit action. false if got BufferOverflowError
330
+ def receive_lines(lines, tail_watcher)
331
+ es = @receive_handler.call(lines, tail_watcher)
332
+ unless es.empty?
333
+ tag = if @tag_prefix || @tag_suffix
334
+ @tag_prefix + tail_watcher.tag + @tag_suffix
335
+ else
336
+ @tag
337
+ end
338
+ begin
339
+ router.emit_stream(tag, es)
340
+ rescue Fluent::Plugin::Buffer::BufferOverflowError
341
+ return false
342
+ rescue
343
+ # ignore non BufferQueueLimitError errors because in_tail can't recover. Engine shows logs and backtraces.
344
+ return true
345
+ end
346
+ end
347
+
348
+ return true
349
+ end
350
+
351
+ def convert_line_to_event(line, es, tail_watcher)
352
+ begin
353
+ line.chomp! # remove \n
354
+ @parser.parse(line) { |time, record|
355
+ if time && record
356
+ record[@path_key] ||= tail_watcher.path unless @path_key.nil?
357
+ es.add(time, record)
358
+ else
359
+ if @emit_unmatched_lines
360
+ record = {'unmatched_line' => line}
361
+ record[@path_key] ||= tail_watcher.path unless @path_key.nil?
362
+ es.add(Fluent::EventTime.now, record)
363
+ end
364
+ log.warn "pattern not match: #{line.inspect}"
365
+ end
366
+ }
367
+ rescue => e
368
+ log.warn line.dump, error: e.to_s
369
+ log.debug_backtrace(e.backtrace)
370
+ end
371
+ end
372
+
373
+ def parse_singleline(lines, tail_watcher)
374
+ es = Fluent::MultiEventStream.new
375
+ lines.each { |line|
376
+ convert_line_to_event(line, es, tail_watcher)
377
+ }
378
+ es
379
+ end
380
+
381
+ def parse_multilines(lines, tail_watcher)
382
+ lb = tail_watcher.line_buffer
383
+ es = Fluent::MultiEventStream.new
384
+ if @parser.has_firstline?
385
+ tail_watcher.line_buffer_timer_flusher.reset_timer if tail_watcher.line_buffer_timer_flusher
386
+ lines.each { |line|
387
+ if @parser.firstline?(line)
388
+ if lb
389
+ convert_line_to_event(lb, es, tail_watcher)
390
+ end
391
+ lb = line
392
+ else
393
+ if lb.nil?
394
+ if @emit_unmatched_lines
395
+ convert_line_to_event(line, es, tail_watcher)
396
+ end
397
+ log.warn "got incomplete line before first line from #{tail_watcher.path}: #{line.inspect}"
398
+ else
399
+ lb << line
400
+ end
401
+ end
402
+ }
403
+ else
404
+ lb ||= ''
405
+ lines.each do |line|
406
+ lb << line
407
+ @parser.parse(lb) { |time, record|
408
+ if time && record
409
+ convert_line_to_event(lb, es, tail_watcher)
410
+ lb = ''
411
+ end
412
+ }
413
+ end
414
+ end
415
+ tail_watcher.line_buffer = lb
416
+ es
417
+ end
418
+
419
+ class TailWatcher
420
+ def initialize(path, rotate_wait, pe, log, read_from_head, enable_watch_timer, read_lines_limit, update_watcher, line_buffer_timer_flusher, from_encoding, encoding, open_on_every_update, &receive_lines)
421
+ @path = path
422
+ @rotate_wait = rotate_wait
423
+ @pe = pe || MemoryPositionEntry.new
424
+ @read_from_head = read_from_head
425
+ @enable_watch_timer = enable_watch_timer
426
+ @read_lines_limit = read_lines_limit
427
+ @receive_lines = receive_lines
428
+ @update_watcher = update_watcher
429
+
430
+ @stat_trigger = StatWatcher.new(self, &method(:on_notify))
431
+ @timer_trigger = nil
432
+
433
+ @rotate_handler = RotateHandler.new(self, &method(:on_rotate))
434
+ @io_handler = nil
435
+ @log = log
436
+
437
+ @line_buffer_timer_flusher = line_buffer_timer_flusher
438
+ @from_encoding = from_encoding
439
+ @encoding = encoding
440
+ @open_on_every_update = open_on_every_update
441
+ end
442
+
443
+ attr_reader :path
444
+ attr_reader :log, :pe, :read_lines_limit, :open_on_every_update
445
+ attr_reader :from_encoding, :encoding
446
+ attr_reader :stat_trigger, :enable_watch_timer
447
+ attr_accessor :timer_trigger
448
+ attr_accessor :line_buffer, :line_buffer_timer_flusher
449
+ attr_accessor :unwatched # This is used for removing position entry from PositionFile
450
+
451
+ def tag
452
+ @parsed_tag ||= @path.tr('/', '.').gsub(/\.+/, '.').gsub(/^\./, '')
453
+ end
454
+
455
+ def wrap_receive_lines(lines)
456
+ @receive_lines.call(lines, self)
457
+ end
458
+
459
+ def attach
460
+ on_notify
461
+ yield self
462
+ end
463
+
464
+ def detach
465
+ @timer_trigger.detach if @enable_watch_timer && @timer_trigger.attached?
466
+ @stat_trigger.detach if @stat_trigger.attached?
467
+ @io_handler.on_notify if @io_handler
468
+ end
469
+
470
+ def close
471
+ if @io_handler
472
+ @io_handler.close
473
+ @io_handler = nil
474
+ end
475
+ end
476
+
477
+ def on_notify
478
+ begin
479
+ stat = Fluent::FileWrapper.stat(@path)
480
+ rescue Errno::ENOENT
481
+ # moved or deleted
482
+ stat = nil
483
+ end
484
+
485
+ @rotate_handler.on_notify(stat) if @rotate_handler
486
+ @line_buffer_timer_flusher.on_notify(self) if @line_buffer_timer_flusher
487
+ @io_handler.on_notify if @io_handler
488
+ end
489
+
490
+ def on_rotate(stat)
491
+ if @io_handler.nil?
492
+ if stat
493
+ # first time
494
+ fsize = stat.size
495
+ inode = stat.ino
496
+
497
+ last_inode = @pe.read_inode
498
+ if inode == last_inode
499
+ # rotated file has the same inode number with the last file.
500
+ # assuming following situation:
501
+ # a) file was once renamed and backed, or
502
+ # b) symlink or hardlink to the same file is recreated
503
+ # in either case, seek to the saved position
504
+ elsif last_inode != 0
505
+ # this is FilePositionEntry and fluentd once started.
506
+ # read data from the head of the rotated file.
507
+ # logs never duplicate because this file is a rotated new file.
508
+ @pe.update(inode, 0)
509
+ else
510
+ # this is MemoryPositionEntry or this is the first time fluentd started.
511
+ # seek to the end of the any files.
512
+ # logs may duplicate without this seek because it's not sure the file is
513
+ # existent file or rotated new file.
514
+ pos = @read_from_head ? 0 : fsize
515
+ @pe.update(inode, pos)
516
+ end
517
+ @io_handler = IOHandler.new(self, &method(:wrap_receive_lines))
518
+ else
519
+ @io_handler = NullIOHandler.new
520
+ end
521
+ else
522
+ watcher_needs_update = false
523
+
524
+ if stat
525
+ inode = stat.ino
526
+ if inode == @pe.read_inode # truncated
527
+ @pe.update_pos(0)
528
+ @io_handler.close
529
+ elsif !@io_handler.opened? # There is no previous file. Reuse TailWatcher
530
+ @pe.update(inode, 0)
531
+ else # file is rotated and new file found
532
+ watcher_needs_update = true
533
+ end
534
+ else # file is rotated and new file not found
535
+ # Clear RotateHandler to avoid duplicated file watch in same path.
536
+ @rotate_handler = nil
537
+ watcher_needs_update = true
538
+ end
539
+
540
+ log_msg = "detected rotation of #{@path}"
541
+ log_msg << "; waiting #{@rotate_wait} seconds" if watcher_needs_update # wait rotate_time if previous file exists
542
+ @log.info log_msg
543
+
544
+ if watcher_needs_update
545
+ @update_watcher.call(@path, swap_state(@pe))
546
+ else
547
+ @io_handler = IOHandler.new(self, &method(:wrap_receive_lines))
548
+ end
549
+ end
550
+ end
551
+
552
+ def swap_state(pe)
553
+ # Use MemoryPositionEntry for rotated file temporary
554
+ mpe = MemoryPositionEntry.new
555
+ mpe.update(pe.read_inode, pe.read_pos)
556
+ @pe = mpe
557
+ pe # This pe will be updated in on_rotate after TailWatcher is initialized
558
+ end
559
+
560
+ class StatWatcher < Coolio::StatWatcher
561
+ def initialize(watcher, &callback)
562
+ @watcher = watcher
563
+ @callback = callback
564
+ super(watcher.path)
565
+ end
566
+
567
+ def on_change(prev, cur)
568
+ @callback.call
569
+ rescue
570
+ # TODO log?
571
+ @watcher.log.error $!.to_s
572
+ @watcher.log.error_backtrace
573
+ end
574
+ end
575
+
576
+
577
+ class FIFO
578
+ def initialize(from_encoding, encoding)
579
+ @from_encoding = from_encoding
580
+ @encoding = encoding
581
+ @buffer = ''.force_encoding(from_encoding)
582
+ @eol = "\n".encode(from_encoding).freeze
583
+ end
584
+
585
+ attr_reader :from_encoding, :encoding, :buffer
586
+
587
+ def <<(chunk)
588
+ # Although "chunk" is most likely transient besides String#force_encoding itself
589
+ # won't affect the actual content of it, it is also probable that "chunk" is
590
+ # a reused buffer and changing its encoding causes some problems on the caller side.
591
+ #
592
+ # Actually, the caller here is specific and "chunk" comes from IO#partial with
593
+ # the second argument, which the function always returns as a return value.
594
+ #
595
+ # Feeding a string that has its encoding attribute set to any double-byte or
596
+ # quad-byte encoding to IO#readpartial as the second arguments results in an
597
+ # assertion failure on Ruby < 2.4.0 for unknown reasons.
598
+ orig_encoding = chunk.encoding
599
+ chunk.force_encoding(from_encoding)
600
+ @buffer << chunk
601
+ # Thus the encoding needs to be reverted back here
602
+ chunk.force_encoding(orig_encoding)
603
+ end
604
+
605
+ def convert(s)
606
+ if @from_encoding == @encoding
607
+ s
608
+ else
609
+ s.encode(@encoding, @from_encoding)
610
+ end
611
+ end
612
+
613
+ def next_line
614
+ idx = @buffer.index(@eol)
615
+ convert(@buffer.slice!(0, idx + 1)) unless idx.nil?
616
+ end
617
+
618
+ def bytesize
619
+ @buffer.bytesize
620
+ end
621
+ end
622
+
623
+ class IOHandler
624
+ def initialize(watcher, &receive_lines)
625
+ @watcher = watcher
626
+ @receive_lines = receive_lines
627
+ @fifo = FIFO.new(@watcher.from_encoding || Encoding::ASCII_8BIT, @watcher.encoding || Encoding::ASCII_8BIT)
628
+ @iobuf = ''.force_encoding('ASCII-8BIT')
629
+ @lines = []
630
+ @io = nil
631
+ @watcher.log.info "following tail of #{@watcher.path}"
632
+ end
633
+
634
+ def on_notify
635
+ with_io do |io|
636
+ begin
637
+ read_more = false
638
+
639
+ if !io.nil? && @lines.empty?
640
+ begin
641
+ while true
642
+ @fifo << io.readpartial(2048, @iobuf)
643
+ while (line = @fifo.next_line)
644
+ @lines << line
645
+ end
646
+ if @lines.size >= @watcher.read_lines_limit
647
+ # not to use too much memory in case the file is very large
648
+ read_more = true
649
+ break
650
+ end
651
+ end
652
+ rescue EOFError
653
+ end
654
+ end
655
+
656
+ unless @lines.empty?
657
+ if @receive_lines.call(@lines)
658
+ @watcher.pe.update_pos(io.pos - @fifo.bytesize)
659
+ @lines.clear
660
+ else
661
+ read_more = false
662
+ end
663
+ end
664
+ end while read_more
665
+ end
666
+ end
667
+
668
+ def close
669
+ if @io && !@io.closed?
670
+ @io.close
671
+ @io = nil
672
+ end
673
+ end
674
+
675
+ def opened?
676
+ !!@io
677
+ end
678
+
679
+ def open
680
+ io = Fluent::FileWrapper.open(@watcher.path)
681
+ io.seek(@watcher.pe.read_pos + @fifo.bytesize)
682
+ io
683
+ rescue Errno::ENOENT
684
+ nil
685
+ end
686
+
687
+ def with_io
688
+ begin
689
+ if @watcher.open_on_every_update
690
+ io = open
691
+ begin
692
+ yield io
693
+ ensure
694
+ io.close unless io.nil?
695
+ end
696
+ else
697
+ @io ||= open
698
+ yield @io
699
+ end
700
+ rescue
701
+ @watcher.log.error $!.to_s
702
+ @watcher.log.error_backtrace
703
+ close
704
+ end
705
+ end
706
+ end
707
+
708
+ class NullIOHandler
709
+ def initialize
710
+ end
711
+
712
+ def io
713
+ end
714
+
715
+ def on_notify
716
+ end
717
+
718
+ def close
719
+ end
720
+
721
+ def opened?
722
+ false
723
+ end
724
+ end
725
+
726
+ class RotateHandler
727
+ def initialize(watcher, &on_rotate)
728
+ @watcher = watcher
729
+ @inode = nil
730
+ @fsize = -1 # first
731
+ @on_rotate = on_rotate
732
+ end
733
+
734
+ def on_notify(stat)
735
+ if stat.nil?
736
+ inode = nil
737
+ fsize = 0
738
+ else
739
+ inode = stat.ino
740
+ fsize = stat.size
741
+ end
742
+
743
+ begin
744
+ if @inode != inode || fsize < @fsize
745
+ @on_rotate.call(stat)
746
+ end
747
+ @inode = inode
748
+ @fsize = fsize
749
+ end
750
+
751
+ rescue
752
+ @watcher.log.error $!.to_s
753
+ @watcher.log.error_backtrace
754
+ end
755
+ end
756
+
757
+ class LineBufferTimerFlusher
758
+ def initialize(log, flush_interval, &flush_method)
759
+ @log = log
760
+ @flush_interval = flush_interval
761
+ @flush_method = flush_method
762
+ @start = nil
763
+ end
764
+
765
+ def on_notify(tw)
766
+ if @start && @flush_interval
767
+ if Time.now - @start >= @flush_interval
768
+ @flush_method.call(tw)
769
+ tw.line_buffer = nil
770
+ @start = nil
771
+ end
772
+ end
773
+ end
774
+
775
+ def reset_timer
776
+ @start = Time.now
777
+ end
778
+ end
779
+ end
780
+
781
+ class PositionFile
782
+ UNWATCHED_POSITION = 0xffffffffffffffff
783
+
784
+ def initialize(file, map, last_pos)
785
+ @file = file
786
+ @map = map
787
+ @last_pos = last_pos
788
+ end
789
+
790
+ def [](path)
791
+ if m = @map[path]
792
+ return m
793
+ end
794
+
795
+ @file.pos = @last_pos
796
+ @file.write path
797
+ @file.write "\t"
798
+ seek = @file.pos
799
+ @file.write "0000000000000000\t0000000000000000\n"
800
+ @last_pos = @file.pos
801
+
802
+ @map[path] = FilePositionEntry.new(@file, seek)
803
+ end
804
+
805
+ def self.parse(file)
806
+ compact(file)
807
+
808
+ map = {}
809
+ file.pos = 0
810
+ file.each_line {|line|
811
+ m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
812
+ next unless m
813
+ path = m[1]
814
+ seek = file.pos - line.bytesize + path.bytesize + 1
815
+ map[path] = FilePositionEntry.new(file, seek)
816
+ }
817
+ new(file, map, file.pos)
818
+ end
819
+
820
+ # Clean up unwatched file entries
821
+ def self.compact(file)
822
+ file.pos = 0
823
+ existent_entries = file.each_line.map { |line|
824
+ m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
825
+ next unless m
826
+ path = m[1]
827
+ pos = m[2].to_i(16)
828
+ ino = m[3].to_i(16)
829
+ # 32bit inode converted to 64bit at this phase
830
+ pos == UNWATCHED_POSITION ? nil : ("%s\t%016x\t%016x\n" % [path, pos, ino])
831
+ }.compact
832
+
833
+ file.pos = 0
834
+ file.truncate(0)
835
+ file.write(existent_entries.join)
836
+ end
837
+ end
838
+
839
+ # pos inode
840
+ # ffffffffffffffff\tffffffffffffffff\n
841
+ class FilePositionEntry
842
+ POS_SIZE = 16
843
+ INO_OFFSET = 17
844
+ INO_SIZE = 16
845
+ LN_OFFSET = 33
846
+ SIZE = 34
847
+
848
+ def initialize(file, seek)
849
+ @file = file
850
+ @seek = seek
851
+ @pos = nil
852
+ end
853
+
854
+ def update(ino, pos)
855
+ @file.pos = @seek
856
+ @file.write "%016x\t%016x" % [pos, ino]
857
+ @pos = pos
858
+ end
859
+
860
+ def update_pos(pos)
861
+ @file.pos = @seek
862
+ @file.write "%016x" % pos
863
+ @pos = pos
864
+ end
865
+
866
+ def read_inode
867
+ @file.pos = @seek + INO_OFFSET
868
+ raw = @file.read(16)
869
+ raw ? raw.to_i(16) : 0
870
+ end
871
+
872
+ def read_pos
873
+ @pos ||= begin
874
+ @file.pos = @seek
875
+ raw = @file.read(16)
876
+ raw ? raw.to_i(16) : 0
877
+ end
878
+ end
879
+ end
880
+
881
+ class MemoryPositionEntry
882
+ def initialize
883
+ @pos = 0
884
+ @inode = 0
885
+ end
886
+
887
+ def update(ino, pos)
888
+ @inode = ino
889
+ @pos = pos
890
+ end
891
+
892
+ def update_pos(pos)
893
+ @pos = pos
894
+ end
895
+
896
+ def read_pos
897
+ @pos
898
+ end
899
+
900
+ def read_inode
901
+ @inode
902
+ end
903
+ end
904
+ end
905
+ end