fluentd 0.14.6 → 0.14.7

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +46 -0
  3. data/bin/fluent-binlog-reader +7 -0
  4. data/example/in_dummy_with_compression.conf +23 -0
  5. data/lib/fluent/agent.rb +8 -12
  6. data/lib/fluent/command/binlog_reader.rb +234 -0
  7. data/lib/fluent/command/fluentd.rb +17 -1
  8. data/lib/fluent/compat/file_util.rb +1 -1
  9. data/lib/fluent/compat/output.rb +5 -1
  10. data/lib/fluent/config/configure_proxy.rb +18 -4
  11. data/lib/fluent/config/element.rb +1 -1
  12. data/lib/fluent/config/section.rb +1 -1
  13. data/lib/fluent/config/v1_parser.rb +1 -1
  14. data/lib/fluent/env.rb +1 -0
  15. data/lib/fluent/event.rb +49 -2
  16. data/lib/fluent/event_router.rb +6 -2
  17. data/lib/fluent/label.rb +8 -0
  18. data/lib/fluent/log.rb +30 -1
  19. data/lib/fluent/plugin.rb +1 -1
  20. data/lib/fluent/plugin/base.rb +3 -0
  21. data/lib/fluent/plugin/buf_file.rb +2 -2
  22. data/lib/fluent/plugin/buf_memory.rb +1 -1
  23. data/lib/fluent/plugin/buffer.rb +12 -2
  24. data/lib/fluent/plugin/buffer/chunk.rb +68 -7
  25. data/lib/fluent/plugin/buffer/file_chunk.rb +4 -4
  26. data/lib/fluent/plugin/buffer/memory_chunk.rb +4 -4
  27. data/lib/fluent/plugin/compressable.rb +91 -0
  28. data/lib/fluent/plugin/filter_grep.rb +4 -4
  29. data/lib/fluent/plugin/formatter.rb +2 -2
  30. data/lib/fluent/plugin/formatter_json.rb +2 -1
  31. data/lib/fluent/plugin/formatter_out_file.rb +3 -30
  32. data/lib/fluent/plugin/in_forward.rb +3 -2
  33. data/lib/fluent/plugin/in_monitor_agent.rb +7 -21
  34. data/lib/fluent/plugin/in_syslog.rb +1 -1
  35. data/lib/fluent/plugin/in_tail.rb +10 -2
  36. data/lib/fluent/plugin/multi_output.rb +63 -3
  37. data/lib/fluent/plugin/out_exec.rb +1 -1
  38. data/lib/fluent/plugin/out_file.rb +5 -1
  39. data/lib/fluent/plugin/out_forward.rb +17 -5
  40. data/lib/fluent/plugin/out_stdout.rb +2 -1
  41. data/lib/fluent/plugin/output.rb +205 -19
  42. data/lib/fluent/plugin/parser.rb +5 -49
  43. data/lib/fluent/plugin/parser_apache2.rb +1 -1
  44. data/lib/fluent/plugin/parser_json.rb +4 -4
  45. data/lib/fluent/plugin/parser_multiline.rb +5 -5
  46. data/lib/fluent/plugin/parser_regexp.rb +1 -2
  47. data/lib/fluent/plugin/parser_syslog.rb +2 -2
  48. data/lib/fluent/plugin/storage_local.rb +2 -1
  49. data/lib/fluent/plugin_helper.rb +1 -0
  50. data/lib/fluent/plugin_helper/compat_parameters.rb +39 -21
  51. data/lib/fluent/plugin_helper/extract.rb +92 -0
  52. data/lib/fluent/plugin_helper/inject.rb +10 -12
  53. data/lib/fluent/plugin_helper/thread.rb +23 -3
  54. data/lib/fluent/registry.rb +1 -1
  55. data/lib/fluent/root_agent.rb +2 -1
  56. data/lib/fluent/supervisor.rb +28 -8
  57. data/lib/fluent/test/base.rb +0 -7
  58. data/lib/fluent/test/driver/base.rb +1 -0
  59. data/lib/fluent/test/driver/output.rb +3 -0
  60. data/lib/fluent/test/helpers.rb +18 -0
  61. data/lib/fluent/test/input_test.rb +4 -2
  62. data/lib/fluent/test/log.rb +3 -1
  63. data/lib/fluent/time.rb +232 -1
  64. data/lib/fluent/timezone.rb +1 -1
  65. data/lib/fluent/version.rb +1 -1
  66. data/test/command/test_binlog_reader.rb +351 -0
  67. data/test/config/test_config_parser.rb +6 -0
  68. data/test/config/test_configurable.rb +47 -1
  69. data/test/helper.rb +0 -1
  70. data/test/plugin/test_buffer.rb +22 -2
  71. data/test/plugin/test_buffer_chunk.rb +34 -4
  72. data/test/plugin/test_buffer_file_chunk.rb +73 -0
  73. data/test/plugin/test_buffer_memory_chunk.rb +73 -0
  74. data/test/plugin/test_compressable.rb +81 -0
  75. data/test/plugin/test_formatter_json.rb +14 -1
  76. data/test/plugin/test_in_forward.rb +67 -3
  77. data/test/plugin/test_in_monitor_agent.rb +17 -1
  78. data/test/plugin/test_in_tail.rb +8 -8
  79. data/test/plugin/test_out_file.rb +0 -8
  80. data/test/plugin/test_out_forward.rb +85 -0
  81. data/test/plugin/test_out_secondary_file.rb +20 -12
  82. data/test/plugin/test_out_stdout.rb +11 -10
  83. data/test/plugin/test_output.rb +234 -0
  84. data/test/plugin/test_output_as_buffered.rb +223 -0
  85. data/test/plugin/test_output_as_buffered_compress.rb +165 -0
  86. data/test/plugin/test_parser_json.rb +8 -0
  87. data/test/plugin/test_parser_regexp.rb +1 -1
  88. data/test/plugin_helper/test_child_process.rb +2 -2
  89. data/test/plugin_helper/test_extract.rb +195 -0
  90. data/test/plugin_helper/test_inject.rb +0 -7
  91. data/test/scripts/fluent/plugin/formatter1/formatter_test1.rb +7 -0
  92. data/test/scripts/fluent/plugin/formatter2/formatter_test2.rb +7 -0
  93. data/test/test_event.rb +186 -0
  94. data/test/test_event_router.rb +1 -1
  95. data/test/test_formatter.rb +0 -7
  96. data/test/test_log.rb +121 -0
  97. data/test/test_plugin_classes.rb +62 -0
  98. data/test/test_root_agent.rb +125 -0
  99. data/test/test_supervisor.rb +25 -2
  100. data/test/test_time_formatter.rb +103 -7
  101. data/test/test_time_parser.rb +211 -0
  102. metadata +23 -4
  103. data/test/plugin/test_parser_time.rb +0 -46
@@ -0,0 +1,91 @@
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 'zlib'
18
+
19
+ module Fluent
20
+ module Plugin
21
+ module Compressable
22
+ def compress(data, **kwargs)
23
+ output_io = kwargs[:output_io]
24
+ io = output_io || StringIO.new
25
+ Zlib::GzipWriter.wrap(io) do |gz|
26
+ gz.write data
27
+ end
28
+
29
+ output_io || io.string
30
+ end
31
+
32
+ # compressed_data is String like `compress(data1) + compress(data2) + ... + compress(dataN)`
33
+ # https://www.ruby-forum.com/topic/971591#979503
34
+ def decompress(compressed_data = nil, output_io: nil, input_io: nil)
35
+ case
36
+ when input_io && output_io
37
+ io_decompress(input_io, output_io)
38
+ when input_io
39
+ output_io = StringIO.new
40
+ io = io_decompress(input_io, output_io)
41
+ io.string
42
+ when compressed_data.nil? || compressed_data.empty?
43
+ # check compressed_data(String) is 0 length
44
+ compressed_data
45
+ when output_io
46
+ # exeucte after checking compressed_data is empty or not
47
+ io = StringIO.new(compressed_data)
48
+ io_decompress(io, output_io)
49
+ else
50
+ string_decompress(compressed_data)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def string_decompress(compressed_data)
57
+ io = StringIO.new(compressed_data)
58
+
59
+ out = ''
60
+ loop do
61
+ gz = Zlib::GzipReader.new(io)
62
+ out += gz.read
63
+ unused = gz.unused
64
+ gz.finish
65
+
66
+ break if unused.nil?
67
+ adjust = unused.length
68
+ io.pos -= adjust
69
+ end
70
+
71
+ out
72
+ end
73
+
74
+ def io_decompress(input, output)
75
+ loop do
76
+ gz = Zlib::GzipReader.new(input)
77
+ v = gz.read
78
+ output.write(v)
79
+ unused = gz.unused
80
+ gz.finish
81
+
82
+ break if unused.nil?
83
+ adjust = unused.length
84
+ input.pos -= adjust
85
+ end
86
+
87
+ output
88
+ end
89
+ end
90
+ end
91
+ end
@@ -38,8 +38,8 @@ module Fluent::Plugin
38
38
  (1..REGEXP_MAX_NUM).each do |i|
39
39
  next unless conf["regexp#{i}"]
40
40
  key, regexp = conf["regexp#{i}"].split(/ /, 2)
41
- raise ConfigError, "regexp#{i} does not contain 2 parameters" unless regexp
42
- raise ConfigError, "regexp#{i} contains a duplicated key, #{key}" if @regexps[key]
41
+ raise Fluent::ConfigError, "regexp#{i} does not contain 2 parameters" unless regexp
42
+ raise Fluent::ConfigError, "regexp#{i} contains a duplicated key, #{key}" if @regexps[key]
43
43
  @regexps[key] = Regexp.compile(regexp)
44
44
  end
45
45
 
@@ -47,8 +47,8 @@ module Fluent::Plugin
47
47
  (1..REGEXP_MAX_NUM).each do |i|
48
48
  next unless conf["exclude#{i}"]
49
49
  key, exclude = conf["exclude#{i}"].split(/ /, 2)
50
- raise ConfigError, "exclude#{i} does not contain 2 parameters" unless exclude
51
- raise ConfigError, "exclude#{i} contains a duplicated key, #{key}" if @excludes[key]
50
+ raise Fluent::ConfigError, "exclude#{i} does not contain 2 parameters" unless exclude
51
+ raise Fluent::ConfigError, "exclude#{i} contains a duplicated key, #{key}" if @excludes[key]
52
52
  @excludes[key] = Regexp.compile(exclude)
53
53
  end
54
54
  end
@@ -16,13 +16,13 @@
16
16
 
17
17
  require 'fluent/plugin/base'
18
18
  require 'fluent/plugin/owned_by_mixin'
19
-
20
- require 'fluent/mixin' # for TimeFormatter
19
+ require 'fluent/time'
21
20
 
22
21
  module Fluent
23
22
  module Plugin
24
23
  class Formatter < Base
25
24
  include OwnedByMixin
25
+ include TimeMixin::Formatter
26
26
 
27
27
  configured_in :format
28
28
 
@@ -15,6 +15,7 @@
15
15
  #
16
16
 
17
17
  require 'fluent/plugin/formatter'
18
+ require 'fluent/env'
18
19
 
19
20
  module Fluent
20
21
  module Plugin
@@ -29,7 +30,7 @@ module Fluent
29
30
  begin
30
31
  raise LoadError unless @json_parser == 'oj'
31
32
  require 'oj'
32
- Oj.default_options = {mode: :compat}
33
+ Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
33
34
  @dump_proc = Oj.method(:dump)
34
35
  rescue LoadError
35
36
  @dump_proc = Yajl.method(:dump)
@@ -32,39 +32,12 @@ module Fluent
32
32
  else "\t"
33
33
  end
34
34
  end
35
- config_param :time_type, :enum, list: [:float, :unixtime, :string], default: :string
36
- config_param :time_format, :string, default: nil
37
- config_param :localtime, :bool, default: true # if localtime is false and timezone is nil, then utc
38
- config_param :timezone, :string, default: nil
35
+ config_set_default :time_type, :string
36
+ config_set_default :time_format, nil # time_format nil => iso8601
39
37
 
40
38
  def configure(conf)
41
- # TODO: make a utility method in TimeFormatter to handle these conversion
42
- # copies of this code: plugin_helper/compat_parameters, compat/formatter_utils and here
43
- if conf.has_key?('time_as_epoch') && Fluent::Config.bool_value(conf['time_as_epoch'])
44
- conf['time_type'] = 'unixtime'
45
- end
46
- if conf.has_key?('localtime') || conf.has_key?('utc')
47
- if conf.has_key?('localtime') && conf.has_key?('utc')
48
- raise Fluent::ConfigError, "both of utc and localtime are specified, use only one of them"
49
- elsif conf.has_key?('localtime')
50
- conf['localtime'] = Fluent::Config.bool_value(conf['localtime'])
51
- elsif conf.has_key?('utc')
52
- conf['localtime'] = !(Fluent::Config.bool_value(conf['utc']))
53
- # Specifying "localtime false" means using UTC in TimeFormatter
54
- # And specifying "utc" is different from specifying "timezone +0000"(it's not always UTC).
55
- # There are difference between "Z" and "+0000" in timezone formatting.
56
- # TODO: add kwargs to TimeFormatter to specify "using localtime", "using UTC" or "using specified timezone" in more explicit way
57
- end
58
- end
59
-
60
39
  super
61
-
62
- @timef = case @time_type
63
- when :float then ->(time){ time.to_r.to_f }
64
- when :unixtime then ->(time){ time.to_i }
65
- else
66
- Fluent::TimeFormatter.new(@time_format, @localtime, @timezone)
67
- end
40
+ @timef = time_formatter_create
68
41
  end
69
42
 
70
43
  def format(tag, time, record)
@@ -153,7 +153,7 @@ module Fluent
153
153
  # In test cases it occasionally appeared that when detaching a watcher, another watcher is also detached.
154
154
  # In the case in the iteration of watchers, a watcher that has been already detached is intended to be detached
155
155
  # and therfore RuntimeError occurs saying that it is not attached to a loop.
156
- # It occures only when testing for sending responses to ForwardOutput.
156
+ # It occurs only when testing for sending responses to ForwardOutput.
157
157
  # Sending responses needs to write the socket that is previously used only to read
158
158
  # and a handler has 2 watchers that is used to read and to write.
159
159
  # This problem occurs possibly because those watchers are thought to be related to each other
@@ -338,7 +338,8 @@ module Fluent
338
338
  # PackedForward
339
339
  option = msg[2]
340
340
  size = (option && option['size']) || 0
341
- es = MessagePackEventStream.new(entries, nil, size.to_i)
341
+ es_class = (option && option['compressed'] == 'gzip') ? CompressedMessagePackEventStream : MessagePackEventStream
342
+ es = es_class.new(entries, nil, size.to_i)
342
343
  es = check_and_skip_invalid_event(tag, es, peeraddr) if @skip_invalid_event
343
344
  es = add_source_host(es, peeraddr[2]) if @source_hostname_key
344
345
  router.emit_stream(tag, es)
@@ -262,34 +262,20 @@ module Fluent::Plugin
262
262
  array.concat Fluent::Engine.root_agent.inputs
263
263
 
264
264
  # get all output plugins
265
- Fluent::Engine.root_agent.outputs.each { |o|
266
- MonitorAgentInput.collect_children(o, array)
267
- }
265
+ array.concat Fluent::Engine.root_agent.outputs
266
+
268
267
  # get all filter plugins
269
- Fluent::Engine.root_agent.filters.each { |f|
270
- MonitorAgentInput.collect_children(f, array)
271
- }
268
+ array.concat Fluent::Engine.root_agent.filters
269
+
272
270
  Fluent::Engine.root_agent.labels.each { |name, l|
273
271
  # TODO: Add label name to outputs / filters for identifing plugins
274
- l.outputs.each { |o| MonitorAgentInput.collect_children(o, array) }
275
- l.filters.each { |f| MonitorAgentInput.collect_children(f, array) }
272
+ array.concat l.outputs
273
+ array.concat l.filters
276
274
  }
277
275
 
278
276
  array
279
277
  end
280
278
 
281
- # get nexted plugins (such as <store> of the copy plugin)
282
- # from the plugin `pe` recursively
283
- def self.collect_children(pe, array=[])
284
- array << pe
285
- if pe.is_a?(Fluent::Plugin::MultiOutput) || pe.is_a?(Fluent::MultiOutput) && pe.respond_to?(:outputs)
286
- pe.outputs.each {|nop|
287
- collect_children(nop, array)
288
- }
289
- end
290
- array
291
- end
292
-
293
279
  # try to match the tag and get the info from the matched output plugin
294
280
  # TODO: Support output in label
295
281
  def plugin_info_by_tag(tag, opts={})
@@ -376,7 +362,7 @@ module Fluent::Plugin
376
362
  case pe
377
363
  when Fluent::Plugin::Input
378
364
  'input'.freeze
379
- when Fluent::Plugin::Output, Fluent::Plugin::BareOutput
365
+ when Fluent::Plugin::Output, Fluent::Plugin::MultiOutput, Fluent::Plugin::BareOutput
380
366
  'output'.freeze
381
367
  when Fluent::Plugin::Filter
382
368
  'filter'.freeze
@@ -86,7 +86,7 @@ module Fluent::Plugin
86
86
  when 'udp'
87
87
  :udp
88
88
  else
89
- raise ConfigError, "syslog input protocol type should be 'tcp' or 'udp'"
89
+ raise Fluent::ConfigError, "syslog input protocol type should be 'tcp' or 'udp'"
90
90
  end
91
91
  end
92
92
  desc 'If true, add source host to event record.'
@@ -208,7 +208,7 @@ module Fluent::Plugin
208
208
  line_buffer_timer_flusher = (@multiline_mode && @multiline_flush_interval) ? TailWatcher::LineBufferTimerFlusher.new(log, @multiline_flush_interval, &method(:flush_buffer)) : nil
209
209
  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))
210
210
  tw.attach do |watcher|
211
- timer_execute(:in_tail_timer_trigger, 1, &watcher.method(:on_notify)) if watcher.enable_watch_timer
211
+ watcher.timer_trigger = timer_execute(:in_tail_timer_trigger, 1, &watcher.method(:on_notify)) if watcher.enable_watch_timer
212
212
  event_loop_attach(watcher.stat_trigger)
213
213
  end
214
214
  tw
@@ -248,6 +248,12 @@ module Fluent::Plugin
248
248
 
249
249
  # refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
250
250
  def update_watcher(path, pe)
251
+ if @pf
252
+ unless pe.read_inode == @pf[path].read_inode
253
+ log.trace "Skip update_watcher because watcher has been already updated by other inotify event"
254
+ return
255
+ end
256
+ end
251
257
  rotated_tw = @tails[path]
252
258
  @tails[path] = setup_watcher(path, pe)
253
259
  close_watcher_after_rotate_wait(rotated_tw) if rotated_tw
@@ -398,6 +404,7 @@ module Fluent::Plugin
398
404
  @update_watcher = update_watcher
399
405
 
400
406
  @stat_trigger = StatWatcher.new(path, log, &method(:on_notify))
407
+ @timer_trigger = nil
401
408
 
402
409
  @rotate_handler = RotateHandler.new(path, log, &method(:on_rotate))
403
410
  @io_handler = nil
@@ -421,11 +428,12 @@ module Fluent::Plugin
421
428
  end
422
429
 
423
430
  def attach
424
- yield self
425
431
  on_notify
432
+ yield self
426
433
  end
427
434
 
428
435
  def detach
436
+ @timer_trigger.detach if @enable_watch_timer && @timer_trigger.attached?
429
437
  @stat_trigger.detach if @stat_trigger.attached?
430
438
  end
431
439
 
@@ -32,7 +32,7 @@ module Fluent
32
32
  config_param :@type, :string, default: nil
33
33
  end
34
34
 
35
- attr_reader :outputs
35
+ attr_reader :outputs, :outputs_statically_created
36
36
 
37
37
  def process(tag, es)
38
38
  raise NotImplementedError, "BUG: output plugins MUST implement this method"
@@ -41,8 +41,7 @@ module Fluent
41
41
  def initialize
42
42
  super
43
43
  @outputs = []
44
-
45
- @compat = false
44
+ @outputs_statically_created = false
46
45
 
47
46
  @counters_monitor = Monitor.new
48
47
  # TODO: well organized counters
@@ -78,11 +77,72 @@ module Fluent
78
77
  end
79
78
  end
80
79
 
80
+ def static_outputs
81
+ @outputs_statically_created = true
82
+ @outputs
83
+ end
84
+
81
85
  # Child plugin's lifecycles are controlled by agent automatically.
82
86
  # It calls `outputs` to traverse plugins, and invoke start/stop/*shutdown/close/terminate on these directly.
83
87
  # * `start` of this plugin will be called after child plugins
84
88
  # * `stop`, `*shutdown`, `close` and `terminate` of this plugin will be called before child plugins
85
89
 
90
+ # But when MultiOutput plugins are created dynamically (by forest plugin or others), agent cannot find
91
+ # sub-plugins. So child plugins' lifecycles MUST be controlled by MultiOutput plugin itself.
92
+ # TODO: this hack will be removed at v2.
93
+ def call_lifecycle_method(method_name, checker_name)
94
+ return if @outputs_statically_created
95
+ @outputs.each do |o|
96
+ begin
97
+ log.debug "calling #{method_name} on output plugin dynamically created", type: Fluent::Plugin.lookup_type_from_class(o.class), plugin_id: o.plugin_id
98
+ o.send(method_name) unless o.send(checker_name)
99
+ rescue Exception => e
100
+ log.warn "unexpected error while calling #{method_name} on output plugin dynamically created", plugin: o.class, plugin_id: o.plugin_id, error: e
101
+ log.warn_backtrace
102
+ end
103
+ end
104
+ end
105
+
106
+ def start
107
+ super
108
+ call_lifecycle_method(:start, :started?)
109
+ end
110
+
111
+ def after_start
112
+ super
113
+ call_lifecycle_method(:after_start, :after_started?)
114
+ end
115
+
116
+ def stop
117
+ super
118
+ call_lifecycle_method(:stop, :stopped?)
119
+ end
120
+
121
+ def before_shutdown
122
+ super
123
+ call_lifecycle_method(:before_shutdown, :before_shutdown?)
124
+ end
125
+
126
+ def shutdown
127
+ super
128
+ call_lifecycle_method(:shutdown, :shutdown?)
129
+ end
130
+
131
+ def after_shutdown
132
+ super
133
+ call_lifecycle_method(:after_shutdown, :after_shutdown?)
134
+ end
135
+
136
+ def close
137
+ super
138
+ call_lifecycle_method(:close, :closed?)
139
+ end
140
+
141
+ def terminate
142
+ super
143
+ call_lifecycle_method(:terminate, :terminated?)
144
+ end
145
+
86
146
  def emit_sync(tag, es)
87
147
  @counters_monitor.synchronize{ @emit_count += 1 }
88
148
  begin
@@ -42,7 +42,7 @@ module Fluent::Plugin
42
42
  desc "The format used to map the incoming events to the program input. (#{Fluent::ExecUtil::SUPPORTED_FORMAT.keys.join(',')})"
43
43
  config_param :format, default: :tsv, skip_accessor: true do |val|
44
44
  f = Fluent::ExecUtil::SUPPORTED_FORMAT[val]
45
- raise ConfigError, "Unsupported format '#{val}'" unless f
45
+ raise Fluent::ConfigError, "Unsupported format '#{val}'" unless f
46
46
  f
47
47
  end
48
48
  config_param :localtime, :bool, default: false
@@ -59,7 +59,11 @@ module Fluent
59
59
 
60
60
  def generate_chunk(metadata)
61
61
  chunk = super
62
- latest_chunk = metadata_list.sort_by(&:timekey).last
62
+ # "symlink" feature is to link from symlink_path to the latest file chunk. Records with latest
63
+ # timekey will be appended into that file chunk. On the other side, resumed file chunks might NOT
64
+ # have timekey, especially in the cases that resumed file chunks are generated by Fluentd v0.12.
65
+ # These chunks will be enqueued immediately, and will be flushed soon.
66
+ latest_chunk = metadata_list.select{|m| m.timekey }.sort_by(&:timekey).last
63
67
  if chunk.metadata == latest_chunk
64
68
  FileUtils.ln_sf(chunk.path, @_symlink_path)
65
69
  end
@@ -80,6 +80,9 @@ module Fluent
80
80
  desc 'Enable client-side DNS round robin.'
81
81
  config_param :dns_round_robin, :bool, default: false # heartbeat_type 'udp' is not available for this
82
82
 
83
+ desc 'Compress buffered data.'
84
+ config_param :compress, :enum, list: [:text, :gzip], default: :text
85
+
83
86
  config_section :security, required: false, multi: false do
84
87
  desc 'The hostname'
85
88
  config_param :self_hostname, :string
@@ -137,6 +140,12 @@ module Fluent
137
140
  end
138
141
  end
139
142
 
143
+ if @compress == :gzip && @buffer.compress == :text
144
+ @buffer.compress = :gzip
145
+ elsif @compress == :text && @buffer.compress == :gzip
146
+ log.info "buffer is compressed. If you also want to save the bandwidth of a network, Add `compress` configuration in <match>"
147
+ end
148
+
140
149
  if @nodes.empty?
141
150
  raise ConfigError, "forward output plugin requires at least one <server> is required"
142
151
  end
@@ -331,6 +340,7 @@ module Fluent
331
340
  def initialize(sender, server, failure:)
332
341
  @sender = sender
333
342
  @log = sender.log
343
+ @compress = sender.compress
334
344
 
335
345
  @name = server.name
336
346
  @host = server.host
@@ -432,7 +442,7 @@ module Fluent
432
442
  raise ForwardOutputConnectionClosedError, "failed to establish connection with node #{@name}"
433
443
  end
434
444
 
435
- option = { 'size' => chunk.size_of_events }
445
+ option = { 'size' => chunk.size_of_events, 'compressed' => @compress }
436
446
  option['chunk'] = Base64.encode64(chunk.unique_id) if @sender.require_ack_response
437
447
 
438
448
  # out_forward always uses Raw32 type for content.
@@ -440,13 +450,15 @@ module Fluent
440
450
 
441
451
  sock.write @sender.forward_header # beginArray(3)
442
452
  sock.write tag.to_msgpack # 1. writeRaw(tag)
443
- sock.write [0xdb, chunk.size].pack('CN') # 2. beginRaw(size) raw32
444
- chunk.write_to(sock) # writeRawBody(packed_es)
453
+ chunk.open(compressed: @compress) do |chunk_io|
454
+ sock.write [0xdb, chunk_io.size].pack('CN') # 2. beginRaw(size) raw32
455
+ IO.copy_stream(chunk_io, sock) # writeRawBody(packed_es)
456
+ end
445
457
  sock.write option.to_msgpack # 3. writeOption(option)
446
458
 
447
459
  if @sender.require_ack_response
448
460
  # Waiting for a response here results in a decrease of throughput because a chunk queue is locked.
449
- # To avoid a decrease of troughput, it is necessary to prepare a list of chunks that wait for responses
461
+ # To avoid a decrease of throughput, it is necessary to prepare a list of chunks that wait for responses
450
462
  # and process them asynchronously.
451
463
  if IO.select([sock], nil, nil, @sender.ack_response_timeout)
452
464
  raw_data = begin
@@ -510,7 +522,7 @@ module Fluent
510
522
  sock.close
511
523
  end
512
524
  when :udp
513
- @usock.send "\0", 0, Socket.pack_sockaddr_in(n.port, n.resolved_host)
525
+ @usock.send "\0", 0, Socket.pack_sockaddr_in(@port, resolved_host)
514
526
  when :none # :none doesn't use this class
515
527
  raise "BUG: heartbeat_type none must not use Node"
516
528
  else