fluentd 0.12.40 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

Files changed (428) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  4. data/.github/ISSUE_TEMPLATE.md +17 -0
  5. data/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  6. data/.gitignore +5 -0
  7. data/.gitlab/cicd-template.yaml +10 -0
  8. data/.gitlab-ci.yml +147 -0
  9. data/.travis.yml +56 -20
  10. data/ADOPTERS.md +5 -0
  11. data/CHANGELOG.md +1369 -0
  12. data/CONTRIBUTING.md +16 -5
  13. data/GOVERNANCE.md +55 -0
  14. data/Gemfile +5 -0
  15. data/GithubWorkflow.md +78 -0
  16. data/LICENSE +202 -0
  17. data/MAINTAINERS.md +7 -0
  18. data/README.md +23 -11
  19. data/Rakefile +48 -2
  20. data/Vagrantfile +17 -0
  21. data/appveyor.yml +37 -0
  22. data/bin/fluent-binlog-reader +7 -0
  23. data/bin/fluent-ca-generate +6 -0
  24. data/bin/fluent-plugin-config-format +5 -0
  25. data/bin/fluent-plugin-generate +5 -0
  26. data/bin/fluentd +3 -0
  27. data/code-of-conduct.md +3 -0
  28. data/example/copy_roundrobin.conf +39 -0
  29. data/example/counter.conf +18 -0
  30. data/example/in_dummy_blocks.conf +17 -0
  31. data/example/in_dummy_with_compression.conf +23 -0
  32. data/example/in_forward.conf +7 -0
  33. data/example/in_forward_client.conf +37 -0
  34. data/example/in_forward_shared_key.conf +15 -0
  35. data/example/in_forward_tls.conf +14 -0
  36. data/example/in_forward_users.conf +24 -0
  37. data/example/in_forward_workers.conf +21 -0
  38. data/example/in_http.conf +3 -1
  39. data/example/in_out_forward.conf +17 -0
  40. data/example/logevents.conf +25 -0
  41. data/example/multi_filters.conf +61 -0
  42. data/example/out_exec_filter.conf +42 -0
  43. data/example/out_forward.conf +13 -13
  44. data/example/out_forward_buf_file.conf +23 -0
  45. data/example/out_forward_client.conf +109 -0
  46. data/example/out_forward_heartbeat_none.conf +16 -0
  47. data/example/out_forward_shared_key.conf +36 -0
  48. data/example/out_forward_tls.conf +18 -0
  49. data/example/out_forward_users.conf +65 -0
  50. data/example/out_null.conf +36 -0
  51. data/example/secondary_file.conf +42 -0
  52. data/example/suppress_config_dump.conf +7 -0
  53. data/example/worker_section.conf +36 -0
  54. data/fluent.conf +29 -0
  55. data/fluentd.gemspec +21 -11
  56. data/lib/fluent/agent.rb +67 -90
  57. data/lib/fluent/clock.rb +62 -0
  58. data/lib/fluent/command/binlog_reader.rb +244 -0
  59. data/lib/fluent/command/ca_generate.rb +181 -0
  60. data/lib/fluent/command/cat.rb +42 -18
  61. data/lib/fluent/command/debug.rb +12 -10
  62. data/lib/fluent/command/fluentd.rb +153 -5
  63. data/lib/fluent/command/plugin_config_formatter.rb +292 -0
  64. data/lib/fluent/command/plugin_generator.rb +324 -0
  65. data/lib/fluent/compat/call_super_mixin.rb +67 -0
  66. data/lib/fluent/compat/detach_process_mixin.rb +33 -0
  67. data/lib/fluent/compat/exec_util.rb +129 -0
  68. data/lib/fluent/compat/file_util.rb +54 -0
  69. data/lib/fluent/compat/filter.rb +68 -0
  70. data/lib/fluent/compat/formatter.rb +111 -0
  71. data/lib/fluent/compat/formatter_utils.rb +85 -0
  72. data/lib/fluent/compat/handle_tag_and_time_mixin.rb +62 -0
  73. data/lib/fluent/compat/handle_tag_name_mixin.rb +53 -0
  74. data/lib/fluent/compat/input.rb +49 -0
  75. data/lib/fluent/compat/output.rb +718 -0
  76. data/lib/fluent/compat/output_chain.rb +60 -0
  77. data/lib/fluent/compat/parser.rb +310 -0
  78. data/lib/fluent/compat/parser_utils.rb +40 -0
  79. data/lib/fluent/compat/propagate_default.rb +62 -0
  80. data/lib/fluent/compat/record_filter_mixin.rb +34 -0
  81. data/lib/fluent/compat/set_tag_key_mixin.rb +50 -0
  82. data/lib/fluent/compat/set_time_key_mixin.rb +69 -0
  83. data/lib/fluent/compat/socket_util.rb +165 -0
  84. data/lib/fluent/compat/string_util.rb +34 -0
  85. data/lib/fluent/compat/structured_format_mixin.rb +26 -0
  86. data/lib/fluent/compat/type_converter.rb +90 -0
  87. data/lib/fluent/config/configure_proxy.rb +210 -62
  88. data/lib/fluent/config/dsl.rb +12 -5
  89. data/lib/fluent/config/element.rb +107 -9
  90. data/lib/fluent/config/literal_parser.rb +9 -3
  91. data/lib/fluent/config/parser.rb +4 -4
  92. data/lib/fluent/config/section.rb +51 -14
  93. data/lib/fluent/config/types.rb +28 -13
  94. data/lib/fluent/config/v1_parser.rb +3 -5
  95. data/lib/fluent/config.rb +23 -20
  96. data/lib/fluent/configurable.rb +79 -21
  97. data/lib/fluent/counter/base_socket.rb +46 -0
  98. data/lib/fluent/counter/client.rb +297 -0
  99. data/lib/fluent/counter/error.rb +86 -0
  100. data/lib/fluent/counter/mutex_hash.rb +163 -0
  101. data/lib/fluent/counter/server.rb +273 -0
  102. data/lib/fluent/counter/store.rb +205 -0
  103. data/lib/fluent/counter/validator.rb +145 -0
  104. data/lib/fluent/counter.rb +23 -0
  105. data/lib/fluent/daemon.rb +15 -0
  106. data/lib/fluent/engine.rb +102 -65
  107. data/lib/fluent/env.rb +7 -3
  108. data/lib/fluent/error.rb +30 -0
  109. data/lib/fluent/event.rb +197 -21
  110. data/lib/fluent/event_router.rb +93 -10
  111. data/lib/fluent/filter.rb +2 -50
  112. data/lib/fluent/formatter.rb +4 -293
  113. data/lib/fluent/input.rb +2 -32
  114. data/lib/fluent/label.rb +10 -2
  115. data/lib/fluent/load.rb +3 -3
  116. data/lib/fluent/log.rb +348 -81
  117. data/lib/fluent/match.rb +37 -36
  118. data/lib/fluent/mixin.rb +12 -176
  119. data/lib/fluent/msgpack_factory.rb +62 -0
  120. data/lib/fluent/output.rb +10 -612
  121. data/lib/fluent/output_chain.rb +23 -0
  122. data/lib/fluent/parser.rb +4 -800
  123. data/lib/fluent/plugin/bare_output.rb +63 -0
  124. data/lib/fluent/plugin/base.rb +192 -0
  125. data/lib/fluent/plugin/buf_file.rb +128 -174
  126. data/lib/fluent/plugin/buf_memory.rb +9 -92
  127. data/lib/fluent/plugin/buffer/chunk.rb +221 -0
  128. data/lib/fluent/plugin/buffer/file_chunk.rb +383 -0
  129. data/lib/fluent/plugin/buffer/memory_chunk.rb +90 -0
  130. data/lib/fluent/plugin/buffer.rb +779 -0
  131. data/lib/fluent/plugin/compressable.rb +92 -0
  132. data/lib/fluent/plugin/exec_util.rb +3 -108
  133. data/lib/fluent/plugin/file_util.rb +4 -34
  134. data/lib/fluent/plugin/file_wrapper.rb +120 -0
  135. data/lib/fluent/plugin/filter.rb +93 -0
  136. data/lib/fluent/plugin/filter_grep.rb +117 -34
  137. data/lib/fluent/plugin/filter_parser.rb +85 -62
  138. data/lib/fluent/plugin/filter_record_transformer.rb +27 -39
  139. data/lib/fluent/plugin/filter_stdout.rb +15 -12
  140. data/lib/fluent/plugin/formatter.rb +50 -0
  141. data/lib/fluent/plugin/formatter_csv.rb +52 -0
  142. data/lib/fluent/plugin/formatter_hash.rb +33 -0
  143. data/lib/fluent/plugin/formatter_json.rb +55 -0
  144. data/lib/fluent/plugin/formatter_ltsv.rb +42 -0
  145. data/lib/fluent/plugin/formatter_msgpack.rb +33 -0
  146. data/lib/fluent/plugin/formatter_out_file.rb +51 -0
  147. data/lib/fluent/plugin/formatter_single_value.rb +34 -0
  148. data/lib/fluent/plugin/formatter_stdout.rb +76 -0
  149. data/lib/fluent/plugin/formatter_tsv.rb +38 -0
  150. data/lib/fluent/plugin/in_debug_agent.rb +17 -6
  151. data/lib/fluent/plugin/in_dummy.rb +47 -20
  152. data/lib/fluent/plugin/in_exec.rb +55 -123
  153. data/lib/fluent/plugin/in_forward.rb +299 -216
  154. data/lib/fluent/plugin/in_gc_stat.rb +14 -36
  155. data/lib/fluent/plugin/in_http.rb +204 -91
  156. data/lib/fluent/plugin/in_monitor_agent.rb +186 -258
  157. data/lib/fluent/plugin/in_object_space.rb +13 -41
  158. data/lib/fluent/plugin/in_syslog.rb +112 -134
  159. data/lib/fluent/plugin/in_tail.rb +408 -745
  160. data/lib/fluent/plugin/in_tcp.rb +66 -9
  161. data/lib/fluent/plugin/in_udp.rb +60 -11
  162. data/lib/fluent/plugin/{in_stream.rb → in_unix.rb} +8 -4
  163. data/lib/fluent/plugin/input.rb +37 -0
  164. data/lib/fluent/plugin/multi_output.rb +158 -0
  165. data/lib/fluent/plugin/out_copy.rb +23 -35
  166. data/lib/fluent/plugin/out_exec.rb +67 -70
  167. data/lib/fluent/plugin/out_exec_filter.rb +204 -271
  168. data/lib/fluent/plugin/out_file.rb +267 -73
  169. data/lib/fluent/plugin/out_forward.rb +854 -325
  170. data/lib/fluent/plugin/out_null.rb +42 -9
  171. data/lib/fluent/plugin/out_relabel.rb +9 -5
  172. data/lib/fluent/plugin/out_roundrobin.rb +18 -37
  173. data/lib/fluent/plugin/out_secondary_file.rb +133 -0
  174. data/lib/fluent/plugin/out_stdout.rb +43 -10
  175. data/lib/fluent/plugin/out_stream.rb +7 -2
  176. data/lib/fluent/plugin/output.rb +1498 -0
  177. data/lib/fluent/plugin/owned_by_mixin.rb +42 -0
  178. data/lib/fluent/plugin/parser.rb +191 -0
  179. data/lib/fluent/plugin/parser_apache.rb +28 -0
  180. data/lib/fluent/plugin/parser_apache2.rb +88 -0
  181. data/lib/fluent/plugin/parser_apache_error.rb +26 -0
  182. data/lib/fluent/plugin/parser_csv.rb +39 -0
  183. data/lib/fluent/plugin/parser_json.rb +94 -0
  184. data/lib/fluent/plugin/parser_ltsv.rb +49 -0
  185. data/lib/fluent/plugin/parser_msgpack.rb +50 -0
  186. data/lib/fluent/plugin/parser_multiline.rb +106 -0
  187. data/lib/fluent/plugin/parser_nginx.rb +28 -0
  188. data/lib/fluent/plugin/parser_none.rb +36 -0
  189. data/lib/fluent/plugin/parser_regexp.rb +68 -0
  190. data/lib/fluent/plugin/parser_syslog.rb +142 -0
  191. data/lib/fluent/plugin/parser_tsv.rb +42 -0
  192. data/lib/fluent/plugin/socket_util.rb +3 -143
  193. data/lib/fluent/plugin/storage.rb +84 -0
  194. data/lib/fluent/plugin/storage_local.rb +164 -0
  195. data/lib/fluent/plugin/string_util.rb +3 -15
  196. data/lib/fluent/plugin.rb +122 -121
  197. data/lib/fluent/plugin_helper/cert_option.rb +178 -0
  198. data/lib/fluent/plugin_helper/child_process.rb +364 -0
  199. data/lib/fluent/plugin_helper/compat_parameters.rb +333 -0
  200. data/lib/fluent/plugin_helper/counter.rb +51 -0
  201. data/lib/fluent/plugin_helper/event_emitter.rb +93 -0
  202. data/lib/fluent/plugin_helper/event_loop.rb +170 -0
  203. data/lib/fluent/plugin_helper/extract.rb +104 -0
  204. data/lib/fluent/plugin_helper/formatter.rb +147 -0
  205. data/lib/fluent/plugin_helper/http_server/app.rb +79 -0
  206. data/lib/fluent/plugin_helper/http_server/compat/server.rb +81 -0
  207. data/lib/fluent/plugin_helper/http_server/compat/webrick_handler.rb +58 -0
  208. data/lib/fluent/plugin_helper/http_server/methods.rb +35 -0
  209. data/lib/fluent/plugin_helper/http_server/request.rb +42 -0
  210. data/lib/fluent/plugin_helper/http_server/router.rb +54 -0
  211. data/lib/fluent/plugin_helper/http_server/server.rb +87 -0
  212. data/lib/fluent/plugin_helper/http_server.rb +76 -0
  213. data/lib/fluent/plugin_helper/inject.rb +151 -0
  214. data/lib/fluent/plugin_helper/parser.rb +147 -0
  215. data/lib/fluent/plugin_helper/record_accessor.rb +210 -0
  216. data/lib/fluent/plugin_helper/retry_state.rb +205 -0
  217. data/lib/fluent/plugin_helper/server.rb +807 -0
  218. data/lib/fluent/plugin_helper/socket.rb +250 -0
  219. data/lib/fluent/plugin_helper/socket_option.rb +80 -0
  220. data/lib/fluent/plugin_helper/storage.rb +349 -0
  221. data/lib/fluent/plugin_helper/thread.rb +179 -0
  222. data/lib/fluent/plugin_helper/timer.rb +92 -0
  223. data/lib/fluent/plugin_helper.rb +73 -0
  224. data/lib/fluent/plugin_id.rb +80 -0
  225. data/lib/fluent/process.rb +3 -489
  226. data/lib/fluent/registry.rb +52 -10
  227. data/lib/fluent/root_agent.rb +204 -42
  228. data/lib/fluent/supervisor.rb +597 -359
  229. data/lib/fluent/system_config.rb +131 -42
  230. data/lib/fluent/test/base.rb +6 -54
  231. data/lib/fluent/test/driver/base.rb +224 -0
  232. data/lib/fluent/test/driver/base_owned.rb +70 -0
  233. data/lib/fluent/test/driver/base_owner.rb +135 -0
  234. data/lib/fluent/test/driver/event_feeder.rb +98 -0
  235. data/lib/fluent/test/driver/filter.rb +57 -0
  236. data/lib/fluent/test/driver/formatter.rb +30 -0
  237. data/lib/fluent/test/driver/input.rb +31 -0
  238. data/lib/fluent/test/driver/multi_output.rb +53 -0
  239. data/lib/fluent/test/driver/output.rb +102 -0
  240. data/lib/fluent/test/driver/parser.rb +30 -0
  241. data/lib/fluent/test/driver/test_event_router.rb +45 -0
  242. data/lib/fluent/test/filter_test.rb +0 -1
  243. data/lib/fluent/test/formatter_test.rb +4 -1
  244. data/lib/fluent/test/helpers.rb +58 -10
  245. data/lib/fluent/test/input_test.rb +27 -19
  246. data/lib/fluent/test/log.rb +79 -0
  247. data/lib/fluent/test/output_test.rb +28 -39
  248. data/lib/fluent/test/parser_test.rb +3 -1
  249. data/lib/fluent/test/startup_shutdown.rb +46 -0
  250. data/lib/fluent/test.rb +33 -1
  251. data/lib/fluent/time.rb +450 -1
  252. data/lib/fluent/timezone.rb +27 -3
  253. data/lib/fluent/{status.rb → unique_id.rb} +15 -24
  254. data/lib/fluent/version.rb +1 -1
  255. data/lib/fluent/winsvc.rb +85 -0
  256. data/templates/new_gem/Gemfile +3 -0
  257. data/templates/new_gem/README.md.erb +43 -0
  258. data/templates/new_gem/Rakefile +13 -0
  259. data/templates/new_gem/fluent-plugin.gemspec.erb +27 -0
  260. data/templates/new_gem/lib/fluent/plugin/filter.rb.erb +14 -0
  261. data/templates/new_gem/lib/fluent/plugin/formatter.rb.erb +14 -0
  262. data/templates/new_gem/lib/fluent/plugin/input.rb.erb +11 -0
  263. data/templates/new_gem/lib/fluent/plugin/output.rb.erb +11 -0
  264. data/templates/new_gem/lib/fluent/plugin/parser.rb.erb +15 -0
  265. data/templates/new_gem/test/helper.rb.erb +8 -0
  266. data/templates/new_gem/test/plugin/test_filter.rb.erb +18 -0
  267. data/templates/new_gem/test/plugin/test_formatter.rb.erb +18 -0
  268. data/templates/new_gem/test/plugin/test_input.rb.erb +18 -0
  269. data/templates/new_gem/test/plugin/test_output.rb.erb +18 -0
  270. data/templates/new_gem/test/plugin/test_parser.rb.erb +18 -0
  271. data/templates/plugin_config_formatter/param.md-compact.erb +25 -0
  272. data/templates/plugin_config_formatter/param.md.erb +34 -0
  273. data/templates/plugin_config_formatter/section.md.erb +12 -0
  274. data/test/command/test_binlog_reader.rb +346 -0
  275. data/test/command/test_ca_generate.rb +70 -0
  276. data/test/command/test_fluentd.rb +901 -0
  277. data/test/command/test_plugin_config_formatter.rb +276 -0
  278. data/test/command/test_plugin_generator.rb +92 -0
  279. data/test/compat/test_calls_super.rb +166 -0
  280. data/test/compat/test_parser.rb +92 -0
  281. data/test/config/test_config_parser.rb +126 -2
  282. data/test/config/test_configurable.rb +946 -187
  283. data/test/config/test_configure_proxy.rb +424 -74
  284. data/test/config/test_dsl.rb +11 -11
  285. data/test/config/test_element.rb +500 -0
  286. data/test/config/test_literal_parser.rb +8 -0
  287. data/test/config/test_plugin_configuration.rb +56 -0
  288. data/test/config/test_section.rb +79 -7
  289. data/test/config/test_system_config.rb +122 -35
  290. data/test/config/test_types.rb +38 -0
  291. data/test/counter/test_client.rb +559 -0
  292. data/test/counter/test_error.rb +44 -0
  293. data/test/counter/test_mutex_hash.rb +179 -0
  294. data/test/counter/test_server.rb +589 -0
  295. data/test/counter/test_store.rb +258 -0
  296. data/test/counter/test_validator.rb +137 -0
  297. data/test/helper.rb +89 -6
  298. data/test/helpers/fuzzy_assert.rb +89 -0
  299. data/test/plugin/test_bare_output.rb +118 -0
  300. data/test/plugin/test_base.rb +115 -0
  301. data/test/plugin/test_buf_file.rb +823 -460
  302. data/test/plugin/test_buf_memory.rb +32 -194
  303. data/test/plugin/test_buffer.rb +1233 -0
  304. data/test/plugin/test_buffer_chunk.rb +198 -0
  305. data/test/plugin/test_buffer_file_chunk.rb +844 -0
  306. data/test/plugin/test_buffer_memory_chunk.rb +338 -0
  307. data/test/plugin/test_compressable.rb +84 -0
  308. data/test/plugin/test_filter.rb +357 -0
  309. data/test/plugin/test_filter_grep.rb +540 -29
  310. data/test/plugin/test_filter_parser.rb +439 -452
  311. data/test/plugin/test_filter_record_transformer.rb +123 -166
  312. data/test/plugin/test_filter_stdout.rb +160 -72
  313. data/test/plugin/test_formatter_csv.rb +111 -0
  314. data/test/plugin/test_formatter_hash.rb +35 -0
  315. data/test/plugin/test_formatter_json.rb +51 -0
  316. data/test/plugin/test_formatter_ltsv.rb +62 -0
  317. data/test/plugin/test_formatter_msgpack.rb +28 -0
  318. data/test/plugin/test_formatter_out_file.rb +95 -0
  319. data/test/plugin/test_formatter_single_value.rb +38 -0
  320. data/test/plugin/test_formatter_tsv.rb +68 -0
  321. data/test/plugin/test_in_debug_agent.rb +24 -1
  322. data/test/plugin/test_in_dummy.rb +111 -18
  323. data/test/plugin/test_in_exec.rb +200 -113
  324. data/test/plugin/test_in_forward.rb +990 -387
  325. data/test/plugin/test_in_gc_stat.rb +10 -8
  326. data/test/plugin/test_in_http.rb +600 -224
  327. data/test/plugin/test_in_monitor_agent.rb +690 -0
  328. data/test/plugin/test_in_object_space.rb +24 -8
  329. data/test/plugin/test_in_syslog.rb +154 -215
  330. data/test/plugin/test_in_tail.rb +1006 -707
  331. data/test/plugin/test_in_tcp.rb +125 -48
  332. data/test/plugin/test_in_udp.rb +204 -63
  333. data/test/plugin/{test_in_stream.rb → test_in_unix.rb} +14 -13
  334. data/test/plugin/test_input.rb +126 -0
  335. data/test/plugin/test_metadata.rb +89 -0
  336. data/test/plugin/test_multi_output.rb +180 -0
  337. data/test/plugin/test_out_copy.rb +117 -112
  338. data/test/plugin/test_out_exec.rb +258 -53
  339. data/test/plugin/test_out_exec_filter.rb +538 -115
  340. data/test/plugin/test_out_file.rb +865 -178
  341. data/test/plugin/test_out_forward.rb +998 -210
  342. data/test/plugin/test_out_null.rb +105 -0
  343. data/test/plugin/test_out_relabel.rb +28 -0
  344. data/test/plugin/test_out_roundrobin.rb +36 -29
  345. data/test/plugin/test_out_secondary_file.rb +458 -0
  346. data/test/plugin/test_out_stdout.rb +135 -37
  347. data/test/plugin/test_out_stream.rb +18 -0
  348. data/test/plugin/test_output.rb +984 -0
  349. data/test/plugin/test_output_as_buffered.rb +2021 -0
  350. data/test/plugin/test_output_as_buffered_backup.rb +312 -0
  351. data/test/plugin/test_output_as_buffered_compress.rb +165 -0
  352. data/test/plugin/test_output_as_buffered_overflow.rb +250 -0
  353. data/test/plugin/test_output_as_buffered_retries.rb +911 -0
  354. data/test/plugin/test_output_as_buffered_secondary.rb +874 -0
  355. data/test/plugin/test_output_as_standard.rb +374 -0
  356. data/test/plugin/test_owned_by.rb +35 -0
  357. data/test/plugin/test_parser.rb +359 -0
  358. data/test/plugin/test_parser_apache.rb +42 -0
  359. data/test/plugin/test_parser_apache2.rb +47 -0
  360. data/test/plugin/test_parser_apache_error.rb +45 -0
  361. data/test/plugin/test_parser_csv.rb +103 -0
  362. data/test/plugin/test_parser_json.rb +138 -0
  363. data/test/plugin/test_parser_labeled_tsv.rb +145 -0
  364. data/test/plugin/test_parser_multiline.rb +100 -0
  365. data/test/plugin/test_parser_nginx.rb +88 -0
  366. data/test/plugin/test_parser_none.rb +52 -0
  367. data/test/plugin/test_parser_regexp.rb +289 -0
  368. data/test/plugin/test_parser_syslog.rb +441 -0
  369. data/test/plugin/test_parser_tsv.rb +122 -0
  370. data/test/plugin/test_storage.rb +167 -0
  371. data/test/plugin/test_storage_local.rb +335 -0
  372. data/test/plugin_helper/data/cert/cert-key.pem +27 -0
  373. data/test/plugin_helper/data/cert/cert-with-no-newline.pem +19 -0
  374. data/test/plugin_helper/data/cert/cert.pem +19 -0
  375. data/test/plugin_helper/http_server/test_app.rb +65 -0
  376. data/test/plugin_helper/http_server/test_route.rb +32 -0
  377. data/test/plugin_helper/test_cert_option.rb +16 -0
  378. data/test/plugin_helper/test_child_process.rb +794 -0
  379. data/test/plugin_helper/test_compat_parameters.rb +353 -0
  380. data/test/plugin_helper/test_event_emitter.rb +51 -0
  381. data/test/plugin_helper/test_event_loop.rb +52 -0
  382. data/test/plugin_helper/test_extract.rb +194 -0
  383. data/test/plugin_helper/test_formatter.rb +255 -0
  384. data/test/plugin_helper/test_http_server_helper.rb +205 -0
  385. data/test/plugin_helper/test_inject.rb +519 -0
  386. data/test/plugin_helper/test_parser.rb +264 -0
  387. data/test/plugin_helper/test_record_accessor.rb +197 -0
  388. data/test/plugin_helper/test_retry_state.rb +442 -0
  389. data/test/plugin_helper/test_server.rb +1714 -0
  390. data/test/plugin_helper/test_storage.rb +542 -0
  391. data/test/plugin_helper/test_thread.rb +164 -0
  392. data/test/plugin_helper/test_timer.rb +132 -0
  393. data/test/scripts/exec_script.rb +0 -6
  394. data/test/scripts/fluent/plugin/formatter1/formatter_test1.rb +7 -0
  395. data/test/scripts/fluent/plugin/formatter2/formatter_test2.rb +7 -0
  396. data/test/scripts/fluent/plugin/out_test.rb +23 -15
  397. data/test/scripts/fluent/plugin/out_test2.rb +80 -0
  398. data/test/test_clock.rb +164 -0
  399. data/test/test_config.rb +16 -7
  400. data/test/test_configdsl.rb +2 -2
  401. data/test/test_event.rb +360 -13
  402. data/test/test_event_router.rb +108 -11
  403. data/test/test_event_time.rb +199 -0
  404. data/test/test_filter.rb +48 -6
  405. data/test/test_formatter.rb +11 -391
  406. data/test/test_input.rb +1 -1
  407. data/test/test_log.rb +591 -31
  408. data/test/test_mixin.rb +1 -1
  409. data/test/test_output.rb +121 -185
  410. data/test/test_plugin.rb +251 -0
  411. data/test/test_plugin_classes.rb +177 -10
  412. data/test/test_plugin_helper.rb +81 -0
  413. data/test/test_plugin_id.rb +101 -0
  414. data/test/test_process.rb +8 -42
  415. data/test/test_root_agent.rb +766 -21
  416. data/test/test_supervisor.rb +481 -0
  417. data/test/test_test_drivers.rb +135 -0
  418. data/test/test_time_formatter.rb +282 -0
  419. data/test/test_time_parser.rb +231 -0
  420. data/test/test_unique_id.rb +47 -0
  421. metadata +454 -60
  422. data/COPYING +0 -14
  423. data/ChangeLog +0 -666
  424. data/lib/fluent/buffer.rb +0 -365
  425. data/lib/fluent/plugin/in_status.rb +0 -76
  426. data/test/plugin/test_in_status.rb +0 -38
  427. data/test/test_buffer.rb +0 -624
  428. data/test/test_parser.rb +0 -1305
@@ -16,18 +16,42 @@
16
16
 
17
17
  require 'cool.io'
18
18
 
19
- require 'fluent/input'
19
+ require 'fluent/plugin/input'
20
20
  require 'fluent/config/error'
21
21
  require 'fluent/event'
22
+ require 'fluent/plugin/buffer'
23
+ require 'fluent/plugin/parser_multiline'
22
24
 
23
- module Fluent
24
- class NewTailInput < Input
25
- Plugin.register_input('tail', self)
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
+ class WatcherSetupError < StandardError
38
+ def initialize(msg)
39
+ @message = msg
40
+ end
41
+
42
+ def to_s
43
+ @message
44
+ end
45
+ end
46
+
47
+ FILE_PERMISSION = 0644
26
48
 
27
49
  def initialize
28
50
  super
29
51
  @paths = []
30
52
  @tails = {}
53
+ @pf_file = nil
54
+ @pf = nil
31
55
  @ignore_list = []
32
56
  end
33
57
 
@@ -43,6 +67,9 @@ module Fluent
43
67
  config_param :pos_file, :string, default: nil
44
68
  desc 'Start to read the logs from the head of file, not bottom.'
45
69
  config_param :read_from_head, :bool, default: false
70
+ # When the program deletes log file and re-creates log file with same filename after passed refresh_interval,
71
+ # in_tail may raise a pos_file related error. This is a known issue but there is no such program on production.
72
+ # If we find such program / application, we will fix the problem.
46
73
  desc 'The interval of refreshing the list of watch file.'
47
74
  config_param :refresh_interval, :time, default: 60
48
75
  desc 'The number of reading lines at each IO.'
@@ -53,12 +80,16 @@ module Fluent
53
80
  config_param :emit_unmatched_lines, :bool, default: false
54
81
  desc 'Enable the additional watch timer.'
55
82
  config_param :enable_watch_timer, :bool, default: true
83
+ desc 'Enable the stat watcher based on inotify.'
84
+ config_param :enable_stat_watcher, :bool, default: true
56
85
  desc 'The encoding after conversion of the input.'
57
86
  config_param :encoding, :string, default: nil
58
87
  desc 'The encoding of the input.'
59
88
  config_param :from_encoding, :string, default: nil
60
89
  desc 'Add the log path being tailed to records. Specify the field name to be used.'
61
90
  config_param :path_key, :string, default: nil
91
+ desc 'Open and close the file on every update instead of leaving it open until it gets rotated.'
92
+ config_param :open_on_every_update, :bool, default: false
62
93
  desc 'Limit the watching files that the modification time is within the specified time range (when use \'*\' in path).'
63
94
  config_param :limit_recently_modified, :time, default: nil
64
95
  desc 'Enable the option to skip the refresh of watching list on startup.'
@@ -68,34 +99,56 @@ module Fluent
68
99
 
69
100
  attr_reader :paths
70
101
 
102
+ @@pos_file_paths = {}
103
+
71
104
  def configure(conf)
105
+ compat_parameters_convert(conf, :parser)
106
+ parser_config = conf.elements('parse').first
107
+ unless parser_config
108
+ raise Fluent::ConfigError, "<parse> section is required."
109
+ end
110
+ unless parser_config["@type"]
111
+ raise Fluent::ConfigError, "parse/@type is required."
112
+ end
113
+
114
+ (1..Fluent::Plugin::MultilineParser::FORMAT_MAX_NUM).each do |n|
115
+ parser_config["format#{n}"] = conf["format#{n}"] if conf["format#{n}"]
116
+ end
117
+
72
118
  super
73
119
 
120
+ if !@enable_watch_timer && !@enable_stat_watcher
121
+ raise Fluent::ConfigError, "either of enable_watch_timer or enable_stat_watcher must be true"
122
+ end
123
+
74
124
  @paths = @path.split(',').map {|path| path.strip }
75
125
  if @paths.empty?
76
- raise ConfigError, "tail: 'path' parameter is required on tail input"
126
+ raise Fluent::ConfigError, "tail: 'path' parameter is required on tail input"
77
127
  end
78
128
 
79
- unless @pos_file
129
+ # TODO: Use plugin_root_dir and storage plugin to store positions if available
130
+ if @pos_file
131
+ if @@pos_file_paths.has_key?(@pos_file) && !called_in_test?
132
+ plugin_id_using_this_path = @@pos_file_paths[@pos_file]
133
+ raise Fluent::ConfigError, "Other 'in_tail' plugin already use same pos_file path: plugin_id = #{plugin_id_using_this_path}, pos_file path = #{@pos_file}"
134
+ end
135
+ @@pos_file_paths[@pos_file] = self.plugin_id
136
+ else
80
137
  $log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
81
138
  $log.warn "this parameter is highly recommended to save the position to resume tailing."
82
139
  end
83
140
 
84
- configure_parser(conf)
85
141
  configure_tag
86
142
  configure_encoding
87
143
 
88
- @multiline_mode = conf['format'] =~ /multiline/
144
+ @multiline_mode = parser_config["@type"] =~ /multiline/
89
145
  @receive_handler = if @multiline_mode
90
146
  method(:parse_multilines)
91
147
  else
92
148
  method(:parse_singleline)
93
149
  end
94
- end
95
-
96
- def configure_parser(conf)
97
- @parser = Plugin.new_parser(conf['format'])
98
- @parser.configure(conf)
150
+ @file_perm = system_config.file_permission || FILE_PERMISSION
151
+ @parser = parser_create(conf: parser_config)
99
152
  end
100
153
 
101
154
  def configure_tag
@@ -112,44 +165,52 @@ module Fluent
112
165
  def configure_encoding
113
166
  unless @encoding
114
167
  if @from_encoding
115
- raise ConfigError, "tail: 'from_encoding' parameter must be specified with 'encoding' parameter."
168
+ raise Fluent::ConfigError, "tail: 'from_encoding' parameter must be specified with 'encoding' parameter."
116
169
  end
117
170
  end
118
171
 
119
172
  @encoding = parse_encoding_param(@encoding) if @encoding
120
173
  @from_encoding = parse_encoding_param(@from_encoding) if @from_encoding
174
+ if @encoding && (@encoding == @from_encoding)
175
+ log.warn "'encoding' and 'from_encoding' are same encoding. No effect"
176
+ end
121
177
  end
122
178
 
123
179
  def parse_encoding_param(encoding_name)
124
180
  begin
125
181
  Encoding.find(encoding_name) if encoding_name
126
182
  rescue ArgumentError => e
127
- raise ConfigError, e.message
183
+ raise Fluent::ConfigError, e.message
128
184
  end
129
185
  end
130
186
 
131
187
  def start
188
+ super
189
+
132
190
  if @pos_file
133
- @pf_file = File.open(@pos_file, File::RDWR|File::CREAT, DEFAULT_FILE_PERMISSION)
191
+ pos_file_dir = File.dirname(@pos_file)
192
+ FileUtils.mkdir_p(pos_file_dir) unless Dir.exist?(pos_file_dir)
193
+ @pf_file = File.open(@pos_file, File::RDWR|File::CREAT|File::BINARY, @file_perm)
134
194
  @pf_file.sync = true
135
195
  @pf = PositionFile.parse(@pf_file)
136
196
  end
137
197
 
138
- @loop = Coolio::Loop.new
139
198
  refresh_watchers unless @skip_refresh_on_startup
140
-
141
- @refresh_trigger = TailWatcher::TimerWatcher.new(@refresh_interval, true, log, &method(:refresh_watchers))
142
- @refresh_trigger.attach(@loop)
143
- @thread = Thread.new(&method(:run))
199
+ timer_execute(:in_tail_refresh_watchers, @refresh_interval, &method(:refresh_watchers))
144
200
  end
145
201
 
146
202
  def shutdown
147
- @refresh_trigger.detach if @refresh_trigger && @refresh_trigger.attached?
148
-
149
- stop_watchers(@tails.keys, true)
150
- @loop.stop rescue nil # when all watchers are detached, `stop` raises RuntimeError. We can ignore this exception.
151
- @thread.join
203
+ # during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
204
+ stop_watchers(@tails.keys, immediate: true, remove_watcher: false)
152
205
  @pf_file.close if @pf_file
206
+
207
+ super
208
+ end
209
+
210
+ def close
211
+ super
212
+ # close file handles after all threads stopped (in #close of thread plugin helper)
213
+ close_watcher_handles
153
214
  end
154
215
 
155
216
  def expand_paths
@@ -160,20 +221,25 @@ module Fluent
160
221
  path = date.strftime(path)
161
222
  if path.include?('*')
162
223
  paths += Dir.glob(path).select { |p|
163
- is_file = !File.directory?(p)
164
- if File.readable?(p) && is_file
165
- if @limit_recently_modified && File.mtime(p) < (date - @limit_recently_modified)
166
- false
224
+ begin
225
+ is_file = !File.directory?(p)
226
+ if File.readable?(p) && is_file
227
+ if @limit_recently_modified && File.mtime(p) < (date - @limit_recently_modified)
228
+ false
229
+ else
230
+ true
231
+ end
167
232
  else
168
- true
169
- end
170
- else
171
- if is_file
172
- unless @ignore_list.include?(path)
173
- log.warn "#{p} unreadable. It is excluded and would be examined next time."
174
- @ignore_list << path if @ignore_repeated_permission_error
233
+ if is_file
234
+ unless @ignore_list.include?(path)
235
+ log.warn "#{p} unreadable. It is excluded and would be examined next time."
236
+ @ignore_list << path if @ignore_repeated_permission_error
237
+ end
175
238
  end
239
+ false
176
240
  end
241
+ rescue Errno::ENOENT
242
+ log.debug("#{p} is missing after refresh file list")
177
243
  false
178
244
  end
179
245
  }
@@ -195,18 +261,32 @@ module Fluent
195
261
  target_paths = expand_paths
196
262
  existence_paths = @tails.keys
197
263
 
264
+ log.debug { "tailing paths: target = #{target_paths.join(",")} | existing = #{existence_paths.join(",")}" }
265
+
198
266
  unwatched = existence_paths - target_paths
199
267
  added = target_paths - existence_paths
200
268
 
201
- stop_watchers(unwatched, false, true) unless unwatched.empty?
269
+ stop_watchers(unwatched, immediate: false, unwatched: true) unless unwatched.empty?
202
270
  start_watchers(added) unless added.empty?
203
271
  end
204
272
 
205
273
  def setup_watcher(path, pe)
206
274
  line_buffer_timer_flusher = (@multiline_mode && @multiline_flush_interval) ? TailWatcher::LineBufferTimerFlusher.new(log, @multiline_flush_interval, &method(:flush_buffer)) : nil
207
- tw = TailWatcher.new(path, @rotate_wait, pe, log, @read_from_head, @enable_watch_timer, @read_lines_limit, method(:update_watcher), line_buffer_timer_flusher, &method(:receive_lines))
208
- tw.attach(@loop)
275
+ tw = TailWatcher.new(path, @rotate_wait, pe, log, @read_from_head, @enable_watch_timer, @enable_stat_watcher, @read_lines_limit, method(:update_watcher), line_buffer_timer_flusher, @from_encoding, @encoding, open_on_every_update, &method(:receive_lines))
276
+ tw.attach do |watcher|
277
+ event_loop_attach(watcher.timer_trigger) if watcher.timer_trigger
278
+ event_loop_attach(watcher.stat_trigger) if watcher.stat_trigger
279
+ end
209
280
  tw
281
+ rescue => e
282
+ if tw
283
+ tw.detach { |watcher|
284
+ event_loop_detach(watcher.timer_trigger) if watcher.timer_trigger
285
+ event_loop_detach(watcher.stat_trigger) if watcher.stat_trigger
286
+ }
287
+ tw.close
288
+ end
289
+ raise e
210
290
  end
211
291
 
212
292
  def start_watchers(paths)
@@ -216,65 +296,86 @@ module Fluent
216
296
  pe = @pf[path]
217
297
  if @read_from_head && pe.read_inode.zero?
218
298
  begin
219
- pe.update(File::Stat.new(path).ino, 0)
299
+ pe.update(Fluent::FileWrapper.stat(path).ino, 0)
220
300
  rescue Errno::ENOENT
221
301
  $log.warn "#{path} not found. Continuing without tailing it."
222
302
  end
223
303
  end
224
304
  end
225
305
 
226
- @tails[path] = setup_watcher(path, pe)
306
+ begin
307
+ tw = setup_watcher(path, pe)
308
+ rescue WatcherSetupError => e
309
+ log.warn "Skip #{path} because unexpected setup error happens: #{e}"
310
+ next
311
+ end
312
+ @tails[path] = tw
227
313
  }
228
314
  end
229
315
 
230
- def stop_watchers(paths, immediate = false, unwatched = false)
316
+ def stop_watchers(paths, immediate: false, unwatched: false, remove_watcher: true)
231
317
  paths.each { |path|
232
- tw = @tails.delete(path)
318
+ tw = remove_watcher ? @tails.delete(path) : @tails[path]
233
319
  if tw
234
320
  tw.unwatched = unwatched
235
321
  if immediate
236
- close_watcher(tw, false)
322
+ detach_watcher(tw, false)
237
323
  else
238
- close_watcher_after_rotate_wait(tw)
324
+ detach_watcher_after_rotate_wait(tw)
239
325
  end
240
326
  end
241
327
  }
242
328
  end
243
329
 
330
+ def close_watcher_handles
331
+ @tails.keys.each do |path|
332
+ tw = @tails.delete(path)
333
+ if tw
334
+ tw.close
335
+ end
336
+ end
337
+ end
338
+
244
339
  # refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
245
340
  def update_watcher(path, pe)
341
+ if @pf
342
+ unless pe.read_inode == @pf[path].read_inode
343
+ log.debug "Skip update_watcher because watcher has been already updated by other inotify event"
344
+ return
345
+ end
346
+ end
246
347
  rotated_tw = @tails[path]
247
348
  @tails[path] = setup_watcher(path, pe)
248
- close_watcher_after_rotate_wait(rotated_tw) if rotated_tw
349
+ detach_watcher_after_rotate_wait(rotated_tw) if rotated_tw
249
350
  end
250
351
 
251
352
  # TailWatcher#close is called by another thread at shutdown phase.
252
353
  # It causes 'can't modify string; temporarily locked' error in IOHandler
253
354
  # so adding close_io argument to avoid this problem.
254
355
  # At shutdown, IOHandler's io will be released automatically after detached the event loop
255
- def close_watcher(tw, close_io = true)
256
- tw.close(close_io)
356
+ def detach_watcher(tw, close_io = true)
357
+ tw.detach { |watcher|
358
+ event_loop_detach(watcher.timer_trigger) if watcher.timer_trigger
359
+ event_loop_detach(watcher.stat_trigger) if watcher.stat_trigger
360
+ }
361
+ tw.close if close_io
257
362
  flush_buffer(tw)
258
363
  if tw.unwatched && @pf
259
364
  @pf[tw.path].update_pos(PositionFile::UNWATCHED_POSITION)
260
365
  end
261
366
  end
262
367
 
263
- def close_watcher_after_rotate_wait(tw)
264
- closer = TailWatcher::Closer.new(@rotate_wait, tw, log, &method(:close_watcher))
265
- closer.attach(@loop)
368
+ def detach_watcher_after_rotate_wait(tw)
369
+ # Call event_loop_attach/event_loop_detach is high-cost for short-live object.
370
+ # If this has a problem with large number of files, use @_event_loop directly instead of timer_execute.
371
+ timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
372
+ detach_watcher(tw)
373
+ end
266
374
  end
267
375
 
268
376
  def flush_buffer(tw)
269
377
  if lb = tw.line_buffer
270
378
  lb.chomp!
271
- if @encoding
272
- if @from_encoding
273
- lb.encode!(@encoding, @from_encoding)
274
- else
275
- lb.force_encoding(@encoding)
276
- end
277
- end
278
379
  @parser.parse(lb) { |time, record|
279
380
  if time && record
280
381
  tag = if @tag_prefix || @tag_suffix
@@ -291,14 +392,7 @@ module Fluent
291
392
  end
292
393
  end
293
394
 
294
- def run
295
- @loop.run
296
- rescue
297
- log.error "unexpected error", error: $!.to_s
298
- log.error_backtrace
299
- end
300
-
301
- # @return true if no error or unrecoverable error happens in emit action. false if got BufferQueueLimitError
395
+ # @return true if no error or unrecoverable error happens in emit action. false if got BufferOverflowError
302
396
  def receive_lines(lines, tail_watcher)
303
397
  es = @receive_handler.call(lines, tail_watcher)
304
398
  unless es.empty?
@@ -309,7 +403,7 @@ module Fluent
309
403
  end
310
404
  begin
311
405
  router.emit_stream(tag, es)
312
- rescue BufferQueueLimitError
406
+ rescue Fluent::Plugin::Buffer::BufferOverflowError
313
407
  return false
314
408
  rescue
315
409
  # ignore non BufferQueueLimitError errors because in_tail can't recover. Engine shows logs and backtraces.
@@ -323,13 +417,6 @@ module Fluent
323
417
  def convert_line_to_event(line, es, tail_watcher)
324
418
  begin
325
419
  line.chomp! # remove \n
326
- if @encoding
327
- if @from_encoding
328
- line.encode!(@encoding, @from_encoding)
329
- else
330
- line.force_encoding(@encoding)
331
- end
332
- end
333
420
  @parser.parse(line) { |time, record|
334
421
  if time && record
335
422
  record[@path_key] ||= tail_watcher.path unless @path_key.nil?
@@ -338,9 +425,9 @@ module Fluent
338
425
  if @emit_unmatched_lines
339
426
  record = {'unmatched_line' => line}
340
427
  record[@path_key] ||= tail_watcher.path unless @path_key.nil?
341
- es.add(::Fluent::Engine.now, record)
428
+ es.add(Fluent::EventTime.now, record)
342
429
  end
343
- log.warn "pattern not match: #{line.inspect}"
430
+ log.warn "pattern not matched: #{line.inspect}"
344
431
  end
345
432
  }
346
433
  rescue => e
@@ -350,7 +437,7 @@ module Fluent
350
437
  end
351
438
 
352
439
  def parse_singleline(lines, tail_watcher)
353
- es = MultiEventStream.new
440
+ es = Fluent::MultiEventStream.new
354
441
  lines.each { |line|
355
442
  convert_line_to_event(line, es, tail_watcher)
356
443
  }
@@ -359,7 +446,7 @@ module Fluent
359
446
 
360
447
  def parse_multilines(lines, tail_watcher)
361
448
  lb = tail_watcher.line_buffer
362
- es = MultiEventStream.new
449
+ es = Fluent::MultiEventStream.new
363
450
  if @parser.has_firstline?
364
451
  tail_watcher.line_buffer_timer_flusher.reset_timer if tail_watcher.line_buffer_timer_flusher
365
452
  lines.each { |line|
@@ -396,28 +483,35 @@ module Fluent
396
483
  end
397
484
 
398
485
  class TailWatcher
399
- def initialize(path, rotate_wait, pe, log, read_from_head, enable_watch_timer, read_lines_limit, update_watcher, line_buffer_timer_flusher, &receive_lines)
486
+ def initialize(path, rotate_wait, pe, log, read_from_head, enable_watch_timer, enable_stat_watcher, read_lines_limit, update_watcher, line_buffer_timer_flusher, from_encoding, encoding, open_on_every_update, &receive_lines)
400
487
  @path = path
401
488
  @rotate_wait = rotate_wait
402
489
  @pe = pe || MemoryPositionEntry.new
403
490
  @read_from_head = read_from_head
404
491
  @enable_watch_timer = enable_watch_timer
492
+ @enable_stat_watcher = enable_stat_watcher
405
493
  @read_lines_limit = read_lines_limit
406
494
  @receive_lines = receive_lines
407
495
  @update_watcher = update_watcher
408
496
 
409
- @timer_trigger = TimerWatcher.new(1, true, log, &method(:on_notify)) if @enable_watch_timer
497
+ @stat_trigger = @enable_stat_watcher ? StatWatcher.new(self, &method(:on_notify)) : nil
498
+ @timer_trigger = @enable_watch_timer ? TimerTrigger.new(1, log, &method(:on_notify)) : nil
410
499
 
411
- @stat_trigger = StatWatcher.new(path, log, &method(:on_notify))
412
-
413
- @rotate_handler = RotateHandler.new(path, log, &method(:on_rotate))
500
+ @rotate_handler = RotateHandler.new(self, &method(:on_rotate))
414
501
  @io_handler = nil
415
502
  @log = log
416
503
 
417
504
  @line_buffer_timer_flusher = line_buffer_timer_flusher
505
+ @from_encoding = from_encoding
506
+ @encoding = encoding
507
+ @open_on_every_update = open_on_every_update
418
508
  end
419
509
 
420
510
  attr_reader :path
511
+ attr_reader :log, :pe, :read_lines_limit, :open_on_every_update
512
+ attr_reader :from_encoding, :encoding
513
+ attr_reader :stat_trigger, :enable_watch_timer, :enable_stat_watcher
514
+ attr_accessor :timer_trigger
421
515
  attr_accessor :line_buffer, :line_buffer_timer_flusher
422
516
  attr_accessor :unwatched # This is used for removing position entry from PositionFile
423
517
 
@@ -429,37 +523,40 @@ module Fluent
429
523
  @receive_lines.call(lines, self)
430
524
  end
431
525
 
432
- def attach(loop)
433
- @timer_trigger.attach(loop) if @enable_watch_timer
434
- @stat_trigger.attach(loop)
526
+ def attach
435
527
  on_notify
528
+ yield self
436
529
  end
437
530
 
438
531
  def detach
439
- @timer_trigger.detach if @enable_watch_timer && @timer_trigger.attached?
440
- @stat_trigger.detach if @stat_trigger.attached?
532
+ yield self
533
+ @io_handler.on_notify if @io_handler
441
534
  end
442
535
 
443
- def close(close_io = true)
444
- if close_io && @io_handler
445
- @io_handler.on_notify
536
+ def close
537
+ if @io_handler
446
538
  @io_handler.close
539
+ @io_handler = nil
447
540
  end
448
- detach
449
541
  end
450
542
 
451
543
  def on_notify
452
- @rotate_handler.on_notify if @rotate_handler
544
+ begin
545
+ stat = Fluent::FileWrapper.stat(@path)
546
+ rescue Errno::ENOENT
547
+ # moved or deleted
548
+ stat = nil
549
+ end
550
+
551
+ @rotate_handler.on_notify(stat) if @rotate_handler
453
552
  @line_buffer_timer_flusher.on_notify(self) if @line_buffer_timer_flusher
454
- return unless @io_handler
455
- @io_handler.on_notify
553
+ @io_handler.on_notify if @io_handler
456
554
  end
457
555
 
458
- def on_rotate(io)
459
- if @io_handler == nil
460
- if io
556
+ def on_rotate(stat)
557
+ if @io_handler.nil?
558
+ if stat
461
559
  # first time
462
- stat = io.stat
463
560
  fsize = stat.size
464
561
  inode = stat.ino
465
562
 
@@ -469,14 +566,15 @@ module Fluent
469
566
  # assuming following situation:
470
567
  # a) file was once renamed and backed, or
471
568
  # b) symlink or hardlink to the same file is recreated
472
- # in either case, seek to the saved position
473
- pos = @pe.read_pos
569
+ # in either case of a and b, seek to the saved position
570
+ # c) file was once renamed, truncated and then backed
571
+ # in this case, consider it truncated
572
+ @pe.update(inode, 0) if fsize < @pe.read_pos
474
573
  elsif last_inode != 0
475
574
  # this is FilePositionEntry and fluentd once started.
476
575
  # read data from the head of the rotated file.
477
576
  # logs never duplicate because this file is a rotated new file.
478
- pos = 0
479
- @pe.update(inode, pos)
577
+ @pe.update(inode, 0)
480
578
  else
481
579
  # this is MemoryPositionEntry or this is the first time fluentd started.
482
580
  # seek to the end of the any files.
@@ -485,161 +583,224 @@ module Fluent
485
583
  pos = @read_from_head ? 0 : fsize
486
584
  @pe.update(inode, pos)
487
585
  end
488
- io.seek(pos)
489
-
490
- @io_handler = IOHandler.new(io, @pe, @log, @read_lines_limit, &method(:wrap_receive_lines))
586
+ @io_handler = IOHandler.new(self, &method(:wrap_receive_lines))
491
587
  else
492
588
  @io_handler = NullIOHandler.new
493
589
  end
494
590
  else
495
- log_msg = "detected rotation of #{@path}"
496
- log_msg << "; waiting #{@rotate_wait} seconds" if @io_handler.io # wait rotate_time if previous file is exist
497
- @log.info log_msg
591
+ watcher_needs_update = false
498
592
 
499
- if io
500
- stat = io.stat
593
+ if stat
501
594
  inode = stat.ino
502
595
  if inode == @pe.read_inode # truncated
503
- @pe.update_pos(stat.size)
504
- io_handler = IOHandler.new(io, @pe, @log, @read_lines_limit, &method(:wrap_receive_lines))
596
+ @pe.update_pos(0)
505
597
  @io_handler.close
506
- @io_handler = io_handler
507
- elsif @io_handler.io.nil? # There is no previous file. Reuse TailWatcher
508
- @pe.update(inode, io.pos)
509
- io_handler = IOHandler.new(io, @pe, @log, @read_lines_limit, &method(:wrap_receive_lines))
510
- @io_handler = io_handler
598
+ elsif !@io_handler.opened? # There is no previous file. Reuse TailWatcher
599
+ @pe.update(inode, 0)
511
600
  else # file is rotated and new file found
512
- detach
513
- @update_watcher.call(@path, swap_state(@pe))
601
+ watcher_needs_update = true
602
+ # Handle the old log file before renewing TailWatcher [fluentd#1055]
603
+ @io_handler.on_notify
514
604
  end
515
605
  else # file is rotated and new file not found
516
606
  # Clear RotateHandler to avoid duplicated file watch in same path.
517
607
  @rotate_handler = nil
518
- detach
519
- @update_watcher.call(@path, swap_state(@pe))
608
+ watcher_needs_update = true
520
609
  end
521
- end
522
610
 
523
- def swap_state(pe)
524
- # Use MemoryPositionEntry for rotated file temporary
525
- mpe = MemoryPositionEntry.new
526
- mpe.update(pe.read_inode, pe.read_pos)
527
- @pe = mpe
528
- @io_handler.pe = mpe # Don't re-create IOHandler because IOHandler has an internal buffer.
611
+ log_msg = "detected rotation of #{@path}"
612
+ log_msg << "; waiting #{@rotate_wait} seconds" if watcher_needs_update # wait rotate_time if previous file exists
613
+ @log.info log_msg
529
614
 
530
- pe # This pe will be updated in on_rotate after TailWatcher is initialized
615
+ if watcher_needs_update
616
+ @update_watcher.call(@path, swap_state(@pe))
617
+ else
618
+ @io_handler = IOHandler.new(self, &method(:wrap_receive_lines))
619
+ end
531
620
  end
532
621
  end
533
622
 
534
- class TimerWatcher < Coolio::TimerWatcher
535
- def initialize(interval, repeat, log, &callback)
623
+ def swap_state(pe)
624
+ # Use MemoryPositionEntry for rotated file temporary
625
+ mpe = MemoryPositionEntry.new
626
+ mpe.update(pe.read_inode, pe.read_pos)
627
+ @pe = mpe
628
+ pe # This pe will be updated in on_rotate after TailWatcher is initialized
629
+ end
630
+
631
+ class TimerTrigger < Coolio::TimerWatcher
632
+ def initialize(interval, log, &callback)
536
633
  @callback = callback
537
634
  @log = log
538
- super(interval, repeat)
635
+ super(interval, true)
539
636
  end
540
637
 
541
638
  def on_timer
542
639
  @callback.call
543
- rescue
544
- # TODO log?
545
- @log.error $!.to_s
640
+ rescue => e
641
+ @log.error e.to_s
546
642
  @log.error_backtrace
547
643
  end
548
644
  end
549
645
 
550
646
  class StatWatcher < Coolio::StatWatcher
551
- def initialize(path, log, &callback)
647
+ def initialize(watcher, &callback)
648
+ @watcher = watcher
552
649
  @callback = callback
553
- @log = log
554
- super(path)
650
+ super(watcher.path)
555
651
  end
556
652
 
557
653
  def on_change(prev, cur)
558
654
  @callback.call
559
655
  rescue
560
656
  # TODO log?
561
- @log.error $!.to_s
562
- @log.error_backtrace
657
+ @watcher.log.error $!.to_s
658
+ @watcher.log.error_backtrace
659
+ end
660
+ end
661
+
662
+ class FIFO
663
+ def initialize(from_encoding, encoding)
664
+ @from_encoding = from_encoding
665
+ @encoding = encoding
666
+ @need_enc = from_encoding != encoding
667
+ @buffer = ''.force_encoding(from_encoding)
668
+ @eol = "\n".encode(from_encoding).freeze
669
+ end
670
+
671
+ attr_reader :from_encoding, :encoding, :buffer
672
+
673
+ def <<(chunk)
674
+ # Although "chunk" is most likely transient besides String#force_encoding itself
675
+ # won't affect the actual content of it, it is also probable that "chunk" is
676
+ # a reused buffer and changing its encoding causes some problems on the caller side.
677
+ #
678
+ # Actually, the caller here is specific and "chunk" comes from IO#partial with
679
+ # the second argument, which the function always returns as a return value.
680
+ #
681
+ # Feeding a string that has its encoding attribute set to any double-byte or
682
+ # quad-byte encoding to IO#readpartial as the second arguments results in an
683
+ # assertion failure on Ruby < 2.4.0 for unknown reasons.
684
+ orig_encoding = chunk.encoding
685
+ chunk.force_encoding(from_encoding)
686
+ @buffer << chunk
687
+ # Thus the encoding needs to be reverted back here
688
+ chunk.force_encoding(orig_encoding)
689
+ end
690
+
691
+ def convert(s)
692
+ if @need_enc
693
+ s.encode!(@encoding, @from_encoding)
694
+ else
695
+ s
696
+ end
697
+ rescue
698
+ s.encode!(@encoding, @from_encoding, :invalid => :replace, :undef => :replace)
563
699
  end
564
- end
565
700
 
566
- class Closer < Coolio::TimerWatcher
567
- def initialize(interval, tw, log, &callback)
568
- @callback = callback
569
- @tw = tw
570
- @log = log
571
- super(interval, false)
701
+ def next_line
702
+ idx = @buffer.index(@eol)
703
+ convert(@buffer.slice!(0, idx + 1)) unless idx.nil?
572
704
  end
573
705
 
574
- def on_timer
575
- @callback.call(@tw)
576
- rescue => e
577
- @log.error e.to_s
578
- @log.error_backtrace(e.backtrace)
579
- ensure
580
- detach
706
+ def bytesize
707
+ @buffer.bytesize
581
708
  end
582
709
  end
583
710
 
584
711
  class IOHandler
585
- def initialize(io, pe, log, read_lines_limit, first = true, &receive_lines)
586
- @log = log
587
- @log.info "following tail of #{io.path}" if first
588
- @io = io
589
- @pe = pe
590
- @read_lines_limit = read_lines_limit
712
+ def initialize(watcher, &receive_lines)
713
+ @watcher = watcher
591
714
  @receive_lines = receive_lines
592
- @buffer = ''.force_encoding('ASCII-8BIT')
715
+ @fifo = FIFO.new(@watcher.from_encoding || Encoding::ASCII_8BIT, @watcher.encoding || Encoding::ASCII_8BIT)
593
716
  @iobuf = ''.force_encoding('ASCII-8BIT')
594
717
  @lines = []
718
+ @io = nil
719
+ @notify_mutex = Mutex.new
720
+ @watcher.log.info "following tail of #{@watcher.path}"
595
721
  end
596
722
 
597
- attr_reader :io
598
- attr_accessor :pe
599
-
600
723
  def on_notify
601
- begin
602
- read_more = false
724
+ @notify_mutex.synchronize { handle_notify }
725
+ end
603
726
 
604
- if @lines.empty?
605
- begin
606
- while true
607
- if @buffer.empty?
608
- @io.readpartial(2048, @buffer)
609
- else
610
- @buffer << @io.readpartial(2048, @iobuf)
611
- end
612
- while idx = @buffer.index("\n".freeze)
613
- @lines << @buffer.slice!(0, idx + 1)
614
- end
615
- if @lines.size >= @read_lines_limit
616
- # not to use too much memory in case the file is very large
617
- read_more = true
618
- break
727
+ def handle_notify
728
+ with_io do |io|
729
+ begin
730
+ read_more = false
731
+
732
+ if !io.nil? && @lines.empty?
733
+ begin
734
+ while true
735
+ @fifo << io.readpartial(8192, @iobuf)
736
+ while (line = @fifo.next_line)
737
+ @lines << line
738
+ end
739
+ if @lines.size >= @watcher.read_lines_limit
740
+ # not to use too much memory in case the file is very large
741
+ read_more = true
742
+ break
743
+ end
619
744
  end
745
+ rescue EOFError
620
746
  end
621
- rescue EOFError
622
747
  end
623
- end
624
748
 
625
- unless @lines.empty?
626
- if @receive_lines.call(@lines)
627
- @pe.update_pos(@io.pos - @buffer.bytesize)
628
- @lines.clear
629
- else
630
- read_more = false
749
+ unless @lines.empty?
750
+ if @receive_lines.call(@lines)
751
+ @watcher.pe.update_pos(io.pos - @fifo.bytesize)
752
+ @lines.clear
753
+ else
754
+ read_more = false
755
+ end
631
756
  end
632
- end
633
- end while read_more
634
-
635
- rescue
636
- @log.error $!.to_s
637
- @log.error_backtrace
638
- close
757
+ end while read_more
758
+ end
639
759
  end
640
760
 
641
761
  def close
642
- @io.close unless @io.closed?
762
+ if @io && !@io.closed?
763
+ @io.close
764
+ @io = nil
765
+ end
766
+ end
767
+
768
+ def opened?
769
+ !!@io
770
+ end
771
+
772
+ def open
773
+ io = Fluent::FileWrapper.open(@watcher.path)
774
+ io.seek(@watcher.pe.read_pos + @fifo.bytesize)
775
+ io
776
+ rescue RangeError
777
+ io.close if io
778
+ raise WatcherSetupError, "seek error with #{@watcher.path}: file position = #{@watcher.pe.read_pos.to_s(16)}, reading bytesize = #{@fifo.bytesize.to_s(16)}"
779
+ rescue Errno::ENOENT
780
+ nil
781
+ end
782
+
783
+ def with_io
784
+ begin
785
+ if @watcher.open_on_every_update
786
+ io = open
787
+ begin
788
+ yield io
789
+ ensure
790
+ io.close unless io.nil?
791
+ end
792
+ else
793
+ @io ||= open
794
+ yield @io
795
+ end
796
+ rescue WatcherSetupError => e
797
+ close
798
+ raise e
799
+ rescue
800
+ @watcher.log.error $!.to_s
801
+ @watcher.log.error_backtrace
802
+ close
803
+ end
643
804
  end
644
805
  end
645
806
 
@@ -655,48 +816,43 @@ module Fluent
655
816
 
656
817
  def close
657
818
  end
819
+
820
+ def opened?
821
+ false
822
+ end
658
823
  end
659
824
 
660
825
  class RotateHandler
661
- def initialize(path, log, &on_rotate)
662
- @path = path
826
+ def initialize(watcher, &on_rotate)
827
+ @watcher = watcher
663
828
  @inode = nil
664
829
  @fsize = -1 # first
665
830
  @on_rotate = on_rotate
666
- @log = log
667
831
  end
668
832
 
669
- def on_notify
670
- begin
671
- stat = File.stat(@path)
672
- inode = stat.ino
673
- fsize = stat.size
674
- rescue Errno::ENOENT
675
- # moved or deleted
833
+ def on_notify(stat)
834
+ if stat.nil?
676
835
  inode = nil
677
836
  fsize = 0
837
+ else
838
+ inode = stat.ino
839
+ fsize = stat.size
678
840
  end
679
841
 
680
842
  begin
681
843
  if @inode != inode || fsize < @fsize
682
- # rotated or truncated
683
- begin
684
- io = File.open(@path)
685
- rescue Errno::ENOENT
686
- end
687
- @on_rotate.call(io)
844
+ @on_rotate.call(stat)
688
845
  end
689
846
  @inode = inode
690
847
  @fsize = fsize
691
848
  end
692
849
 
693
850
  rescue
694
- @log.error $!.to_s
695
- @log.error_backtrace
851
+ @watcher.log.error $!.to_s
852
+ @watcher.log.error_backtrace
696
853
  end
697
854
  end
698
855
 
699
-
700
856
  class LineBufferTimerFlusher
701
857
  def initialize(log, flush_interval, &flush_method)
702
858
  @log = log
@@ -721,12 +877,12 @@ module Fluent
721
877
  end
722
878
  end
723
879
 
724
-
725
880
  class PositionFile
726
881
  UNWATCHED_POSITION = 0xffffffffffffffff
727
882
 
728
- def initialize(file, map, last_pos)
883
+ def initialize(file, file_mutex, map, last_pos)
729
884
  @file = file
885
+ @file_mutex = file_mutex
730
886
  @map = map
731
887
  @last_pos = last_pos
732
888
  end
@@ -736,31 +892,34 @@ module Fluent
736
892
  return m
737
893
  end
738
894
 
739
- @file.pos = @last_pos
740
- @file.write path
741
- @file.write "\t"
742
- seek = @file.pos
743
- @file.write "0000000000000000\t0000000000000000\n"
744
- @last_pos = @file.pos
745
-
746
- @map[path] = FilePositionEntry.new(@file, seek)
895
+ @file_mutex.synchronize {
896
+ @file.pos = @last_pos
897
+ @file.write "#{path}\t0000000000000000\t0000000000000000\n"
898
+ seek = @last_pos + path.bytesize + 1
899
+ @last_pos = @file.pos
900
+ @map[path] = FilePositionEntry.new(@file, @file_mutex, seek, 0, 0)
901
+ }
747
902
  end
748
903
 
749
904
  def self.parse(file)
750
905
  compact(file)
751
906
 
907
+ file_mutex = Mutex.new
752
908
  map = {}
753
909
  file.pos = 0
754
910
  file.each_line {|line|
755
911
  m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
756
- next unless m
912
+ unless m
913
+ $log.warn "Unparsable line in pos_file: #{line}"
914
+ next
915
+ end
757
916
  path = m[1]
758
917
  pos = m[2].to_i(16)
759
918
  ino = m[3].to_i(16)
760
919
  seek = file.pos - line.bytesize + path.bytesize + 1
761
- map[path] = FilePositionEntry.new(file, seek)
920
+ map[path] = FilePositionEntry.new(file, file_mutex, seek, pos, ino)
762
921
  }
763
- new(file, map, file.pos)
922
+ new(file, file_mutex, map, file.pos)
764
923
  end
765
924
 
766
925
  # Clean up unwatched file entries
@@ -768,7 +927,10 @@ module Fluent
768
927
  file.pos = 0
769
928
  existent_entries = file.each_line.map { |line|
770
929
  m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
771
- next unless m
930
+ unless m
931
+ $log.warn "Unparsable line in pos_file: #{line}"
932
+ next
933
+ end
772
934
  path = m[1]
773
935
  pos = m[2].to_i(16)
774
936
  ino = m[3].to_i(16)
@@ -791,536 +953,37 @@ module Fluent
791
953
  LN_OFFSET = 33
792
954
  SIZE = 34
793
955
 
794
- def initialize(file, seek)
956
+ def initialize(file, file_mutex, seek, pos, inode)
795
957
  @file = file
958
+ @file_mutex = file_mutex
796
959
  @seek = seek
960
+ @pos = pos
961
+ @inode = inode
797
962
  end
798
963
 
799
964
  def update(ino, pos)
800
- @file.pos = @seek
801
- @file.write "%016x\t%016x" % [pos, ino]
802
- end
803
-
804
- def update_pos(pos)
805
- @file.pos = @seek
806
- @file.write "%016x" % pos
807
- end
808
-
809
- def read_inode
810
- @file.pos = @seek + INO_OFFSET
811
- raw = @file.read(16)
812
- raw ? raw.to_i(16) : 0
813
- end
814
-
815
- def read_pos
816
- @file.pos = @seek
817
- raw = @file.read(16)
818
- raw ? raw.to_i(16) : 0
819
- end
820
- end
821
-
822
- class MemoryPositionEntry
823
- def initialize
824
- @pos = 0
825
- @inode = 0
826
- end
827
-
828
- def update(ino, pos)
829
- @inode = ino
965
+ @file_mutex.synchronize {
966
+ @file.pos = @seek
967
+ @file.write "%016x\t%016x" % [pos, ino]
968
+ }
830
969
  @pos = pos
970
+ @inode = ino
831
971
  end
832
972
 
833
973
  def update_pos(pos)
974
+ @file_mutex.synchronize {
975
+ @file.pos = @seek
976
+ @file.write "%016x" % pos
977
+ }
834
978
  @pos = pos
835
979
  end
836
980
 
837
- def read_pos
838
- @pos
839
- end
840
-
841
981
  def read_inode
842
982
  @inode
843
983
  end
844
- end
845
- end
846
-
847
- # This TailInput is for existence plugins which extends old in_tail
848
- # This class will be removed after release v1.
849
- class TailInput < Input
850
- def initialize
851
- super
852
- @paths = []
853
- end
854
-
855
- config_param :path, :string
856
- config_param :tag, :string
857
- config_param :rotate_wait, :time, :default => 5
858
- config_param :pos_file, :string, :default => nil
859
-
860
- attr_reader :paths
861
-
862
- def configure(conf)
863
- super
864
-
865
- @paths = @path.split(',').map {|path| path.strip }
866
- if @paths.empty?
867
- raise ConfigError, "tail: 'path' parameter is required on tail input"
868
- end
869
-
870
- unless @pos_file
871
- $log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
872
- $log.warn "this parameter is highly recommended to save the position to resume tailing."
873
- end
874
-
875
- configure_parser(conf)
876
- end
877
-
878
- def configure_parser(conf)
879
- @parser = TextParser.new
880
- @parser.configure(conf)
881
- end
882
-
883
- def start
884
- if @pos_file
885
- @pf_file = File.open(@pos_file, File::RDWR|File::CREAT, DEFAULT_FILE_PERMISSION)
886
- @pf_file.sync = true
887
- @pf = PositionFile.parse(@pf_file)
888
- end
889
-
890
- @loop = Coolio::Loop.new
891
- @tails = @paths.map {|path|
892
- pe = @pf ? @pf[path] : MemoryPositionEntry.new
893
- tw = TailWatcher.new(path, @rotate_wait, pe, &method(:receive_lines))
894
- tw.log = log
895
- tw
896
- }
897
- @tails.each {|tail|
898
- tail.attach(@loop)
899
- }
900
- @thread = Thread.new(&method(:run))
901
- end
902
-
903
- def shutdown
904
- @tails.each {|tail|
905
- tail.close
906
- }
907
- @loop.stop
908
- @thread.join
909
- @pf_file.close if @pf_file
910
- end
911
-
912
- def run
913
- @loop.run
914
- rescue
915
- log.error "unexpected error", :error=>$!.to_s
916
- log.error_backtrace
917
- end
918
-
919
- def receive_lines(lines)
920
- es = MultiEventStream.new
921
- lines.each {|line|
922
- begin
923
- line.chomp! # remove \n
924
- time, record = parse_line(line)
925
- if time && record
926
- es.add(time, record)
927
- else
928
- log.warn "pattern not match: #{line.inspect}"
929
- end
930
- rescue
931
- log.warn line.dump, :error=>$!.to_s
932
- log.debug_backtrace
933
- end
934
- }
935
-
936
- unless es.empty?
937
- begin
938
- router.emit_stream(@tag, es)
939
- rescue
940
- # ignore errors. Engine shows logs and backtraces.
941
- end
942
- end
943
- end
944
-
945
- def parse_line(line)
946
- return @parser.parse(line)
947
- end
948
-
949
- class TailWatcher
950
- def initialize(path, rotate_wait, pe, &receive_lines)
951
- @path = path
952
- @rotate_wait = rotate_wait
953
- @pe = pe || MemoryPositionEntry.new
954
- @receive_lines = receive_lines
955
-
956
- @rotate_queue = []
957
-
958
- @timer_trigger = TimerWatcher.new(1, true, &method(:on_notify))
959
- @stat_trigger = StatWatcher.new(path, &method(:on_notify))
960
-
961
- @rotate_handler = RotateHandler.new(path, &method(:on_rotate))
962
- @io_handler = nil
963
- @log = $log
964
- end
965
-
966
- # We use accessor approach to assign each logger, not passing log object at initialization,
967
- # because several plugins depend on these internal classes.
968
- # This approach avoids breaking plugins with new log_level option.
969
- attr_accessor :log
970
-
971
- def log=(logger)
972
- @log = logger
973
- @timer_trigger.log = logger
974
- @stat_trigger.log = logger
975
- @rotate_handler.log = logger
976
- end
977
-
978
- def attach(loop)
979
- @timer_trigger.attach(loop)
980
- @stat_trigger.attach(loop)
981
- on_notify
982
- end
983
-
984
- def detach
985
- @timer_trigger.detach if @timer_trigger.attached?
986
- @stat_trigger.detach if @stat_trigger.attached?
987
- end
988
-
989
- def close
990
- @rotate_queue.reject! {|req|
991
- req.io.close
992
- true
993
- }
994
- detach
995
- end
996
-
997
- def on_notify
998
- @rotate_handler.on_notify
999
- return unless @io_handler
1000
- @io_handler.on_notify
1001
-
1002
- # proceeds rotate queue
1003
- return if @rotate_queue.empty?
1004
- @rotate_queue.first.tick
1005
-
1006
- while @rotate_queue.first.ready?
1007
- if io = @rotate_queue.first.io
1008
- stat = io.stat
1009
- inode = stat.ino
1010
- if inode == @pe.read_inode
1011
- # rotated file has the same inode number with the last file.
1012
- # assuming following situation:
1013
- # a) file was once renamed and backed, or
1014
- # b) symlink or hardlink to the same file is recreated
1015
- # in either case, seek to the saved position
1016
- pos = @pe.read_pos
1017
- else
1018
- pos = io.pos
1019
- end
1020
- @pe.update(inode, pos)
1021
- io_handler = IOHandler.new(io, @pe, log, &@receive_lines)
1022
- else
1023
- io_handler = NullIOHandler.new
1024
- end
1025
- @io_handler.close
1026
- @io_handler = io_handler
1027
- @rotate_queue.shift
1028
- break if @rotate_queue.empty?
1029
- end
1030
- end
1031
-
1032
- def on_rotate(io)
1033
- if @io_handler == nil
1034
- if io
1035
- # first time
1036
- stat = io.stat
1037
- fsize = stat.size
1038
- inode = stat.ino
1039
-
1040
- last_inode = @pe.read_inode
1041
- if inode == last_inode
1042
- # seek to the saved position
1043
- pos = @pe.read_pos
1044
- elsif last_inode != 0
1045
- # this is FilePositionEntry and fluentd once started.
1046
- # read data from the head of the rotated file.
1047
- # logs never duplicate because this file is a rotated new file.
1048
- pos = 0
1049
- @pe.update(inode, pos)
1050
- else
1051
- # this is MemoryPositionEntry or this is the first time fluentd started.
1052
- # seek to the end of the any files.
1053
- # logs may duplicate without this seek because it's not sure the file is
1054
- # existent file or rotated new file.
1055
- pos = fsize
1056
- @pe.update(inode, pos)
1057
- end
1058
- io.seek(pos)
1059
-
1060
- @io_handler = IOHandler.new(io, @pe, log, &@receive_lines)
1061
- else
1062
- @io_handler = NullIOHandler.new
1063
- end
1064
-
1065
- else
1066
- if io && @rotate_queue.find {|req| req.io == io }
1067
- return
1068
- end
1069
- last_io = @rotate_queue.empty? ? @io_handler.io : @rotate_queue.last.io
1070
- if last_io == nil
1071
- log.info "detected rotation of #{@path}"
1072
- # rotate imeediately if previous file is nil
1073
- wait = 0
1074
- else
1075
- log.info "detected rotation of #{@path}; waiting #{@rotate_wait} seconds"
1076
- wait = @rotate_wait
1077
- wait -= @rotate_queue.first.wait unless @rotate_queue.empty?
1078
- end
1079
- @rotate_queue << RotationRequest.new(io, wait)
1080
- end
1081
- end
1082
-
1083
- class TimerWatcher < Coolio::TimerWatcher
1084
- def initialize(interval, repeat, &callback)
1085
- @callback = callback
1086
- @log = $log
1087
- super(interval, repeat)
1088
- end
1089
-
1090
- attr_accessor :log
1091
-
1092
- def on_timer
1093
- @callback.call
1094
- rescue
1095
- # TODO log?
1096
- @log.error $!.to_s
1097
- @log.error_backtrace
1098
- end
1099
- end
1100
-
1101
- class StatWatcher < Coolio::StatWatcher
1102
- def initialize(path, &callback)
1103
- @callback = callback
1104
- @log = $log
1105
- super(path)
1106
- end
1107
-
1108
- attr_accessor :log
1109
-
1110
- def on_change(prev, cur)
1111
- @callback.call
1112
- rescue
1113
- # TODO log?
1114
- @log.error $!.to_s
1115
- @log.error_backtrace
1116
- end
1117
- end
1118
-
1119
- class RotationRequest
1120
- def initialize(io, wait)
1121
- @io = io
1122
- @wait = wait
1123
- end
1124
-
1125
- attr_reader :io, :wait
1126
-
1127
- def tick
1128
- @wait -= 1
1129
- end
1130
-
1131
- def ready?
1132
- @wait <= 0
1133
- end
1134
- end
1135
-
1136
- MAX_LINES_AT_ONCE = 1000
1137
-
1138
- class IOHandler
1139
- def initialize(io, pe, log, &receive_lines)
1140
- @log = log
1141
- @log.info "following tail of #{io.path}"
1142
- @io = io
1143
- @pe = pe
1144
- @receive_lines = receive_lines
1145
- @buffer = ''.force_encoding('ASCII-8BIT')
1146
- @iobuf = ''.force_encoding('ASCII-8BIT')
1147
- end
1148
-
1149
- attr_reader :io
1150
-
1151
- def on_notify
1152
- begin
1153
- lines = []
1154
- read_more = false
1155
-
1156
- begin
1157
- while true
1158
- if @buffer.empty?
1159
- @io.read_nonblock(2048, @buffer)
1160
- else
1161
- @buffer << @io.read_nonblock(2048, @iobuf)
1162
- end
1163
- while line = @buffer.slice!(/.*?\n/m)
1164
- lines << line
1165
- end
1166
- if lines.size >= MAX_LINES_AT_ONCE
1167
- # not to use too much memory in case the file is very large
1168
- read_more = true
1169
- break
1170
- end
1171
- end
1172
- rescue EOFError
1173
- end
1174
-
1175
- unless lines.empty?
1176
- @receive_lines.call(lines)
1177
- @pe.update_pos(@io.pos - @buffer.bytesize)
1178
- end
1179
-
1180
- end while read_more
1181
-
1182
- rescue
1183
- @log.error $!.to_s
1184
- @log.error_backtrace
1185
- close
1186
- end
1187
-
1188
- def close
1189
- @io.close unless @io.closed?
1190
- end
1191
- end
1192
-
1193
- class NullIOHandler
1194
- def initialize
1195
- end
1196
-
1197
- def io
1198
- end
1199
-
1200
- def on_notify
1201
- end
1202
-
1203
- def close
1204
- end
1205
- end
1206
-
1207
- class RotateHandler
1208
- def initialize(path, &on_rotate)
1209
- @path = path
1210
- @inode = nil
1211
- @fsize = -1 # first
1212
- @on_rotate = on_rotate
1213
- @log = $log
1214
- end
1215
-
1216
- attr_accessor :log
1217
-
1218
- def on_notify
1219
- begin
1220
- io = File.open(@path)
1221
- stat = io.stat
1222
- inode = stat.ino
1223
- fsize = stat.size
1224
- rescue Errno::ENOENT
1225
- # moved or deleted
1226
- inode = nil
1227
- fsize = 0
1228
- end
1229
-
1230
- begin
1231
- if @inode != inode || fsize < @fsize
1232
- # rotated or truncated
1233
- @on_rotate.call(io)
1234
- io = nil
1235
- end
1236
-
1237
- @inode = inode
1238
- @fsize = fsize
1239
- ensure
1240
- io.close if io
1241
- end
1242
-
1243
- rescue
1244
- @log.error $!.to_s
1245
- @log.error_backtrace
1246
- end
1247
- end
1248
- end
1249
-
1250
-
1251
- class PositionFile
1252
- def initialize(file, map, last_pos)
1253
- @file = file
1254
- @map = map
1255
- @last_pos = last_pos
1256
- end
1257
-
1258
- def [](path)
1259
- if m = @map[path]
1260
- return m
1261
- end
1262
-
1263
- @file.pos = @last_pos
1264
- @file.write path
1265
- @file.write "\t"
1266
- seek = @file.pos
1267
- @file.write "0000000000000000\t00000000\n"
1268
- @last_pos = @file.pos
1269
-
1270
- @map[path] = FilePositionEntry.new(@file, seek)
1271
- end
1272
-
1273
- def self.parse(file)
1274
- map = {}
1275
- file.pos = 0
1276
- file.each_line {|line|
1277
- m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
1278
- next unless m
1279
- path = m[1]
1280
- pos = m[2].to_i(16)
1281
- ino = m[3].to_i(16)
1282
- seek = file.pos - line.bytesize + path.bytesize + 1
1283
- map[path] = FilePositionEntry.new(file, seek)
1284
- }
1285
- new(file, map, file.pos)
1286
- end
1287
- end
1288
-
1289
- # pos inode
1290
- # ffffffffffffffff\tffffffff\n
1291
- class FilePositionEntry
1292
- POS_SIZE = 16
1293
- INO_OFFSET = 17
1294
- INO_SIZE = 8
1295
- LN_OFFSET = 25
1296
- SIZE = 26
1297
-
1298
- def initialize(file, seek)
1299
- @file = file
1300
- @seek = seek
1301
- end
1302
-
1303
- def update(ino, pos)
1304
- @file.pos = @seek
1305
- @file.write "%016x\t%08x" % [pos, ino]
1306
- @inode = ino
1307
- end
1308
-
1309
- def update_pos(pos)
1310
- @file.pos = @seek
1311
- @file.write "%016x" % pos
1312
- end
1313
-
1314
- def read_inode
1315
- @file.pos = @seek + INO_OFFSET
1316
- raw = @file.read(8)
1317
- raw ? raw.to_i(16) : 0
1318
- end
1319
984
 
1320
985
  def read_pos
1321
- @file.pos = @seek
1322
- raw = @file.read(16)
1323
- raw ? raw.to_i(16) : 0
986
+ @pos
1324
987
  end
1325
988
  end
1326
989