fluentd 0.12.43 → 0.14.0

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 (253) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +6 -0
  3. data/.gitignore +2 -0
  4. data/.travis.yml +33 -21
  5. data/CONTRIBUTING.md +1 -0
  6. data/ChangeLog +1239 -0
  7. data/README.md +0 -25
  8. data/Rakefile +2 -1
  9. data/Vagrantfile +17 -0
  10. data/appveyor.yml +35 -0
  11. data/example/filter_stdout.conf +5 -5
  12. data/example/in_forward.conf +2 -2
  13. data/example/in_http.conf +2 -2
  14. data/example/in_out_forward.conf +17 -0
  15. data/example/in_syslog.conf +2 -2
  16. data/example/in_tail.conf +2 -2
  17. data/example/in_tcp.conf +2 -2
  18. data/example/in_udp.conf +2 -2
  19. data/example/out_copy.conf +4 -4
  20. data/example/out_file.conf +2 -2
  21. data/example/out_forward.conf +2 -2
  22. data/example/out_forward_buf_file.conf +23 -0
  23. data/example/v0_12_filter.conf +8 -8
  24. data/fluent.conf +29 -0
  25. data/fluentd.gemspec +18 -11
  26. data/lib/fluent/agent.rb +60 -58
  27. data/lib/fluent/command/cat.rb +1 -1
  28. data/lib/fluent/command/debug.rb +7 -5
  29. data/lib/fluent/command/fluentd.rb +97 -2
  30. data/lib/fluent/compat/call_super_mixin.rb +67 -0
  31. data/lib/fluent/compat/filter.rb +50 -0
  32. data/lib/fluent/compat/formatter.rb +109 -0
  33. data/lib/fluent/compat/input.rb +50 -0
  34. data/lib/fluent/compat/output.rb +617 -0
  35. data/lib/fluent/compat/output_chain.rb +60 -0
  36. data/lib/fluent/compat/parser.rb +163 -0
  37. data/lib/fluent/compat/propagate_default.rb +62 -0
  38. data/lib/fluent/config.rb +23 -20
  39. data/lib/fluent/config/configure_proxy.rb +119 -70
  40. data/lib/fluent/config/dsl.rb +5 -18
  41. data/lib/fluent/config/element.rb +72 -8
  42. data/lib/fluent/config/error.rb +0 -3
  43. data/lib/fluent/config/literal_parser.rb +0 -2
  44. data/lib/fluent/config/parser.rb +4 -4
  45. data/lib/fluent/config/section.rb +39 -28
  46. data/lib/fluent/config/types.rb +2 -13
  47. data/lib/fluent/config/v1_parser.rb +1 -3
  48. data/lib/fluent/configurable.rb +48 -16
  49. data/lib/fluent/daemon.rb +15 -0
  50. data/lib/fluent/engine.rb +26 -52
  51. data/lib/fluent/env.rb +6 -4
  52. data/lib/fluent/event.rb +58 -11
  53. data/lib/fluent/event_router.rb +5 -5
  54. data/lib/fluent/filter.rb +2 -50
  55. data/lib/fluent/formatter.rb +4 -293
  56. data/lib/fluent/input.rb +2 -32
  57. data/lib/fluent/label.rb +2 -2
  58. data/lib/fluent/load.rb +3 -2
  59. data/lib/fluent/log.rb +107 -38
  60. data/lib/fluent/match.rb +0 -36
  61. data/lib/fluent/mixin.rb +117 -7
  62. data/lib/fluent/msgpack_factory.rb +62 -0
  63. data/lib/fluent/output.rb +7 -612
  64. data/lib/fluent/output_chain.rb +23 -0
  65. data/lib/fluent/parser.rb +4 -800
  66. data/lib/fluent/plugin.rb +100 -121
  67. data/lib/fluent/plugin/bare_output.rb +63 -0
  68. data/lib/fluent/plugin/base.rb +121 -0
  69. data/lib/fluent/plugin/buf_file.rb +101 -182
  70. data/lib/fluent/plugin/buf_memory.rb +9 -92
  71. data/lib/fluent/plugin/buffer.rb +473 -0
  72. data/lib/fluent/plugin/buffer/chunk.rb +135 -0
  73. data/lib/fluent/plugin/buffer/file_chunk.rb +339 -0
  74. data/lib/fluent/plugin/buffer/memory_chunk.rb +100 -0
  75. data/lib/fluent/plugin/exec_util.rb +80 -75
  76. data/lib/fluent/plugin/file_util.rb +33 -28
  77. data/lib/fluent/plugin/file_wrapper.rb +120 -0
  78. data/lib/fluent/plugin/filter.rb +51 -0
  79. data/lib/fluent/plugin/filter_grep.rb +13 -40
  80. data/lib/fluent/plugin/filter_record_transformer.rb +22 -18
  81. data/lib/fluent/plugin/formatter.rb +93 -0
  82. data/lib/fluent/plugin/formatter_csv.rb +48 -0
  83. data/lib/fluent/plugin/formatter_hash.rb +32 -0
  84. data/lib/fluent/plugin/formatter_json.rb +47 -0
  85. data/lib/fluent/plugin/formatter_ltsv.rb +42 -0
  86. data/lib/fluent/plugin/formatter_msgpack.rb +32 -0
  87. data/lib/fluent/plugin/formatter_out_file.rb +45 -0
  88. data/lib/fluent/plugin/formatter_single_value.rb +34 -0
  89. data/lib/fluent/plugin/formatter_stdout.rb +39 -0
  90. data/lib/fluent/plugin/in_debug_agent.rb +4 -0
  91. data/lib/fluent/plugin/in_dummy.rb +22 -18
  92. data/lib/fluent/plugin/in_exec.rb +18 -8
  93. data/lib/fluent/plugin/in_forward.rb +36 -79
  94. data/lib/fluent/plugin/in_gc_stat.rb +4 -0
  95. data/lib/fluent/plugin/in_http.rb +21 -18
  96. data/lib/fluent/plugin/in_monitor_agent.rb +15 -48
  97. data/lib/fluent/plugin/in_object_space.rb +6 -1
  98. data/lib/fluent/plugin/in_stream.rb +7 -3
  99. data/lib/fluent/plugin/in_syslog.rb +46 -95
  100. data/lib/fluent/plugin/in_tail.rb +58 -640
  101. data/lib/fluent/plugin/in_tcp.rb +8 -1
  102. data/lib/fluent/plugin/in_udp.rb +8 -18
  103. data/lib/fluent/plugin/input.rb +33 -0
  104. data/lib/fluent/plugin/multi_output.rb +95 -0
  105. data/lib/fluent/plugin/out_buffered_null.rb +59 -0
  106. data/lib/fluent/plugin/out_copy.rb +11 -7
  107. data/lib/fluent/plugin/out_exec.rb +15 -11
  108. data/lib/fluent/plugin/out_exec_filter.rb +18 -10
  109. data/lib/fluent/plugin/out_file.rb +34 -5
  110. data/lib/fluent/plugin/out_forward.rb +25 -19
  111. data/lib/fluent/plugin/out_null.rb +0 -14
  112. data/lib/fluent/plugin/out_roundrobin.rb +11 -7
  113. data/lib/fluent/plugin/out_stdout.rb +5 -7
  114. data/lib/fluent/plugin/out_stream.rb +3 -1
  115. data/lib/fluent/plugin/output.rb +979 -0
  116. data/lib/fluent/plugin/owned_by_mixin.rb +42 -0
  117. data/lib/fluent/plugin/parser.rb +244 -0
  118. data/lib/fluent/plugin/parser_apache.rb +24 -0
  119. data/lib/fluent/plugin/parser_apache2.rb +84 -0
  120. data/lib/fluent/plugin/parser_apache_error.rb +21 -0
  121. data/lib/fluent/plugin/parser_csv.rb +31 -0
  122. data/lib/fluent/plugin/parser_json.rb +79 -0
  123. data/lib/fluent/plugin/parser_ltsv.rb +50 -0
  124. data/lib/fluent/plugin/parser_multiline.rb +102 -0
  125. data/lib/fluent/plugin/parser_nginx.rb +24 -0
  126. data/lib/fluent/plugin/parser_none.rb +36 -0
  127. data/lib/fluent/plugin/parser_syslog.rb +82 -0
  128. data/lib/fluent/plugin/parser_tsv.rb +37 -0
  129. data/lib/fluent/plugin/socket_util.rb +119 -117
  130. data/lib/fluent/plugin/storage.rb +84 -0
  131. data/lib/fluent/plugin/storage_local.rb +116 -0
  132. data/lib/fluent/plugin/string_util.rb +16 -13
  133. data/lib/fluent/plugin_helper.rb +39 -0
  134. data/lib/fluent/plugin_helper/child_process.rb +298 -0
  135. data/lib/fluent/plugin_helper/compat_parameters.rb +99 -0
  136. data/lib/fluent/plugin_helper/event_emitter.rb +80 -0
  137. data/lib/fluent/plugin_helper/event_loop.rb +118 -0
  138. data/lib/fluent/plugin_helper/retry_state.rb +177 -0
  139. data/lib/fluent/plugin_helper/storage.rb +308 -0
  140. data/lib/fluent/plugin_helper/thread.rb +147 -0
  141. data/lib/fluent/plugin_helper/timer.rb +85 -0
  142. data/lib/fluent/plugin_id.rb +63 -0
  143. data/lib/fluent/process.rb +21 -30
  144. data/lib/fluent/registry.rb +21 -9
  145. data/lib/fluent/root_agent.rb +115 -40
  146. data/lib/fluent/supervisor.rb +330 -320
  147. data/lib/fluent/system_config.rb +42 -18
  148. data/lib/fluent/test.rb +6 -1
  149. data/lib/fluent/test/base.rb +23 -3
  150. data/lib/fluent/test/driver/base.rb +247 -0
  151. data/lib/fluent/test/driver/event_feeder.rb +98 -0
  152. data/lib/fluent/test/driver/filter.rb +35 -0
  153. data/lib/fluent/test/driver/input.rb +31 -0
  154. data/lib/fluent/test/driver/output.rb +78 -0
  155. data/lib/fluent/test/driver/test_event_router.rb +45 -0
  156. data/lib/fluent/test/filter_test.rb +0 -1
  157. data/lib/fluent/test/formatter_test.rb +2 -1
  158. data/lib/fluent/test/input_test.rb +23 -17
  159. data/lib/fluent/test/output_test.rb +28 -39
  160. data/lib/fluent/test/parser_test.rb +1 -1
  161. data/lib/fluent/time.rb +104 -1
  162. data/lib/fluent/{status.rb → unique_id.rb} +15 -24
  163. data/lib/fluent/version.rb +1 -1
  164. data/lib/fluent/winsvc.rb +72 -0
  165. data/test/compat/test_calls_super.rb +164 -0
  166. data/test/config/test_config_parser.rb +83 -0
  167. data/test/config/test_configurable.rb +547 -274
  168. data/test/config/test_configure_proxy.rb +146 -29
  169. data/test/config/test_dsl.rb +3 -181
  170. data/test/config/test_element.rb +274 -0
  171. data/test/config/test_literal_parser.rb +1 -1
  172. data/test/config/test_section.rb +79 -7
  173. data/test/config/test_system_config.rb +21 -0
  174. data/test/config/test_types.rb +3 -26
  175. data/test/helper.rb +78 -8
  176. data/test/plugin/test_bare_output.rb +118 -0
  177. data/test/plugin/test_base.rb +75 -0
  178. data/test/plugin/test_buf_file.rb +420 -521
  179. data/test/plugin/test_buf_memory.rb +32 -194
  180. data/test/plugin/test_buffer.rb +981 -0
  181. data/test/plugin/test_buffer_chunk.rb +110 -0
  182. data/test/plugin/test_buffer_file_chunk.rb +770 -0
  183. data/test/plugin/test_buffer_memory_chunk.rb +265 -0
  184. data/test/plugin/test_filter.rb +255 -0
  185. data/test/plugin/test_filter_grep.rb +2 -73
  186. data/test/plugin/test_filter_record_transformer.rb +24 -68
  187. data/test/plugin/test_filter_stdout.rb +6 -6
  188. data/test/plugin/test_in_debug_agent.rb +2 -0
  189. data/test/plugin/test_in_dummy.rb +11 -17
  190. data/test/plugin/test_in_exec.rb +6 -25
  191. data/test/plugin/test_in_forward.rb +112 -151
  192. data/test/plugin/test_in_gc_stat.rb +2 -0
  193. data/test/plugin/test_in_http.rb +106 -157
  194. data/test/plugin/test_in_object_space.rb +21 -5
  195. data/test/plugin/test_in_stream.rb +14 -13
  196. data/test/plugin/test_in_syslog.rb +30 -275
  197. data/test/plugin/test_in_tail.rb +95 -282
  198. data/test/plugin/test_in_tcp.rb +14 -0
  199. data/test/plugin/test_in_udp.rb +21 -67
  200. data/test/plugin/test_input.rb +122 -0
  201. data/test/plugin/test_multi_output.rb +180 -0
  202. data/test/plugin/test_out_buffered_null.rb +79 -0
  203. data/test/plugin/test_out_copy.rb +15 -2
  204. data/test/plugin/test_out_exec.rb +75 -25
  205. data/test/plugin/test_out_exec_filter.rb +74 -8
  206. data/test/plugin/test_out_file.rb +61 -7
  207. data/test/plugin/test_out_forward.rb +92 -15
  208. data/test/plugin/test_out_roundrobin.rb +1 -0
  209. data/test/plugin/test_out_stdout.rb +22 -13
  210. data/test/plugin/test_out_stream.rb +18 -0
  211. data/test/plugin/test_output.rb +515 -0
  212. data/test/plugin/test_output_as_buffered.rb +1540 -0
  213. data/test/plugin/test_output_as_buffered_overflow.rb +247 -0
  214. data/test/plugin/test_output_as_buffered_retries.rb +808 -0
  215. data/test/plugin/test_output_as_buffered_secondary.rb +776 -0
  216. data/test/plugin/test_output_as_standard.rb +362 -0
  217. data/test/plugin/test_owned_by.rb +35 -0
  218. data/test/plugin/test_storage.rb +167 -0
  219. data/test/plugin/test_storage_local.rb +8 -0
  220. data/test/plugin_helper/test_child_process.rb +599 -0
  221. data/test/plugin_helper/test_compat_parameters.rb +175 -0
  222. data/test/plugin_helper/test_event_emitter.rb +51 -0
  223. data/test/plugin_helper/test_event_loop.rb +52 -0
  224. data/test/plugin_helper/test_retry_state.rb +399 -0
  225. data/test/plugin_helper/test_storage.rb +411 -0
  226. data/test/plugin_helper/test_thread.rb +164 -0
  227. data/test/plugin_helper/test_timer.rb +100 -0
  228. data/test/scripts/exec_script.rb +0 -6
  229. data/test/scripts/fluent/plugin/out_test.rb +3 -0
  230. data/test/test_config.rb +13 -4
  231. data/test/test_event.rb +24 -13
  232. data/test/test_event_router.rb +8 -7
  233. data/test/test_event_time.rb +187 -0
  234. data/test/test_formatter.rb +13 -51
  235. data/test/test_input.rb +1 -1
  236. data/test/test_log.rb +239 -16
  237. data/test/test_mixin.rb +1 -1
  238. data/test/test_output.rb +53 -66
  239. data/test/test_parser.rb +105 -323
  240. data/test/test_plugin_helper.rb +81 -0
  241. data/test/test_root_agent.rb +4 -52
  242. data/test/test_supervisor.rb +272 -0
  243. data/test/test_unique_id.rb +47 -0
  244. metadata +181 -55
  245. data/CHANGELOG.md +0 -710
  246. data/lib/fluent/buffer.rb +0 -365
  247. data/lib/fluent/plugin/filter_parser.rb +0 -107
  248. data/lib/fluent/plugin/in_status.rb +0 -76
  249. data/lib/fluent/test/helpers.rb +0 -86
  250. data/test/plugin/data/log/foo/bar2 +0 -0
  251. data/test/plugin/test_filter_parser.rb +0 -744
  252. data/test/plugin/test_in_status.rb +0 -38
  253. data/test/test_buffer.rb +0 -624
@@ -39,16 +39,21 @@ module Fluent
39
39
  class ForwardOutput < ObjectBufferedOutput
40
40
  Plugin.register_output('forward', self)
41
41
 
42
+ LISTEN_PORT = 24224
43
+
42
44
  def initialize
43
45
  super
44
46
  require 'fluent/plugin/socket_util'
45
47
  @nodes = [] #=> [Node]
48
+ @loop = nil
49
+ @thread = nil
50
+ @finished = false
46
51
  end
47
52
 
48
53
  desc 'The timeout time when sending event logs.'
49
54
  config_param :send_timeout, :time, default: 60
50
55
  desc 'The transport protocol to use for heartbeats.(udp,tcp,none)'
51
- config_param :heartbeat_type, default: :udp do |val|
56
+ config_param :heartbeat_type, default: :tcp do |val|
52
57
  case val.downcase
53
58
  when 'tcp'
54
59
  :tcp
@@ -86,8 +91,9 @@ module Fluent
86
91
 
87
92
  attr_reader :nodes
88
93
 
89
- config_param :port, :integer, default: DEFAULT_LISTEN_PORT, deprecated: "User <server> host xxx </server> instead."
90
- config_param :host, :string, default: nil, deprecated: "Use <server> port xxx </server> instead."
94
+ # backward compatibility
95
+ config_param :port, :integer, default: LISTEN_PORT
96
+ config_param :host, :string, default: nil
91
97
 
92
98
  attr_accessor :extend_internal_protocol
93
99
 
@@ -96,11 +102,12 @@ module Fluent
96
102
 
97
103
  # backward compatibility
98
104
  if host = conf['host']
105
+ log.warn "'host' option in forward output is obsoleted. Use '<server> host xxx </server>' instead."
99
106
  port = conf['port']
100
- port = port ? port.to_i : DEFAULT_LISTEN_PORT
101
- e = conf.add_element('server')
102
- e['host'] = host
103
- e['port'] = port.to_s
107
+ port = port ? port.to_i : LISTEN_PORT
108
+ element = conf.add_element('server')
109
+ element['host'] = host
110
+ element['port'] = port.to_s
104
111
  end
105
112
 
106
113
  recover_sample_size = @recover_wait / @heartbeat_interval
@@ -123,7 +130,7 @@ module Fluent
123
130
 
124
131
  host = e['host']
125
132
  port = e['port']
126
- port = port ? port.to_i : DEFAULT_LISTEN_PORT
133
+ port = port ? port.to_i : LISTEN_PORT
127
134
 
128
135
  weight = e['weight']
129
136
  weight = weight ? weight.to_i : 60
@@ -186,6 +193,8 @@ module Fluent
186
193
  end
187
194
  @thread.join if @thread
188
195
  @usock.close if @usock
196
+
197
+ super
189
198
  end
190
199
 
191
200
  def run
@@ -291,6 +300,7 @@ module Fluent
291
300
  #sock.write FORWARD_TCP_HEARTBEAT_DATA
292
301
  node.heartbeat(true)
293
302
  ensure
303
+ sock.close_write
294
304
  sock.close
295
305
  end
296
306
  end
@@ -369,6 +379,7 @@ module Fluent
369
379
  node.heartbeat(false)
370
380
  return res # for test
371
381
  ensure
382
+ sock.close_write
372
383
  sock.close
373
384
  end
374
385
  end
@@ -394,23 +405,19 @@ module Fluent
394
405
  def on_timer
395
406
  return if @finished
396
407
  @nodes.each {|n|
408
+ if n.tick
409
+ rebuild_weight_array
410
+ end
397
411
  begin
398
412
  #log.trace "sending heartbeat #{n.host}:#{n.port} on #{@heartbeat_type}"
399
413
  if @heartbeat_type == :tcp
400
- if send_heartbeat_tcp(n)
401
- rebuild_weight_array
402
- end
414
+ send_heartbeat_tcp(n)
403
415
  else
404
416
  @usock.send "\0", 0, Socket.pack_sockaddr_in(n.port, n.resolved_host)
405
417
  end
406
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR, Errno::ECONNREFUSED => e
418
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR, Errno::ECONNREFUSED
407
419
  # TODO log
408
- log.debug "failed to send heartbeat packet to #{n.host}:#{n.port}", error: e.to_s
409
- rescue => e
410
- log.debug "unexpected error happen during heartbeat to #{n.host}:#{n.port}", error: e.to_s
411
- end
412
- if n.tick
413
- rebuild_weight_array
420
+ log.debug "failed to send heartbeat packet to #{n.host}:#{n.port}", error: $!.to_s
414
421
  end
415
422
  }
416
423
  end
@@ -438,7 +445,6 @@ module Fluent
438
445
  end
439
446
 
440
447
  def on_heartbeat(sockaddr, msg)
441
- port, host = Socket.unpack_sockaddr_in(sockaddr)
442
448
  if node = @nodes.find {|n| n.sockaddr == sockaddr }
443
449
  #log.trace "heartbeat from '#{node.name}'", :host=>node.host, :port=>node.port
444
450
  if node.heartbeat
@@ -20,20 +20,6 @@ module Fluent
20
20
  class NullOutput < Output
21
21
  Plugin.register_output('null', self)
22
22
 
23
- def initialize
24
- super
25
- end
26
-
27
- def configure(conf)
28
- super
29
- end
30
-
31
- def start
32
- end
33
-
34
- def shutdown
35
- end
36
-
37
23
  def emit(tag, es, chain)
38
24
  chain.next
39
25
  end
@@ -37,7 +37,7 @@ module Fluent
37
37
  conf.elements.select {|e|
38
38
  e.name == 'store'
39
39
  }.each {|e|
40
- type = e['@type'] || e['type']
40
+ type = e['@type']
41
41
  unless type
42
42
  raise ConfigError, "Missing 'type' parameter on <store> directive"
43
43
  end
@@ -57,17 +57,21 @@ module Fluent
57
57
  end
58
58
 
59
59
  def start
60
+ super
61
+
60
62
  rebuild_weight_array
61
63
 
62
- @outputs.each {|o|
63
- o.start
64
- }
64
+ @outputs.each do |o|
65
+ o.start unless o.started?
66
+ end
65
67
  end
66
68
 
67
69
  def shutdown
68
- @outputs.each {|o|
69
- o.shutdown
70
- }
70
+ @outputs.each do |o|
71
+ o.shutdown unless o.shutdown?
72
+ end
73
+
74
+ super
71
75
  end
72
76
 
73
77
  def emit(tag, es, chain)
@@ -14,28 +14,26 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
- require 'fluent/output'
17
+ require 'fluent/plugin/output'
18
18
 
19
- module Fluent
19
+ module Fluent::Plugin
20
20
  class StdoutOutput < Output
21
- Plugin.register_output('stdout', self)
21
+ Fluent::Plugin.register_output('stdout', self)
22
22
 
23
23
  desc 'Output format.(json,hash)'
24
24
  config_param :output_type, default: 'json'
25
25
 
26
26
  def configure(conf)
27
27
  super
28
- @formatter = Plugin.new_formatter(@output_type)
28
+ @formatter = Fluent::Plugin.new_formatter(@output_type, parent: self)
29
29
  @formatter.configure(conf)
30
30
  end
31
31
 
32
- def emit(tag, es, chain)
32
+ def process(tag, es)
33
33
  es.each {|time,record|
34
34
  $log.write "#{Time.at(time).localtime} #{tag}: #{@formatter.format(tag, time, record).chomp}\n"
35
35
  }
36
36
  $log.flush
37
-
38
- chain.next
39
37
  end
40
38
  end
41
39
  end
@@ -85,13 +85,15 @@ module Fluent
85
85
  class TcpOutput < StreamOutput
86
86
  Plugin.register_output('tcp', self)
87
87
 
88
+ LISTEN_PORT = 24224
89
+
88
90
  def initialize
89
91
  super
90
92
  $log.warn "'tcp' output is obsoleted and will be removed. Use 'forward' instead."
91
93
  $log.warn "see 'forward' section in http://docs.fluentd.org/ for the high-availability configuration."
92
94
  end
93
95
 
94
- config_param :port, :integer, default: DEFAULT_LISTEN_PORT
96
+ config_param :port, :integer, default: LISTEN_PORT
95
97
  config_param :host, :string
96
98
 
97
99
  def configure(conf)
@@ -0,0 +1,979 @@
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/base'
18
+ require 'fluent/log'
19
+ require 'fluent/plugin_id'
20
+ require 'fluent/plugin_helper'
21
+ require 'fluent/timezone'
22
+ require 'fluent/unique_id'
23
+
24
+ require 'time'
25
+ require 'monitor'
26
+
27
+ module Fluent
28
+ module Plugin
29
+ class Output < Base
30
+ include PluginId
31
+ include PluginLoggerMixin
32
+ include PluginHelper::Mixin
33
+ include UniqueId::Mixin
34
+
35
+ helpers :thread, :retry_state
36
+
37
+ CHUNK_KEY_PATTERN = /^[-_.@a-zA-Z0-9]+$/
38
+ CHUNK_KEY_PLACEHOLDER_PATTERN = /\$\{[-_.@a-zA-Z0-9]+\}/
39
+
40
+ config_param :time_as_integer, :bool, default: false
41
+
42
+ # `<buffer>` and `<secondary>` sections are available only when '#format' and '#write' are implemented
43
+ config_section :buffer, param_name: :buffer_config, init: true, required: false, multi: false, final: true do
44
+ config_argument :chunk_keys, :array, value_type: :string, default: []
45
+ config_param :@type, :string, default: 'memory', alias: :type
46
+
47
+ config_param :timekey, :time, default: nil # range size to be used: `time.to_i / @timekey`
48
+ config_param :timekey_wait, :time, default: 600
49
+ # These are for #extract_placeholders
50
+ config_param :timekey_use_utc, :bool, default: false # default is localtime
51
+ config_param :timekey_zone, :string, default: Time.now.strftime('%z') # e.g., "-0700" or "Asia/Tokyo"
52
+
53
+ desc 'If true, plugin will try to flush buffer just before shutdown.'
54
+ config_param :flush_at_shutdown, :bool, default: nil # change default by buffer_plugin.persistent?
55
+
56
+ desc 'How to enqueue chunks to be flushed. "interval" flushes per flush_interval, "immediate" flushes just after event arrival.'
57
+ config_param :flush_mode, :enum, list: [:default, :lazy, :interval, :immediate], default: :default
58
+ config_param :flush_interval, :time, default: 60, desc: 'The interval between buffer chunk flushes.'
59
+
60
+ config_param :flush_thread_count, :integer, default: 1, desc: 'The number of threads to flush the buffer.'
61
+
62
+ config_param :flush_thread_interval, :float, default: 1.0, desc: 'Seconds to sleep between checks for buffer flushes in flush threads.'
63
+ config_param :flush_thread_burst_interval, :float, default: 1.0, desc: 'Seconds to sleep between flushes when many buffer chunks are queued.'
64
+
65
+ config_param :delayed_commit_timeout, :time, default: 60, desc: 'Seconds of timeout for buffer chunks to be committed by plugins later.'
66
+
67
+ config_param :overflow_action, :enum, list: [:throw_exception, :block, :drop_oldest_chunk], default: :throw_exception, desc: 'The action when the size of buffer exceeds the limit.'
68
+
69
+ config_param :retry_forever, :bool, default: false, desc: 'If true, plugin will ignore retry_timeout and retry_max_times options and retry flushing forever.'
70
+ config_param :retry_timeout, :time, default: 72 * 60 * 60, desc: 'The maximum seconds to retry to flush while failing, until plugin discards buffer chunks.'
71
+ # 72hours == 17 times with exponential backoff (not to change default behavior)
72
+ config_param :retry_max_times, :integer, default: nil, desc: 'The maximum number of times to retry to flush while failing.'
73
+
74
+ config_param :retry_secondary_threshold, :float, default: 0.8, desc: 'ratio of retry_timeout to switch to use secondary while failing.'
75
+ # expornential backoff sequence will be initialized at the time of this threshold
76
+
77
+ desc 'How to wait next retry to flush buffer.'
78
+ config_param :retry_type, :enum, list: [:exponential_backoff, :periodic], default: :exponential_backoff
79
+ ### Periodic -> fixed :retry_wait
80
+ ### Exponencial backoff: k is number of retry times
81
+ # c: constant factor, @retry_wait
82
+ # b: base factor, @retry_exponential_backoff_base
83
+ # k: times
84
+ # total retry time: c + c * b^1 + (...) + c*b^k = c*b^(k+1) - 1
85
+ config_param :retry_wait, :time, default: 1, desc: 'Seconds to wait before next retry to flush, or constant factor of exponential backoff.'
86
+ config_param :retry_exponential_backoff_base, :float, default: 2, desc: 'The base number of exponencial backoff for retries.'
87
+ config_param :retry_max_interval, :time, default: nil, desc: 'The maximum interval seconds for exponencial backoff between retries while failing.'
88
+
89
+ config_param :retry_randomize, :bool, default: true, desc: 'If true, output plugin will retry after randomized interval not to do burst retries.'
90
+ end
91
+
92
+ config_section :secondary, param_name: :secondary_config, required: false, multi: false, final: true do
93
+ config_param :@type, :string, default: nil, alias: :type
94
+ config_section :buffer, required: false, multi: false do
95
+ # dummy to detect invalid specification for here
96
+ end
97
+ config_section :secondary, required: false, multi: false do
98
+ # dummy to detect invalid specification for here
99
+ end
100
+ end
101
+
102
+ def process(tag, es)
103
+ raise NotImplementedError, "BUG: output plugins MUST implement this method"
104
+ end
105
+
106
+ def write(chunk)
107
+ raise NotImplementedError, "BUG: output plugins MUST implement this method"
108
+ end
109
+
110
+ def try_write(chunk)
111
+ raise NotImplementedError, "BUG: output plugins MUST implement this method"
112
+ end
113
+
114
+ def format(tag, time, record)
115
+ # standard msgpack_event_stream chunk will be used if this method is not implemented in plugin subclass
116
+ raise NotImplementedError, "BUG: output plugins MUST implement this method"
117
+ end
118
+
119
+ def prefer_buffered_processing
120
+ # override this method to return false only when all of these are true:
121
+ # * plugin has both implementation for buffered and non-buffered methods
122
+ # * plugin is expected to work as non-buffered plugin if no `<buffer>` sections specified
123
+ true
124
+ end
125
+
126
+ def prefer_delayed_commit
127
+ # override this method to decide which is used of `write` or `try_write` if both are implemented
128
+ true
129
+ end
130
+
131
+ # Internal states
132
+ FlushThreadState = Struct.new(:thread, :next_time)
133
+ DequeuedChunkInfo = Struct.new(:chunk_id, :time, :timeout) do
134
+ def expired?
135
+ time + timeout < Time.now
136
+ end
137
+ end
138
+
139
+ attr_reader :as_secondary, :delayed_commit, :delayed_commit_timeout
140
+ attr_reader :num_errors, :emit_count, :emit_records, :write_count, :rollback_count
141
+
142
+ # for tests
143
+ attr_reader :buffer, :retry, :secondary, :chunk_keys, :chunk_key_time, :chunk_key_tag
144
+ attr_accessor :output_enqueue_thread_waiting, :in_tests
145
+
146
+ # output_enqueue_thread_waiting: for test of output.rb itself
147
+ # in_tests: for tests of plugins with test drivers
148
+
149
+ def initialize
150
+ super
151
+ @counters_monitor = Monitor.new
152
+ @buffering = false
153
+ @delayed_commit = false
154
+ @as_secondary = false
155
+ @in_tests = false
156
+ @primary_instance = nil
157
+
158
+ # TODO: well organized counters
159
+ @num_errors = 0
160
+ @emit_count = 0
161
+ @emit_records = 0
162
+ @write_count = 0
163
+ @rollback_count = 0
164
+
165
+ # How to process events is decided here at once, but it will be decided in delayed way on #configure & #start
166
+ if implement?(:synchronous)
167
+ if implement?(:buffered) || implement?(:delayed_commit)
168
+ @buffering = nil # do #configure or #start to determine this for full-featured plugins
169
+ else
170
+ @buffering = false
171
+ end
172
+ else
173
+ @buffering = true
174
+ end
175
+ @custom_format = implement?(:custom_format)
176
+
177
+ @buffer = nil
178
+ @secondary = nil
179
+ @retry = nil
180
+ @dequeued_chunks = nil
181
+ @output_flush_threads = nil
182
+
183
+ @simple_chunking = nil
184
+ @chunk_keys = @chunk_key_time = @chunk_key_tag = nil
185
+ @flush_mode = nil
186
+ end
187
+
188
+ def acts_as_secondary(primary)
189
+ @as_secondary = true
190
+ @primary_instance = primary
191
+ (class << self; self; end).module_eval do
192
+ define_method(:extract_placeholders){ |str, metadata| @primary_instance.extract_placeholders(str, metadata) }
193
+ define_method(:commit_write){ |chunk_id| @primary_instance.commit_write(chunk_id, delayed: delayed_commit, secondary: true) }
194
+ define_method(:rollback_write){ |chunk_id| @primary_instance.rollback_write(chunk_id) }
195
+ end
196
+ end
197
+
198
+ def configure(conf)
199
+ unless implement?(:synchronous) || implement?(:buffered) || implement?(:delayed_commit)
200
+ raise "BUG: output plugin must implement some methods. see developer documents."
201
+ end
202
+
203
+ has_buffer_section = (conf.elements(name: 'buffer').size > 0)
204
+
205
+ super
206
+
207
+ if has_buffer_section
208
+ unless implement?(:buffered) || implement?(:delayed_commit)
209
+ raise Fluent::ConfigError, "<buffer> section is configured, but plugin '#{self.class}' doesn't support buffering"
210
+ end
211
+ @buffering = true
212
+ else # no buffer sections
213
+ if implement?(:synchronous)
214
+ if !implement?(:buffered) && !implement?(:delayed_commit)
215
+ if @as_secondary
216
+ raise Fluent::ConfigError, "secondary plugin '#{self.class}' must support buffering, but doesn't."
217
+ end
218
+ @buffering = false
219
+ else
220
+ if @as_secondary
221
+ # secondary plugin always works as buffered plugin without buffer instance
222
+ @buffering = true
223
+ else
224
+ # @buffering.nil? shows that enabling buffering or not will be decided in lazy way in #start
225
+ @buffering = nil
226
+ end
227
+ end
228
+ else # buffered or delayed_commit is supported by `unless` of first line in this method
229
+ @buffering = true
230
+ end
231
+ end
232
+
233
+ if @as_secondary
234
+ if !@buffering && !@buffering.nil?
235
+ raise Fluent::ConfigError, "secondary plugin '#{self.class}' must support buffering, but doesn't"
236
+ end
237
+ end
238
+
239
+ if (@buffering || @buffering.nil?) && !@as_secondary
240
+ # When @buffering.nil?, @buffer_config was initialized with default value for all parameters.
241
+ # If so, this configuration MUST success.
242
+ @chunk_keys = @buffer_config.chunk_keys.dup
243
+ @chunk_key_time = !!@chunk_keys.delete('time')
244
+ @chunk_key_tag = !!@chunk_keys.delete('tag')
245
+ if @chunk_keys.any?{ |key| key !~ CHUNK_KEY_PATTERN }
246
+ raise Fluent::ConfigError, "chunk_keys specification includes invalid char"
247
+ end
248
+
249
+ if @chunk_key_time
250
+ raise Fluent::ConfigError, "<buffer ...> argument includes 'time', but timekey is not configured" unless @buffer_config.timekey
251
+ Fluent::Timezone.validate!(@buffer_config.timekey_zone)
252
+ @buffer_config.timekey_zone = '+0000' if @buffer_config.timekey_use_utc
253
+ @output_time_formatter_cache = {}
254
+ end
255
+
256
+ # no chunk keys or only tags (chunking can be done without iterating event stream)
257
+ @simple_chunking = !@chunk_key_time && @chunk_keys.empty?
258
+
259
+ @flush_mode = @buffer_config.flush_mode
260
+ if @flush_mode == :default
261
+ @flush_mode = (@chunk_key_time ? :lazy : :interval)
262
+ end
263
+
264
+ buffer_type = @buffer_config[:@type]
265
+ buffer_conf = conf.elements(name: 'buffer').first || Fluent::Config::Element.new('buffer', '', {}, [])
266
+ @buffer = Plugin.new_buffer(buffer_type, parent: self)
267
+ @buffer.configure(buffer_conf)
268
+
269
+ @flush_at_shutdown = @buffer_config.flush_at_shutdown
270
+ if @flush_at_shutdown.nil?
271
+ @flush_at_shutdown = if @buffer.persistent?
272
+ false
273
+ else
274
+ true # flush_at_shutdown is true in default for on-memory buffer
275
+ end
276
+ elsif !@flush_at_shutdown && !@buffer.persistent?
277
+ buf_type = Plugin.lookup_type_from_class(@buffer.class)
278
+ log.warn "'flush_at_shutdown' is false, and buffer plugin '#{buf_type}' is not persistent buffer."
279
+ log.warn "your configuration will lose buffered data at shutdown. please confirm your configuration again."
280
+ end
281
+ end
282
+
283
+ if @secondary_config
284
+ raise Fluent::ConfigError, "Invalid <secondary> section for non-buffered plugin" unless @buffering
285
+ raise Fluent::ConfigError, "<secondary> section cannot have <buffer> section" if @secondary_config.buffer
286
+ raise Fluent::ConfigError, "<secondary> section cannot have <secondary> section" if @secondary_config.secondary
287
+ raise Fluent::ConfigError, "<secondary> section and 'retry_forever' are exclusive" if @buffer_config.retry_forever
288
+
289
+ secondary_type = @secondary_config[:@type]
290
+ secondary_conf = conf.elements(name: 'secondary').first
291
+ @secondary = Plugin.new_output(secondary_type)
292
+ @secondary.acts_as_secondary(self)
293
+ @secondary.configure(secondary_conf)
294
+ @secondary.router = router if @secondary.has_router?
295
+ if self.class != @secondary.class
296
+ log.warn "secondary type should be same with primary one", primary: self.class.to_s, secondary: @secondary.class.to_s
297
+ end
298
+ else
299
+ @secondary = nil
300
+ end
301
+
302
+ self
303
+ end
304
+
305
+ def start
306
+ super
307
+
308
+ if @buffering.nil?
309
+ @buffering = prefer_buffered_processing
310
+ if !@buffering && @buffer
311
+ @buffer.terminate # it's not started, so terminate will be enough
312
+ end
313
+ end
314
+
315
+ if @buffering
316
+ m = method(:emit_buffered)
317
+ (class << self; self; end).module_eval do
318
+ define_method(:emit_events, m)
319
+ end
320
+
321
+ @custom_format = implement?(:custom_format)
322
+ @delayed_commit = if implement?(:buffered) && implement?(:delayed_commit)
323
+ prefer_delayed_commit
324
+ else
325
+ implement?(:delayed_commit)
326
+ end
327
+ @delayed_commit_timeout = @buffer_config.delayed_commit_timeout
328
+ else # !@buffering
329
+ m = method(:emit_sync)
330
+ (class << self; self; end).module_eval do
331
+ define_method(:emit_events, m)
332
+ end
333
+ end
334
+
335
+ if @buffering && !@as_secondary
336
+ @retry = nil
337
+ @retry_mutex = Mutex.new
338
+
339
+ @buffer.start
340
+
341
+ @output_flush_threads = []
342
+ @output_flush_threads_mutex = Mutex.new
343
+ @output_flush_threads_running = true
344
+
345
+ # mainly for test: detect enqueue works as code below:
346
+ # @output.interrupt_flushes
347
+ # # emits
348
+ # @output.enqueue_thread_wait
349
+ @output_flush_interrupted = false
350
+ @output_enqueue_thread_mutex = Mutex.new
351
+ @output_enqueue_thread_waiting = false
352
+
353
+ @dequeued_chunks = []
354
+ @dequeued_chunks_mutex = Mutex.new
355
+
356
+ @buffer_config.flush_thread_count.times do |i|
357
+ thread_title = "flush_thread_#{i}".to_sym
358
+ thread_state = FlushThreadState.new(nil, nil)
359
+ thread = thread_create(thread_title) do
360
+ flush_thread_run(thread_state)
361
+ end
362
+ thread_state.thread = thread
363
+ @output_flush_threads_mutex.synchronize do
364
+ @output_flush_threads << thread_state
365
+ end
366
+ end
367
+ @output_flush_thread_current_position = 0
368
+
369
+ unless @in_tests
370
+ if @flush_mode == :interval || @chunk_key_time
371
+ thread_create(:enqueue_thread, &method(:enqueue_thread_run))
372
+ end
373
+ end
374
+ end
375
+ @secondary.start if @secondary
376
+ end
377
+
378
+ def stop
379
+ @secondary.stop if @secondary
380
+ @buffer.stop if @buffering && @buffer
381
+
382
+ super
383
+ end
384
+
385
+ def before_shutdown
386
+ @secondary.before_shutdown if @secondary
387
+
388
+ if @buffering && @buffer
389
+ if @flush_at_shutdown
390
+ force_flush
391
+ end
392
+ @buffer.before_shutdown
393
+ end
394
+
395
+ super
396
+ end
397
+
398
+ def shutdown
399
+ @secondary.shutdown if @secondary
400
+ @buffer.shutdown if @buffering && @buffer
401
+
402
+ super
403
+ end
404
+
405
+ def after_shutdown
406
+ try_rollback_all if @buffering && !@as_secondary # rollback regardless with @delayed_commit, because secondary may do it
407
+ @secondary.after_shutdown if @secondary
408
+
409
+ if @buffering && @buffer
410
+ @buffer.after_shutdown
411
+
412
+ @output_flush_threads_running = false
413
+ if @output_flush_threads && !@output_flush_threads.empty?
414
+ @output_flush_threads.each do |state|
415
+ state.thread.run if state.thread.alive? # to wakeup thread and make it to stop by itself
416
+ end
417
+ @output_flush_threads.each do |state|
418
+ state.thread.join
419
+ end
420
+ end
421
+ end
422
+
423
+ super
424
+ end
425
+
426
+ def close
427
+ @buffer.close if @buffering && @buffer
428
+ @secondary.close if @secondary
429
+
430
+ super
431
+ end
432
+
433
+ def terminate
434
+ @buffer.terminate if @buffering && @buffer
435
+ @secondary.terminate if @secondary
436
+
437
+ super
438
+ end
439
+
440
+ def support_in_v12_style?(feature)
441
+ # for plugins written in v0.12 styles
442
+ case feature
443
+ when :synchronous then false
444
+ when :buffered then false
445
+ when :delayed_commit then false
446
+ when :custom_format then false
447
+ else
448
+ raise ArgumentError, "unknown feature: #{feature}"
449
+ end
450
+ end
451
+
452
+ def implement?(feature)
453
+ methods_of_plugin = self.class.instance_methods(false)
454
+ case feature
455
+ when :synchronous then methods_of_plugin.include?(:process) || support_in_v12_style?(:synchronous)
456
+ when :buffered then methods_of_plugin.include?(:write) || support_in_v12_style?(:buffered)
457
+ when :delayed_commit then methods_of_plugin.include?(:try_write)
458
+ when :custom_format then methods_of_plugin.include?(:format) || support_in_v12_style?(:custom_format)
459
+ else
460
+ raise ArgumentError, "Unknown feature for output plugin: #{feature}"
461
+ end
462
+ end
463
+
464
+ # TODO: optimize this code
465
+ def extract_placeholders(str, metadata)
466
+ if metadata.timekey.nil? && metadata.tag.nil? && metadata.variables.nil?
467
+ str
468
+ else
469
+ rvalue = str
470
+ # strftime formatting
471
+ if @chunk_key_time # this section MUST be earlier than rest to use raw 'str'
472
+ @output_time_formatter_cache[str] ||= Fluent::Timezone.formatter(@buffer_config.timekey_zone, str)
473
+ rvalue = @output_time_formatter_cache[str].call(metadata.timekey)
474
+ end
475
+ # ${tag}, ${tag[0]}, ${tag[1]}, ...
476
+ if @chunk_key_tag
477
+ if str =~ /\$\{tag\[\d+\]\}/
478
+ hash = {'${tag}' => metadata.tag}
479
+ metadata.tag.split('.').each_with_index do |part, i|
480
+ hash["${tag[#{i}]}"] = part
481
+ end
482
+ rvalue = rvalue.gsub(/\$\{tag(\[\d+\])?\}/, hash)
483
+ elsif str.include?('${tag}')
484
+ rvalue = rvalue.gsub('${tag}', metadata.tag)
485
+ end
486
+ end
487
+ # ${a_chunk_key}, ...
488
+ if !@chunk_keys.empty? && metadata.variables
489
+ hash = {'${tag}' => '${tag}'} # not to erase this wrongly
490
+ @chunk_keys.each do |key|
491
+ hash["${#{key}}"] = metadata.variables[key.to_sym]
492
+ end
493
+ rvalue = rvalue.gsub(CHUNK_KEY_PLACEHOLDER_PATTERN, hash)
494
+ end
495
+ rvalue
496
+ end
497
+ end
498
+
499
+ def emit_events(tag, es)
500
+ # actually this method will be overwritten by #configure
501
+ if @buffering
502
+ emit_buffered(tag, es)
503
+ else
504
+ emit_sync(tag, es)
505
+ end
506
+ end
507
+
508
+ def emit_sync(tag, es)
509
+ @counters_monitor.synchronize{ @emit_count += 1 }
510
+ begin
511
+ process(tag, es)
512
+ @counters_monitor.synchronize{ @emit_records += es.size }
513
+ rescue
514
+ @counters_monitor.synchronize{ @num_errors += 1 }
515
+ raise
516
+ end
517
+ end
518
+
519
+ def emit_buffered(tag, es)
520
+ @counters_monitor.synchronize{ @emit_count += 1 }
521
+ begin
522
+ execute_chunking(tag, es, enqueue: (@flush_mode == :immediate))
523
+ if !@retry && @buffer.queued?
524
+ submit_flush_once
525
+ end
526
+ rescue
527
+ # TODO: separate number of errors into emit errors and write/flush errors
528
+ @counters_monitor.synchronize{ @num_errors += 1 }
529
+ raise
530
+ end
531
+ end
532
+
533
+ # TODO: optimize this code
534
+ def metadata(tag, time, record)
535
+ # this arguments are ordered in output plugin's rule
536
+ # Metadata 's argument order is different from this one (timekey, tag, variables)
537
+
538
+ raise ArgumentError, "tag must be a String: #{tag.class}" unless tag.nil? || tag.is_a?(String)
539
+ raise ArgumentError, "time must be a Fluent::EventTime (or Integer): #{time.class}" unless time.nil? || time.is_a?(Fluent::EventTime) || time.is_a?(Integer)
540
+ raise ArgumentError, "record must be a Hash: #{record.class}" unless record.nil? || record.is_a?(Hash)
541
+
542
+ if @chunk_keys.nil? && @chunk_key_time.nil? && @chunk_key_tag.nil?
543
+ # for tests
544
+ return Struct.new(:timekey, :tag, :variables).new
545
+ end
546
+
547
+ # timekey is int from epoch, and `timekey - timekey % 60` is assumed to mach with 0s of each minutes.
548
+ # it's wrong if timezone is configured as one which supports leap second, but it's very rare and
549
+ # we can ignore it (especially in production systems).
550
+ if @chunk_keys.empty?
551
+ if !@chunk_key_time && !@chunk_key_tag
552
+ @buffer.metadata()
553
+ elsif @chunk_key_time && @chunk_key_tag
554
+ time_int = time.to_i
555
+ timekey = (time_int - (time_int % @buffer_config.timekey)).to_i
556
+ @buffer.metadata(timekey: timekey, tag: tag)
557
+ elsif @chunk_key_time
558
+ time_int = time.to_i
559
+ timekey = (time_int - (time_int % @buffer_config.timekey)).to_i
560
+ @buffer.metadata(timekey: timekey)
561
+ else
562
+ @buffer.metadata(tag: tag)
563
+ end
564
+ else
565
+ timekey = if @chunk_key_time
566
+ time_int = time.to_i
567
+ (time_int - (time_int % @buffer_config.timekey)).to_i
568
+ else
569
+ nil
570
+ end
571
+ pairs = Hash[@chunk_keys.map{|k| [k.to_sym, record[k]]}]
572
+ @buffer.metadata(timekey: timekey, tag: (@chunk_key_tag ? tag : nil), variables: pairs)
573
+ end
574
+ end
575
+
576
+ def execute_chunking(tag, es, enqueue: false)
577
+ if @simple_chunking
578
+ handle_stream_simple(tag, es, enqueue: enqueue)
579
+ elsif @custom_format
580
+ handle_stream_with_custom_format(tag, es, enqueue: enqueue)
581
+ else
582
+ handle_stream_with_standard_format(tag, es, enqueue: enqueue)
583
+ end
584
+ end
585
+
586
+ def write_guard(&block)
587
+ begin
588
+ block.call
589
+ rescue Fluent::Plugin::Buffer::BufferOverflowError
590
+ log.warn "failed to write data into buffer by buffer overflow"
591
+ case @buffer_config.overflow_action
592
+ when :throw_exception
593
+ raise
594
+ when :block
595
+ log.debug "buffer.write is now blocking"
596
+ until @buffer.storable?
597
+ sleep 1
598
+ end
599
+ log.debug "retrying buffer.write after blocked operation"
600
+ retry
601
+ when :drop_oldest_chunk
602
+ begin
603
+ oldest = @buffer.dequeue_chunk
604
+ if oldest
605
+ log.warn "dropping oldest chunk to make space after buffer overflow", chunk_id: oldest.unique_id
606
+ @buffer.purge_chunk(oldest.unique_id)
607
+ else
608
+ log.error "no queued chunks to be dropped for drop_oldest_chunk"
609
+ end
610
+ rescue
611
+ # ignore any errors
612
+ end
613
+ raise unless @buffer.storable?
614
+ retry
615
+ else
616
+ raise "BUG: unknown overflow_action '#{@buffer_config.overflow_action}'"
617
+ end
618
+ end
619
+ end
620
+
621
+ def handle_stream_with_custom_format(tag, es, enqueue: false)
622
+ meta_and_data = {}
623
+ records = 0
624
+ es.each do |time, record|
625
+ meta = metadata(tag, time, record)
626
+ meta_and_data[meta] ||= []
627
+ meta_and_data[meta] << format(tag, time, record)
628
+ records += 1
629
+ end
630
+ write_guard do
631
+ @buffer.write(meta_and_data, bulk: false, enqueue: enqueue)
632
+ end
633
+ @counters_monitor.synchronize{ @emit_records += records }
634
+ true
635
+ end
636
+
637
+ def handle_stream_with_standard_format(tag, es, enqueue: false)
638
+ meta_and_data = {}
639
+ records = 0
640
+ es.each do |time, record|
641
+ meta = metadata(tag, time, record)
642
+ meta_and_data[meta] ||= MultiEventStream.new
643
+ meta_and_data[meta].add(time, record)
644
+ records += 1
645
+ end
646
+ meta_and_data_bulk = {}
647
+ meta_and_data.each_pair do |meta, m_es|
648
+ meta_and_data_bulk[meta] = [m_es.to_msgpack_stream(time_int: @time_as_integer), m_es.size]
649
+ end
650
+ write_guard do
651
+ @buffer.write(meta_and_data_bulk, bulk: true, enqueue: enqueue)
652
+ end
653
+ @counters_monitor.synchronize{ @emit_records += records }
654
+ true
655
+ end
656
+
657
+ def handle_stream_simple(tag, es, enqueue: false)
658
+ meta = metadata((@chunk_key_tag ? tag : nil), nil, nil)
659
+ records = es.size
660
+ if @custom_format
661
+ records = 0
662
+ es_size = 0
663
+ es_bulk = ''
664
+ es.each do |time,record|
665
+ es_bulk << format(tag, time, record)
666
+ es_size += 1
667
+ records += 1
668
+ end
669
+ else
670
+ es_size = es.size
671
+ es_bulk = es.to_msgpack_stream(time_int: @time_as_integer)
672
+ end
673
+ write_guard do
674
+ @buffer.write({meta => [es_bulk, es_size]}, bulk: true, enqueue: enqueue)
675
+ end
676
+ @counters_monitor.synchronize{ @emit_records += records }
677
+ true
678
+ end
679
+
680
+ def commit_write(chunk_id, delayed: @delayed_commit, secondary: false)
681
+ if delayed
682
+ @dequeued_chunks_mutex.synchronize do
683
+ @dequeued_chunks.delete_if{ |info| info.chunk_id == chunk_id }
684
+ end
685
+ end
686
+ @buffer.purge_chunk(chunk_id)
687
+
688
+ @retry_mutex.synchronize do
689
+ if @retry # success to flush chunks in retries
690
+ if secondary
691
+ log.warn "retry succeeded by secondary.", plugin_id: plugin_id, chunk_id: dump_unique_id_hex(chunk_id)
692
+ else
693
+ log.warn "retry succeeded.", plugin_id: plugin_id, chunk_id: dump_unique_id_hex(chunk_id)
694
+ end
695
+ @retry = nil
696
+ end
697
+ end
698
+ end
699
+
700
+ def rollback_write(chunk_id)
701
+ # This API is to rollback chunks explicitly from plugins.
702
+ # 3rd party plugins can depend it on automatic rollback of #try_rollback_write
703
+ @dequeued_chunks_mutex.synchronize do
704
+ @dequeued_chunks.delete_if{ |info| info.chunk_id == chunk_id }
705
+ end
706
+ # returns true if chunk was rollbacked as expected
707
+ # false if chunk was already flushed and couldn't be rollbacked unexpectedly
708
+ # in many cases, false can be just ignored
709
+ if @buffer.takeback_chunk(chunk_id)
710
+ @counters_monitor.synchronize{ @rollback_count += 1 }
711
+ true
712
+ else
713
+ false
714
+ end
715
+ end
716
+
717
+ def try_rollback_write
718
+ @dequeued_chunks_mutex.synchronize do
719
+ while @dequeued_chunks.first && @dequeued_chunks.first.expired?
720
+ info = @dequeued_chunks.shift
721
+ if @buffer.takeback_chunk(info.chunk_id)
722
+ @counters_monitor.synchronize{ @rollback_count += 1 }
723
+ log.warn "failed to flush the buffer chunk, timeout to commit.", plugin_id: plugin_id, chunk_id: dump_unique_id_hex(info.chunk_id), flushed_at: info.time
724
+ end
725
+ end
726
+ end
727
+ end
728
+
729
+ def try_rollback_all
730
+ return unless @dequeued_chunks
731
+ @dequeued_chunks_mutex.synchronize do
732
+ until @dequeued_chunks.empty?
733
+ info = @dequeued_chunks.shift
734
+ if @buffer.takeback_chunk(info.chunk_id)
735
+ @counters_monitor.synchronize{ @rollback_count += 1 }
736
+ log.info "delayed commit for buffer chunks was cancelled in shutdown", plugin_id: plugin_id, chunk_id: dump_unique_id_hex(info.chunk_id)
737
+ end
738
+ end
739
+ end
740
+ end
741
+
742
+ def next_flush_time
743
+ if @buffer.queued?
744
+ @retry_mutex.synchronize do
745
+ @retry ? @retry.next_time : Time.now + @buffer_config.flush_thread_burst_interval
746
+ end
747
+ else
748
+ Time.now + @buffer_config.flush_thread_interval
749
+ end
750
+ end
751
+
752
+ def try_flush
753
+ chunk = @buffer.dequeue_chunk
754
+ return unless chunk
755
+
756
+ output = self
757
+ using_secondary = false
758
+ if @retry_mutex.synchronize{ @retry && @retry.secondary? }
759
+ output = @secondary
760
+ using_secondary = true
761
+ end
762
+
763
+ unless @custom_format
764
+ chunk.extend ChunkMessagePackEventStreamer
765
+ end
766
+
767
+ begin
768
+ if output.delayed_commit
769
+ @counters_monitor.synchronize{ @write_count += 1 }
770
+ output.try_write(chunk)
771
+ @dequeued_chunks_mutex.synchronize do
772
+ # delayed_commit_timeout for secondary is configured in <buffer> of primary (<secondary> don't get <buffer>)
773
+ @dequeued_chunks << DequeuedChunkInfo.new(chunk.unique_id, Time.now, self.delayed_commit_timeout)
774
+ end
775
+ else # output plugin without delayed purge
776
+ chunk_id = chunk.unique_id
777
+ @counters_monitor.synchronize{ @write_count += 1 }
778
+ output.write(chunk)
779
+ commit_write(chunk_id, secondary: using_secondary)
780
+ end
781
+ rescue => e
782
+ log.debug "taking back chunk for errors.", plugin_id: plugin_id, chunk: dump_unique_id_hex(chunk.unique_id)
783
+ @buffer.takeback_chunk(chunk.unique_id)
784
+
785
+ @retry_mutex.synchronize do
786
+ if @retry
787
+ @counters_monitor.synchronize{ @num_errors += 1 }
788
+ if @retry.limit?
789
+ records = @buffer.queued_records
790
+ log.error "failed to flush the buffer, and hit limit for retries. dropping all chunks in the buffer queue.", plugin_id: plugin_id, retry_times: @retry.steps, records: records, error: e
791
+ log.error_backtrace e.backtrace
792
+ @buffer.clear_queue!
793
+ log.debug "buffer queue cleared", plugin_id: plugin_id
794
+ @retry = nil
795
+ else
796
+ @retry.step
797
+ msg = if using_secondary
798
+ "failed to flush the buffer with secondary output."
799
+ else
800
+ "failed to flush the buffer."
801
+ end
802
+ log.warn msg, plugin_id: plugin_id, retry_time: @retry.steps, next_retry: @retry.next_time, chunk: dump_unique_id_hex(chunk.unique_id), error: e
803
+ log.warn_backtrace e.backtrace
804
+ end
805
+ else
806
+ @retry = retry_state(@buffer_config.retry_randomize)
807
+ @counters_monitor.synchronize{ @num_errors += 1 }
808
+ log.warn "failed to flush the buffer.", plugin_id: plugin_id, retry_time: @retry.steps, next_retry: @retry.next_time, chunk: dump_unique_id_hex(chunk.unique_id), error: e
809
+ log.warn_backtrace e.backtrace
810
+ end
811
+ end
812
+ end
813
+ end
814
+
815
+ def retry_state(randomize)
816
+ if @secondary
817
+ retry_state_create(
818
+ :output_retries, @buffer_config.retry_type, @buffer_config.retry_wait, @buffer_config.retry_timeout,
819
+ forever: @buffer_config.retry_forever, max_steps: @buffer_config.retry_max_times, backoff_base: @buffer_config.retry_exponential_backoff_base,
820
+ max_interval: @buffer_config.retry_max_interval,
821
+ secondary: true, secondary_threshold: @buffer_config.retry_secondary_threshold,
822
+ randomize: randomize
823
+ )
824
+ else
825
+ retry_state_create(
826
+ :output_retries, @buffer_config.retry_type, @buffer_config.retry_wait, @buffer_config.retry_timeout,
827
+ forever: @buffer_config.retry_forever, max_steps: @buffer_config.retry_max_times, backoff_base: @buffer_config.retry_exponential_backoff_base,
828
+ max_interval: @buffer_config.retry_max_interval,
829
+ randomize: randomize
830
+ )
831
+ end
832
+ end
833
+
834
+ def submit_flush_once
835
+ # Without locks: it is rough but enough to select "next" writer selection
836
+ @output_flush_thread_current_position = (@output_flush_thread_current_position + 1) % @buffer_config.flush_thread_count
837
+ state = @output_flush_threads[@output_flush_thread_current_position]
838
+ state.next_time = 0
839
+ state.thread.run
840
+ end
841
+
842
+ def force_flush
843
+ if @buffering
844
+ @buffer.enqueue_all
845
+ submit_flush_all
846
+ end
847
+ end
848
+
849
+ def submit_flush_all
850
+ while !@retry && @buffer.queued?
851
+ submit_flush_once
852
+ sleep @buffer_config.flush_thread_burst_interval
853
+ end
854
+ end
855
+
856
+ # only for tests of output plugin
857
+ def interrupt_flushes
858
+ @output_flush_interrupted = true
859
+ end
860
+
861
+ # only for tests of output plugin
862
+ def enqueue_thread_wait
863
+ @output_enqueue_thread_mutex.synchronize do
864
+ @output_flush_interrupted = false
865
+ @output_enqueue_thread_waiting = true
866
+ end
867
+ require 'timeout'
868
+ Timeout.timeout(10) do
869
+ Thread.pass while @output_enqueue_thread_waiting
870
+ end
871
+ end
872
+
873
+ # only for tests of output plugin
874
+ def flush_thread_wakeup
875
+ @output_flush_threads.each do |state|
876
+ state.next_time = 0
877
+ state.thread.run
878
+ end
879
+ end
880
+
881
+ def enqueue_thread_run
882
+ value_for_interval = nil
883
+ if @flush_mode == :interval
884
+ value_for_interval = @buffer_config.flush_interval
885
+ end
886
+ if @chunk_key_time
887
+ if !value_for_interval || @buffer_config.timekey < value_for_interval
888
+ value_for_interval = @buffer_config.timekey
889
+ end
890
+ end
891
+ unless value_for_interval
892
+ raise "BUG: both of flush_interval and timekey are disabled"
893
+ end
894
+ interval = value_for_interval / 11.0
895
+ if interval < @buffer_config.flush_thread_interval
896
+ interval = @buffer_config.flush_thread_interval
897
+ end
898
+
899
+ begin
900
+ while @output_flush_threads_running
901
+ now_int = Time.now.to_i
902
+ if @output_flush_interrupted
903
+ sleep interval
904
+ next
905
+ end
906
+
907
+ @output_enqueue_thread_mutex.lock
908
+ begin
909
+ if @flush_mode == :interval
910
+ flush_interval = @buffer_config.flush_interval.to_i
911
+ # This block should be done by integer values.
912
+ # If both of flush_interval & flush_thread_interval are 1s, expected actual flush timing is 1.5s.
913
+ # If we use integered values for this comparison, expected actual flush timing is 1.0s.
914
+ @buffer.enqueue_all{ |metadata, chunk| chunk.created_at.to_i + flush_interval <= now_int }
915
+ end
916
+
917
+ if @chunk_key_time
918
+ timekey_unit = @buffer_config.timekey
919
+ timekey_wait = @buffer_config.timekey_wait
920
+ current_timekey = now_int - now_int % timekey_unit
921
+ @buffer.enqueue_all{ |metadata, chunk| metadata.timekey < current_timekey && metadata.timekey + timekey_unit + timekey_wait <= now_int }
922
+ end
923
+ rescue => e
924
+ log.error "unexpected error while checking flushed chunks. ignored.", plugin_id: plugin_id, error_class: e.class, error: e
925
+ log.error_backtrace
926
+ end
927
+ @output_enqueue_thread_waiting = false
928
+ @output_enqueue_thread_mutex.unlock
929
+ sleep interval
930
+ end
931
+ rescue => e
932
+ # normal errors are rescued by inner begin-rescue clause.
933
+ log.error "error on enqueue thread", plugin_id: plugin_id, error_class: e.class, error: e
934
+ log.error_backtrace
935
+ raise
936
+ end
937
+ end
938
+
939
+ def flush_thread_run(state)
940
+ flush_thread_interval = @buffer_config.flush_thread_interval
941
+
942
+ # If the given clock_id is not supported, Errno::EINVAL is raised.
943
+ clock_id = Process::CLOCK_MONOTONIC rescue Process::CLOCK_MONOTONIC_RAW
944
+ state.next_time = Process.clock_gettime(clock_id) + flush_thread_interval
945
+
946
+ begin
947
+ # This thread don't use `thread_current_running?` because this thread should run in `before_shutdown` phase
948
+ while @output_flush_threads_running
949
+ time = Process.clock_gettime(clock_id)
950
+ interval = state.next_time - time
951
+
952
+ if state.next_time <= time
953
+ try_flush
954
+ # next_flush_interval uses flush_thread_interval or flush_thread_burst_interval (or retrying)
955
+ interval = next_flush_time.to_f - Time.now.to_f
956
+ # TODO: if secondary && delayed-commit, next_flush_time will be much longer than expected (because @retry still exists)
957
+ # @retry should be cleard if delayed commit is enabled? Or any other solution?
958
+ state.next_time = Process.clock_gettime(clock_id) + interval
959
+ end
960
+
961
+ if @dequeued_chunks_mutex.synchronize{ !@dequeued_chunks.empty? && @dequeued_chunks.first.expired? }
962
+ unless @output_flush_interrupted
963
+ try_rollback_write
964
+ end
965
+ end
966
+
967
+ sleep interval if interval > 0
968
+ end
969
+ rescue => e
970
+ # normal errors are rescued by output plugins in #try_flush
971
+ # so this rescue section is for critical & unrecoverable errors
972
+ log.error "error on output thread", plugin_id: plugin_id, error_class: e.class, error: e
973
+ log.error_backtrace
974
+ raise
975
+ end
976
+ end
977
+ end
978
+ end
979
+ end