fluentd 1.6.3 → 1.7.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/.drone.yml +35 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +2 -0
  4. data/CHANGELOG.md +58 -0
  5. data/README.md +5 -1
  6. data/fluentd.gemspec +1 -1
  7. data/lib/fluent/clock.rb +4 -0
  8. data/lib/fluent/compat/output.rb +3 -3
  9. data/lib/fluent/compat/socket_util.rb +1 -1
  10. data/lib/fluent/config/element.rb +3 -3
  11. data/lib/fluent/config/literal_parser.rb +1 -1
  12. data/lib/fluent/config/section.rb +4 -1
  13. data/lib/fluent/error.rb +4 -0
  14. data/lib/fluent/event.rb +28 -24
  15. data/lib/fluent/event_router.rb +2 -1
  16. data/lib/fluent/log.rb +1 -1
  17. data/lib/fluent/msgpack_factory.rb +8 -0
  18. data/lib/fluent/plugin/bare_output.rb +4 -4
  19. data/lib/fluent/plugin/buf_file_single.rb +211 -0
  20. data/lib/fluent/plugin/buffer.rb +62 -63
  21. data/lib/fluent/plugin/buffer/chunk.rb +21 -3
  22. data/lib/fluent/plugin/buffer/file_chunk.rb +37 -12
  23. data/lib/fluent/plugin/buffer/file_single_chunk.rb +314 -0
  24. data/lib/fluent/plugin/buffer/memory_chunk.rb +2 -1
  25. data/lib/fluent/plugin/compressable.rb +10 -6
  26. data/lib/fluent/plugin/filter_grep.rb +2 -2
  27. data/lib/fluent/plugin/formatter_csv.rb +10 -6
  28. data/lib/fluent/plugin/in_syslog.rb +10 -3
  29. data/lib/fluent/plugin/in_tail.rb +7 -2
  30. data/lib/fluent/plugin/in_tcp.rb +34 -7
  31. data/lib/fluent/plugin/multi_output.rb +4 -4
  32. data/lib/fluent/plugin/out_exec_filter.rb +1 -0
  33. data/lib/fluent/plugin/out_file.rb +13 -3
  34. data/lib/fluent/plugin/out_forward.rb +126 -588
  35. data/lib/fluent/plugin/out_forward/ack_handler.rb +161 -0
  36. data/lib/fluent/plugin/out_forward/connection_manager.rb +113 -0
  37. data/lib/fluent/plugin/out_forward/error.rb +28 -0
  38. data/lib/fluent/plugin/out_forward/failure_detector.rb +84 -0
  39. data/lib/fluent/plugin/out_forward/handshake_protocol.rb +121 -0
  40. data/lib/fluent/plugin/out_forward/load_balancer.rb +111 -0
  41. data/lib/fluent/plugin/out_forward/socket_cache.rb +138 -0
  42. data/lib/fluent/plugin/out_http.rb +231 -0
  43. data/lib/fluent/plugin/output.rb +29 -35
  44. data/lib/fluent/plugin/parser.rb +77 -0
  45. data/lib/fluent/plugin/parser_csv.rb +75 -0
  46. data/lib/fluent/plugin_helper/server.rb +1 -1
  47. data/lib/fluent/plugin_helper/thread.rb +1 -0
  48. data/lib/fluent/root_agent.rb +1 -1
  49. data/lib/fluent/time.rb +4 -2
  50. data/lib/fluent/timezone.rb +21 -7
  51. data/lib/fluent/version.rb +1 -1
  52. data/test/command/test_fluentd.rb +1 -1
  53. data/test/command/test_plugin_generator.rb +18 -2
  54. data/test/config/test_configurable.rb +78 -40
  55. data/test/counter/test_store.rb +1 -1
  56. data/test/helper.rb +1 -0
  57. data/test/helpers/process_extenstion.rb +33 -0
  58. data/test/plugin/out_forward/test_ack_handler.rb +101 -0
  59. data/test/plugin/out_forward/test_connection_manager.rb +145 -0
  60. data/test/plugin/out_forward/test_handshake_protocol.rb +103 -0
  61. data/test/plugin/out_forward/test_load_balancer.rb +60 -0
  62. data/test/plugin/out_forward/test_socket_cache.rb +139 -0
  63. data/test/plugin/test_buf_file.rb +118 -2
  64. data/test/plugin/test_buf_file_single.rb +734 -0
  65. data/test/plugin/test_buffer.rb +4 -48
  66. data/test/plugin/test_buffer_file_chunk.rb +19 -1
  67. data/test/plugin/test_buffer_file_single_chunk.rb +620 -0
  68. data/test/plugin/test_formatter_csv.rb +16 -0
  69. data/test/plugin/test_in_syslog.rb +56 -6
  70. data/test/plugin/test_in_tail.rb +1 -1
  71. data/test/plugin/test_in_tcp.rb +25 -0
  72. data/test/plugin/test_out_forward.rb +75 -201
  73. data/test/plugin/test_out_http.rb +352 -0
  74. data/test/plugin/test_output_as_buffered.rb +27 -24
  75. data/test/plugin/test_parser.rb +40 -0
  76. data/test/plugin/test_parser_csv.rb +83 -0
  77. data/test/plugin_helper/test_record_accessor.rb +1 -1
  78. data/test/test_time_formatter.rb +140 -121
  79. metadata +33 -4
@@ -40,7 +40,7 @@ module Fluent
40
40
 
41
41
  def initialize
42
42
  super
43
- @counters_monitor = Monitor.new
43
+ @counter_mutex = Mutex.new
44
44
  # TODO: well organized counters
45
45
  @num_errors = 0
46
46
  @emit_count = 0
@@ -48,12 +48,12 @@ module Fluent
48
48
  end
49
49
 
50
50
  def emit_sync(tag, es)
51
- @counters_monitor.synchronize{ @emit_count += 1 }
51
+ @counter_mutex.synchronize{ @emit_count += 1 }
52
52
  begin
53
53
  process(tag, es)
54
- @counters_monitor.synchronize{ @emit_records += es.size }
54
+ @counter_mutex.synchronize{ @emit_records += es.size }
55
55
  rescue
56
- @counters_monitor.synchronize{ @num_errors += 1 }
56
+ @counter_mutex.synchronize{ @num_errors += 1 }
57
57
  raise
58
58
  end
59
59
  end
@@ -0,0 +1,211 @@
1
+ #
2
+ # Fluentd
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'fileutils'
18
+
19
+ require 'fluent/plugin/buffer'
20
+ require 'fluent/plugin/buffer/file_single_chunk'
21
+ require 'fluent/system_config'
22
+
23
+ module Fluent
24
+ module Plugin
25
+ class FileSingleBuffer < Fluent::Plugin::Buffer
26
+ Plugin.register_buffer('file_single', self)
27
+
28
+ include SystemConfig::Mixin
29
+
30
+ DEFAULT_CHUNK_LIMIT_SIZE = 256 * 1024 * 1024 # 256MB
31
+ DEFAULT_TOTAL_LIMIT_SIZE = 64 * 1024 * 1024 * 1024 # 64GB
32
+
33
+ PATH_SUFFIX = ".#{Fluent::Plugin::Buffer::FileSingleChunk::PATH_EXT}"
34
+ DIR_PERMISSION = 0755
35
+
36
+ desc 'The path where buffer chunks are stored.'
37
+ config_param :path, :string, default: nil
38
+ desc 'Calculate the number of record in chunk during resume'
39
+ config_param :calc_num_records, :bool, default: true
40
+ desc 'The format of chunk. This is used to calculate the number of record'
41
+ config_param :chunk_format, :enum, list: [:msgpack, :text, :auto], default: :auto
42
+
43
+ config_set_default :chunk_limit_size, DEFAULT_CHUNK_LIMIT_SIZE
44
+ config_set_default :total_limit_size, DEFAULT_TOTAL_LIMIT_SIZE
45
+
46
+ desc 'The permission of chunk file. If no specified, <system> setting or 0644 is used'
47
+ config_param :file_permission, :string, default: nil
48
+ desc 'The permission of chunk directory. If no specified, <system> setting or 0755 is used'
49
+ config_param :dir_permission, :string, default: nil
50
+
51
+ @@buffer_paths = {}
52
+
53
+ def initialize
54
+ super
55
+
56
+ @multi_workers_available = false
57
+ @additional_resume_path = nil
58
+ end
59
+
60
+ def configure(conf)
61
+ super
62
+
63
+ if @chunk_format == :auto
64
+ @chunk_format = owner.formatted_to_msgpack_binary? ? :msgpack : :text
65
+ end
66
+
67
+ @key_in_path = nil
68
+ if owner.chunk_keys.empty?
69
+ log.debug "use event tag for buffer key"
70
+ else
71
+ if owner.chunk_key_tag
72
+ raise Fluent::ConfigError, "chunk keys must be tag or one field"
73
+ elsif owner.chunk_keys.size > 1
74
+ raise Fluent::ConfigError, "2 or more chunk keys is not allowed"
75
+ else
76
+ @key_in_path = owner.chunk_keys.first.to_sym
77
+ end
78
+ end
79
+
80
+ multi_workers_configured = owner.system_config.workers > 1
81
+ using_plugin_root_dir = false
82
+ unless @path
83
+ if root_dir = owner.plugin_root_dir
84
+ @path = File.join(root_dir, 'buffer')
85
+ using_plugin_root_dir = true # plugin_root_dir path contains worker id
86
+ else
87
+ raise Fluent::ConfigError, "buffer path is not configured. specify 'path' in <buffer>"
88
+ end
89
+ end
90
+
91
+ type_of_owner = Plugin.lookup_type_from_class(@_owner.class)
92
+ if @@buffer_paths.has_key?(@path) && !called_in_test?
93
+ type_using_this_path = @@buffer_paths[@path]
94
+ raise Fluent::ConfigError, "Other '#{type_using_this_path}' plugin already uses same buffer path: type = #{type_of_owner}, buffer path = #{@path}"
95
+ end
96
+
97
+ @@buffer_paths[@path] = type_of_owner
98
+
99
+ specified_directory_exists = File.exist?(@path) && File.directory?(@path)
100
+ unexisting_path_for_directory = !File.exist?(@path) && !@path.include?('.*')
101
+
102
+ if specified_directory_exists || unexisting_path_for_directory # directory
103
+ if using_plugin_root_dir || !multi_workers_configured
104
+ @path = File.join(@path, "fsb.*#{PATH_SUFFIX}")
105
+ else
106
+ @path = File.join(@path, "worker#{fluentd_worker_id}", "fsb.*#{PATH_SUFFIX}")
107
+ if fluentd_worker_id == 0
108
+ # worker 0 always checks unflushed buffer chunks to be resumed (might be created while non-multi-worker configuration)
109
+ @additional_resume_path = File.join(File.expand_path("../../", @path), "fsb.*#{PATH_SUFFIX}")
110
+ end
111
+ end
112
+ @multi_workers_available = true
113
+ else # specified path is file path
114
+ if File.basename(@path).include?('.*.')
115
+ new_path = File.join(File.dirname(@path), "fsb.*#{PATH_SUFFIX}")
116
+ log.warn "file_single doesn't allow user specified 'prefix.*.suffix' style path. Use '#{new_path}' for file instead: #{@path}"
117
+ @path = new_path
118
+ elsif File.basename(@path).end_with?('.*')
119
+ @path = @path + PATH_SUFFIX
120
+ else
121
+ # existing file will be ignored
122
+ @path = @path + ".*#{PATH_SUFFIX}"
123
+ end
124
+ @multi_workers_available = false
125
+ end
126
+
127
+ @dir_permission = if @dir_permission
128
+ @dir_permission.to_i(8)
129
+ else
130
+ system_config.dir_permission || DIR_PERMISSION
131
+ end
132
+ end
133
+
134
+ # This method is called only when multi worker is configured
135
+ def multi_workers_ready?
136
+ unless @multi_workers_available
137
+ log.error "file_single buffer with multi workers should be configured to use directory 'path', or system root_dir and plugin id"
138
+ end
139
+ @multi_workers_available
140
+ end
141
+
142
+ def start
143
+ FileUtils.mkdir_p(File.dirname(@path), mode: @dir_permission)
144
+
145
+ super
146
+ end
147
+
148
+ def persistent?
149
+ true
150
+ end
151
+
152
+ def resume
153
+ stage = {}
154
+ queue = []
155
+
156
+ patterns = [@path]
157
+ patterns.unshift @additional_resume_path if @additional_resume_path
158
+ Dir.glob(patterns) do |path|
159
+ next unless File.file?(path)
160
+
161
+ log.debug { "restoring buffer file: path = #{path}" }
162
+
163
+ m = new_metadata() # this metadata will be updated in FileSingleChunk.new
164
+ mode = Fluent::Plugin::Buffer::FileSingleChunk.assume_chunk_state(path)
165
+ if mode == :unknown
166
+ log.debug "unknown state chunk found", path: path
167
+ next
168
+ end
169
+
170
+ begin
171
+ chunk = Fluent::Plugin::Buffer::FileSingleChunk.new(m, path, mode, @key_in_path)
172
+ chunk.restore_size(@chunk_format) if @calc_num_records
173
+ rescue Fluent::Plugin::Buffer::FileSingleChunk::FileChunkError => e
174
+ handle_broken_files(path, mode, e)
175
+ next
176
+ end
177
+
178
+ case chunk.state
179
+ when :staged
180
+ stage[chunk.metadata] = chunk
181
+ when :queued
182
+ queue << chunk
183
+ end
184
+ end
185
+
186
+ queue.sort_by!(&:modified_at)
187
+
188
+ return stage, queue
189
+ end
190
+
191
+ def generate_chunk(metadata)
192
+ # FileChunk generates real path with unique_id
193
+ if @file_permission
194
+ chunk = Fluent::Plugin::Buffer::FileSingleChunk.new(metadata, @path, :create, @key_in_path, perm: @file_permission, compress: @compress)
195
+ else
196
+ chunk = Fluent::Plugin::Buffer::FileSingleChunk.new(metadata, @path, :create, @key_in_path, compress: @compress)
197
+ end
198
+
199
+ log.debug "Created new chunk", chunk_id: dump_unique_id_hex(chunk.unique_id), metadata: metadata
200
+
201
+ chunk
202
+ end
203
+
204
+ def handle_broken_files(path, mode, e)
205
+ log.error "found broken chunk file during resume. Delete corresponding files:", path: path, mode: mode, err_msg: e.message
206
+ # After support 'backup_dir' feature, these files are moved to backup_dir instead of unlink.
207
+ File.unlink(path) rescue nil
208
+ end
209
+ end
210
+ end
211
+ end
@@ -133,6 +133,15 @@ module Fluent
133
133
  cmp_variables(variables, variables2)
134
134
  end
135
135
  end
136
+
137
+ # This is an optimization code. Current Struct's implementation is comparing all data.
138
+ # https://github.com/ruby/ruby/blob/0623e2b7cc621b1733a760b72af246b06c30cf96/struct.c#L1200-L1203
139
+ # Actually this overhead is very small but this class is generated *per chunk* (and used in hash object).
140
+ # This means that this class is one of the most called object in Fluentd.
141
+ # See https://github.com/fluent/fluentd/pull/2560
142
+ def hash
143
+ timekey.object_id
144
+ end
136
145
  end
137
146
 
138
147
  # for tests
@@ -155,7 +164,7 @@ module Fluent
155
164
 
156
165
  @stage_size = @queue_size = 0
157
166
  @timekeys = Hash.new(0)
158
- @metadata_list = [] # keys of @stage
167
+ @mutex = Mutex.new
159
168
  end
160
169
 
161
170
  def persistent?
@@ -175,16 +184,18 @@ module Fluent
175
184
 
176
185
  @stage, @queue = resume
177
186
  @stage.each_pair do |metadata, chunk|
178
- @metadata_list << metadata unless @metadata_list.include?(metadata)
179
187
  @stage_size += chunk.bytesize
180
- add_timekey(metadata)
188
+ if chunk.metadata && chunk.metadata.timekey
189
+ add_timekey(metadata.timekey)
190
+ end
181
191
  end
182
192
  @queue.each do |chunk|
183
- @metadata_list << chunk.metadata unless @metadata_list.include?(chunk.metadata)
184
193
  @queued_num[chunk.metadata] ||= 0
185
194
  @queued_num[chunk.metadata] += 1
186
195
  @queue_size += chunk.bytesize
187
- add_timekey(chunk.metadata)
196
+ if chunk.metadata && chunk.metadata.timekey
197
+ add_timekey(chunk.metadata.timekey)
198
+ end
188
199
  end
189
200
  log.debug "buffer started", instance: self.object_id, stage_size: @stage_size, queue_size: @queue_size
190
201
  end
@@ -207,7 +218,7 @@ module Fluent
207
218
 
208
219
  def terminate
209
220
  super
210
- @dequeued = @stage = @queue = @queued_num = @metadata_list = nil
221
+ @dequeued = @stage = @queue = @queued_num = nil
211
222
  @stage_size = @queue_size = 0
212
223
  @timekeys.clear
213
224
  end
@@ -230,61 +241,17 @@ module Fluent
230
241
  raise NotImplementedError, "Implement this method in child class"
231
242
  end
232
243
 
233
- def metadata_list
234
- synchronize do
235
- @metadata_list.dup
236
- end
237
- end
238
-
239
- # it's too dangerous, and use it so carefully to remove metadata for tests
240
- def metadata_list_clear!
241
- synchronize do
242
- @metadata_list.clear
243
- end
244
- end
245
-
246
244
  def new_metadata(timekey: nil, tag: nil, variables: nil)
247
245
  Metadata.new(timekey, tag, variables)
248
246
  end
249
247
 
250
- def add_metadata(metadata)
251
- log.on_trace { log.trace "adding metadata", instance: self.object_id, metadata: metadata }
252
-
253
- synchronize do
254
- if i = @metadata_list.index(metadata)
255
- @metadata_list[i]
256
- else
257
- @metadata_list << metadata
258
- add_timekey(metadata)
259
- metadata
260
- end
261
- end
262
- end
263
-
264
248
  def metadata(timekey: nil, tag: nil, variables: nil)
265
- meta = new_metadata(timekey: timekey, tag: tag, variables: variables)
266
- add_metadata(meta)
267
- end
268
-
269
- def add_timekey(metadata)
270
- if t = metadata.timekey
271
- @timekeys[t] += 1
249
+ meta = Metadata.new(timekey, tag, variables)
250
+ if (t = meta.timekey)
251
+ add_timekey(t)
272
252
  end
273
- nil
253
+ meta
274
254
  end
275
- private :add_timekey
276
-
277
- def del_timekey(metadata)
278
- if t = metadata.timekey
279
- if @timekeys[t] <= 1
280
- @timekeys.delete(t)
281
- else
282
- @timekeys[t] -= 1
283
- end
284
- end
285
- nil
286
- end
287
- private :del_timekey
288
255
 
289
256
  def timekeys
290
257
  @timekeys.keys
@@ -408,13 +375,12 @@ module Fluent
408
375
  synchronize { @queue.reduce(0){|r, chunk| r + chunk.size } }
409
376
  end
410
377
 
411
- def queued?(metadata=nil)
412
- synchronize do
413
- if metadata
414
- n = @queued_num[metadata]
415
- n && n.nonzero?
416
- else
417
- !@queue.empty?
378
+ def queued?(metadata = nil, optimistic: false)
379
+ if optimistic
380
+ optimistic_queued?(metadata)
381
+ else
382
+ synchronize do
383
+ optimistic_queued?(metadata)
418
384
  end
419
385
  end
420
386
  end
@@ -514,6 +480,7 @@ module Fluent
514
480
  end
515
481
 
516
482
  def purge_chunk(chunk_id)
483
+ metadata = nil
517
484
  synchronize do
518
485
  chunk = @dequeued.delete(chunk_id)
519
486
  return nil unless chunk # purged by other threads
@@ -532,13 +499,16 @@ module Fluent
532
499
 
533
500
  @dequeued_num[chunk.metadata] -= 1
534
501
  if metadata && !@stage[metadata] && (!@queued_num[metadata] || @queued_num[metadata] < 1) && @dequeued_num[metadata].zero?
535
- @metadata_list.delete(metadata)
536
502
  @queued_num.delete(metadata)
537
503
  @dequeued_num.delete(metadata)
538
- del_timekey(metadata)
539
504
  end
540
505
  log.trace "chunk purged", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: metadata
541
506
  end
507
+
508
+ if metadata && metadata.timekey
509
+ del_timekey(metadata.timekey)
510
+ end
511
+
542
512
  nil
543
513
  end
544
514
 
@@ -774,6 +744,35 @@ module Fluent
774
744
 
775
745
  { 'buffer' => stats }
776
746
  end
747
+
748
+ private
749
+
750
+ def optimistic_queued?(metadata = nil)
751
+ if metadata
752
+ n = @queued_num[metadata]
753
+ n && n.nonzero?
754
+ else
755
+ !@queue.empty?
756
+ end
757
+ end
758
+
759
+ def add_timekey(t)
760
+ @mutex.synchronize do
761
+ @timekeys[t] += 1
762
+ end
763
+ nil
764
+ end
765
+
766
+ def del_timekey(t)
767
+ @mutex.synchronize do
768
+ if @timekeys[t] <= 1
769
+ @timekeys.delete(t)
770
+ else
771
+ @timekeys[t] -= 1
772
+ end
773
+ end
774
+ nil
775
+ end
777
776
  end
778
777
  end
779
778
  end
@@ -57,13 +57,31 @@ module Fluent
57
57
  @state = :unstaged
58
58
 
59
59
  @size = 0
60
- @created_at = Time.now
61
- @modified_at = Time.now
60
+ @created_at = Fluent::Clock.real_now
61
+ @modified_at = Fluent::Clock.real_now
62
62
 
63
63
  extend Decompressable if compress == :gzip
64
64
  end
65
65
 
66
- attr_reader :unique_id, :metadata, :created_at, :modified_at, :state
66
+ attr_reader :unique_id, :metadata, :state
67
+
68
+ def raw_create_at
69
+ @created_at
70
+ end
71
+
72
+ def raw_modified_at
73
+ @modified_at
74
+ end
75
+
76
+ # for compatibility
77
+ def created_at
78
+ @created_at_object ||= Time.at(@created_at)
79
+ end
80
+
81
+ # for compatibility
82
+ def modified_at
83
+ @modified_at_object ||= Time.at(@modified_at)
84
+ end
67
85
 
68
86
  # data is array of formatted record string
69
87
  def append(data, **kwargs)