fluentd 1.16.3 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/DISCUSSION_TEMPLATE/q-a-japanese.yml +50 -0
- data/.github/DISCUSSION_TEMPLATE/q-a.yml +47 -0
- data/.github/workflows/test-ruby-head.yml +31 -0
- data/.github/workflows/{linux-test.yaml → test.yml} +9 -13
- data/CHANGELOG.md +66 -0
- data/README.md +1 -1
- data/Rakefile +1 -1
- data/fluentd.gemspec +9 -1
- data/lib/fluent/command/binlog_reader.rb +1 -1
- data/lib/fluent/config/configure_proxy.rb +2 -2
- data/lib/fluent/config/types.rb +1 -1
- data/lib/fluent/configurable.rb +2 -2
- data/lib/fluent/counter/mutex_hash.rb +1 -1
- data/lib/fluent/fluent_log_event_router.rb +0 -2
- data/lib/fluent/plugin/buf_file.rb +1 -1
- data/lib/fluent/plugin/buffer/file_chunk.rb +1 -1
- data/lib/fluent/plugin/buffer/file_single_chunk.rb +2 -3
- data/lib/fluent/plugin/buffer.rb +95 -86
- data/lib/fluent/plugin/filter_parser.rb +26 -8
- data/lib/fluent/plugin/in_http.rb +18 -53
- data/lib/fluent/plugin/in_tail.rb +52 -8
- data/lib/fluent/plugin/out_http.rb +125 -13
- data/lib/fluent/plugin/owned_by_mixin.rb +0 -1
- data/lib/fluent/plugin/parser_json.rb +22 -5
- data/lib/fluent/plugin/parser_msgpack.rb +24 -3
- data/lib/fluent/plugin_helper/metrics.rb +2 -2
- data/lib/fluent/registry.rb +6 -6
- data/lib/fluent/test/output_test.rb +1 -1
- data/lib/fluent/unique_id.rb +1 -1
- data/lib/fluent/version.rb +1 -1
- data/test/command/test_fluentd.rb +8 -2
- data/test/log/test_console_adapter.rb +10 -3
- data/test/plugin/data/log_numeric/01.log +0 -0
- data/test/plugin/data/log_numeric/02.log +0 -0
- data/test/plugin/data/log_numeric/12.log +0 -0
- data/test/plugin/data/log_numeric/14.log +0 -0
- data/test/plugin/test_buffer.rb +110 -0
- data/test/plugin/test_in_http.rb +23 -1
- data/test/plugin/test_in_tail.rb +307 -3
- data/test/plugin/test_out_forward.rb +34 -39
- data/test/plugin/test_out_http.rb +128 -0
- data/test/plugin/test_owned_by.rb +0 -1
- data/test/plugin/test_parser_json.rb +106 -0
- data/test/plugin/test_parser_msgpack.rb +127 -0
- data/test/plugin/test_storage.rb +0 -1
- data/test/plugin_helper/test_child_process.rb +17 -7
- metadata +104 -9
- data/.github/workflows/macos-test.yaml +0 -34
- data/.github/workflows/windows-test.yaml +0 -49
- /data/.github/ISSUE_TEMPLATE/{bug_report.yaml → bug_report.yml} +0 -0
- /data/.github/ISSUE_TEMPLATE/{feature_request.yaml → feature_request.yml} +0 -0
data/lib/fluent/plugin/buffer.rb
CHANGED
|
@@ -580,7 +580,7 @@ module Fluent
|
|
|
580
580
|
chunk = @dequeued.delete(chunk_id)
|
|
581
581
|
return false unless chunk # already purged by other thread
|
|
582
582
|
@queue.unshift(chunk)
|
|
583
|
-
log.trace "chunk taken back", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: chunk.metadata
|
|
583
|
+
log.on_trace { log.trace "chunk taken back", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: chunk.metadata }
|
|
584
584
|
@queued_num[chunk.metadata] += 1 # BUG if nil
|
|
585
585
|
@dequeued_num[chunk.metadata] -= 1
|
|
586
586
|
end
|
|
@@ -610,7 +610,7 @@ module Fluent
|
|
|
610
610
|
@queued_num.delete(metadata)
|
|
611
611
|
@dequeued_num.delete(metadata)
|
|
612
612
|
end
|
|
613
|
-
log.trace "chunk purged", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: metadata
|
|
613
|
+
log.on_trace { log.trace "chunk purged", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: metadata }
|
|
614
614
|
end
|
|
615
615
|
|
|
616
616
|
nil
|
|
@@ -728,7 +728,6 @@ module Fluent
|
|
|
728
728
|
|
|
729
729
|
def write_step_by_step(metadata, data, format, splits_count, &block)
|
|
730
730
|
splits = []
|
|
731
|
-
errors = []
|
|
732
731
|
if splits_count > data.size
|
|
733
732
|
splits_count = data.size
|
|
734
733
|
end
|
|
@@ -749,16 +748,14 @@ module Fluent
|
|
|
749
748
|
modified_chunks = []
|
|
750
749
|
modified_metadata = metadata
|
|
751
750
|
get_next_chunk = ->(){
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
modified_chunks << c
|
|
761
|
-
c
|
|
751
|
+
if staged_chunk_used
|
|
752
|
+
# Staging new chunk here is bad idea:
|
|
753
|
+
# Recovering whole state including newly staged chunks is much harder than current implementation.
|
|
754
|
+
modified_metadata = modified_metadata.dup_next
|
|
755
|
+
generate_chunk(modified_metadata)
|
|
756
|
+
else
|
|
757
|
+
synchronize { @stage[modified_metadata] ||= generate_chunk(modified_metadata).staged! }
|
|
758
|
+
end
|
|
762
759
|
}
|
|
763
760
|
|
|
764
761
|
writing_splits_index = 0
|
|
@@ -766,104 +763,116 @@ module Fluent
|
|
|
766
763
|
|
|
767
764
|
while writing_splits_index < splits.size
|
|
768
765
|
chunk = get_next_chunk.call
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
original_bytesize = committed_bytesize = chunk.bytesize
|
|
774
|
-
begin
|
|
775
|
-
while writing_splits_index < splits.size
|
|
776
|
-
split = splits[writing_splits_index]
|
|
777
|
-
formatted_split = format ? format.call(split) : nil
|
|
766
|
+
errors = []
|
|
767
|
+
# The chunk must be locked until being passed to &block.
|
|
768
|
+
chunk.mon_enter
|
|
769
|
+
modified_chunks << {chunk: chunk, adding_bytesize: 0, errors: errors}
|
|
778
770
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
if @compress != :text
|
|
782
|
-
determined_bytesize = nil
|
|
783
|
-
elsif formatted_split
|
|
784
|
-
determined_bytesize = formatted_split.bytesize
|
|
785
|
-
elsif split.first.respond_to?(:bytesize)
|
|
786
|
-
determined_bytesize = split.first.bytesize
|
|
787
|
-
end
|
|
771
|
+
raise ShouldRetry unless chunk.writable?
|
|
772
|
+
staged_chunk_used = true if chunk.staged?
|
|
788
773
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
errors << "a #{determined_bytesize} bytes record (nth: #{writing_splits_index}) is larger than buffer chunk limit size (#{@chunk_limit_size})"
|
|
795
|
-
writing_splits_index += 1
|
|
796
|
-
next
|
|
797
|
-
end
|
|
774
|
+
original_bytesize = committed_bytesize = chunk.bytesize
|
|
775
|
+
begin
|
|
776
|
+
while writing_splits_index < splits.size
|
|
777
|
+
split = splits[writing_splits_index]
|
|
778
|
+
formatted_split = format ? format.call(split) : nil
|
|
798
779
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
780
|
+
if split.size == 1 # Check BufferChunkOverflowError
|
|
781
|
+
determined_bytesize = nil
|
|
782
|
+
if @compress != :text
|
|
783
|
+
determined_bytesize = nil
|
|
784
|
+
elsif formatted_split
|
|
785
|
+
determined_bytesize = formatted_split.bytesize
|
|
786
|
+
elsif split.first.respond_to?(:bytesize)
|
|
787
|
+
determined_bytesize = split.first.bytesize
|
|
805
788
|
end
|
|
806
789
|
|
|
807
|
-
if
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
790
|
+
if determined_bytesize && determined_bytesize > @chunk_limit_size
|
|
791
|
+
# It is a obvious case that BufferChunkOverflowError should be raised here.
|
|
792
|
+
# But if it raises here, already processed 'split' or
|
|
793
|
+
# the proceeding 'split' will be lost completely.
|
|
794
|
+
# So it is a last resort to delay raising such a exception
|
|
795
|
+
errors << "a #{determined_bytesize} bytes record (nth: #{writing_splits_index}) is larger than buffer chunk limit size (#{@chunk_limit_size})"
|
|
796
|
+
writing_splits_index += 1
|
|
797
|
+
next
|
|
811
798
|
end
|
|
812
|
-
adding_bytes = chunk.bytesize - committed_bytesize
|
|
813
799
|
|
|
814
|
-
if
|
|
815
|
-
|
|
800
|
+
if determined_bytesize.nil? || chunk.bytesize + determined_bytesize > @chunk_limit_size
|
|
801
|
+
# The split will (might) cause size over so keep already processed
|
|
802
|
+
# 'split' content here (allow performance regression a bit).
|
|
803
|
+
chunk.commit
|
|
816
804
|
committed_bytesize = chunk.bytesize
|
|
805
|
+
end
|
|
806
|
+
end
|
|
817
807
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
# As already processed content is kept after rollback, then unstaged chunk should be queued.
|
|
825
|
-
# After that, re-process current split again.
|
|
826
|
-
# New chunk should be allocated, to do it, modify @stage and so on.
|
|
827
|
-
synchronize { @stage.delete(modified_metadata) }
|
|
828
|
-
staged_chunk_used = false
|
|
829
|
-
chunk.unstaged!
|
|
830
|
-
break
|
|
831
|
-
end
|
|
832
|
-
end
|
|
808
|
+
if format
|
|
809
|
+
chunk.concat(formatted_split, split.size)
|
|
810
|
+
else
|
|
811
|
+
chunk.append(split, compress: @compress)
|
|
812
|
+
end
|
|
813
|
+
adding_bytes = chunk.bytesize - committed_bytesize
|
|
833
814
|
|
|
834
|
-
|
|
835
|
-
|
|
815
|
+
if chunk_size_over?(chunk) # split size is larger than difference between size_full? and size_over?
|
|
816
|
+
chunk.rollback
|
|
817
|
+
committed_bytesize = chunk.bytesize
|
|
818
|
+
|
|
819
|
+
if split.size == 1 # Check BufferChunkOverflowError again
|
|
820
|
+
if adding_bytes > @chunk_limit_size
|
|
821
|
+
errors << "concatenated/appended a #{adding_bytes} bytes record (nth: #{writing_splits_index}) is larger than buffer chunk limit size (#{@chunk_limit_size})"
|
|
822
|
+
writing_splits_index += 1
|
|
823
|
+
next
|
|
836
824
|
else
|
|
837
|
-
|
|
825
|
+
# As already processed content is kept after rollback, then unstaged chunk should be queued.
|
|
826
|
+
# After that, re-process current split again.
|
|
827
|
+
# New chunk should be allocated, to do it, modify @stage and so on.
|
|
828
|
+
synchronize { @stage.delete(modified_metadata) }
|
|
829
|
+
staged_chunk_used = false
|
|
830
|
+
chunk.unstaged!
|
|
831
|
+
break
|
|
838
832
|
end
|
|
833
|
+
end
|
|
839
834
|
|
|
840
|
-
|
|
835
|
+
if chunk_size_full?(chunk) || split.size == 1
|
|
836
|
+
enqueue_chunk_before_retry = true
|
|
837
|
+
else
|
|
838
|
+
splits_count *= 10
|
|
841
839
|
end
|
|
842
840
|
|
|
843
|
-
|
|
841
|
+
raise ShouldRetry
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
writing_splits_index += 1
|
|
844
845
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
end
|
|
846
|
+
if chunk_size_full?(chunk)
|
|
847
|
+
break
|
|
848
848
|
end
|
|
849
|
-
rescue
|
|
850
|
-
chunk.purge if chunk.unstaged? # unstaged chunk will leak unless purge it
|
|
851
|
-
raise
|
|
852
849
|
end
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
850
|
+
rescue
|
|
851
|
+
chunk.purge if chunk.unstaged? # unstaged chunk will leak unless purge it
|
|
852
|
+
raise
|
|
856
853
|
end
|
|
854
|
+
|
|
855
|
+
modified_chunks.last[:adding_bytesize] = chunk.bytesize - original_bytesize
|
|
856
|
+
end
|
|
857
|
+
modified_chunks.each do |data|
|
|
858
|
+
block.call(data[:chunk], data[:adding_bytesize], data[:errors])
|
|
857
859
|
end
|
|
858
860
|
rescue ShouldRetry
|
|
859
|
-
modified_chunks.each do |
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
861
|
+
modified_chunks.each do |data|
|
|
862
|
+
chunk = data[:chunk]
|
|
863
|
+
chunk.rollback rescue nil
|
|
864
|
+
if chunk.unstaged?
|
|
865
|
+
chunk.purge rescue nil
|
|
863
866
|
end
|
|
867
|
+
chunk.mon_exit rescue nil
|
|
864
868
|
end
|
|
865
869
|
enqueue_chunk(metadata) if enqueue_chunk_before_retry
|
|
866
870
|
retry
|
|
871
|
+
ensure
|
|
872
|
+
modified_chunks.each do |data|
|
|
873
|
+
chunk = data[:chunk]
|
|
874
|
+
chunk.mon_exit
|
|
875
|
+
end
|
|
867
876
|
end
|
|
868
877
|
|
|
869
878
|
STATS_KEYS = [
|
|
@@ -70,6 +70,13 @@ module Fluent::Plugin
|
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
begin
|
|
73
|
+
# Note: https://github.com/fluent/fluentd/issues/4100
|
|
74
|
+
# If the parser returns multiple records from one raw_value,
|
|
75
|
+
# this returns only the first one record.
|
|
76
|
+
# This should be fixed in the future version.
|
|
77
|
+
result_time = nil
|
|
78
|
+
result_record = nil
|
|
79
|
+
|
|
73
80
|
@parser.parse(raw_value) do |t, values|
|
|
74
81
|
if values
|
|
75
82
|
t = if @reserve_time
|
|
@@ -79,20 +86,31 @@ module Fluent::Plugin
|
|
|
79
86
|
end
|
|
80
87
|
@accessor.delete(record) if @remove_key_name_field
|
|
81
88
|
r = handle_parsed(tag, record, t, values)
|
|
82
|
-
|
|
89
|
+
|
|
90
|
+
if result_record.nil?
|
|
91
|
+
result_time = t
|
|
92
|
+
result_record = r
|
|
93
|
+
else
|
|
94
|
+
if @emit_invalid_record_to_error
|
|
95
|
+
router.emit_error_event(tag, t, r, Fluent::Plugin::Parser::ParserError.new(
|
|
96
|
+
"Could not emit the event. The parser returned multiple results, but currently filter_parser plugin only returns the first parsed result. Raw data: '#{raw_value}'"
|
|
97
|
+
))
|
|
98
|
+
end
|
|
99
|
+
end
|
|
83
100
|
else
|
|
84
101
|
if @emit_invalid_record_to_error
|
|
85
102
|
router.emit_error_event(tag, time, record, Fluent::Plugin::Parser::ParserError.new("pattern not matched with data '#{raw_value}'"))
|
|
86
103
|
end
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
end
|
|
104
|
+
|
|
105
|
+
next unless @reserve_data
|
|
106
|
+
next unless result_record.nil?
|
|
107
|
+
|
|
108
|
+
result_time = time
|
|
109
|
+
result_record = handle_parsed(tag, record, time, {})
|
|
94
110
|
end
|
|
95
111
|
end
|
|
112
|
+
|
|
113
|
+
return result_time, result_record
|
|
96
114
|
rescue Fluent::Plugin::Parser::ParserError => e
|
|
97
115
|
if @emit_invalid_record_to_error
|
|
98
116
|
raise e
|
|
@@ -203,54 +203,24 @@ module Fluent::Plugin
|
|
|
203
203
|
begin
|
|
204
204
|
path = path_info[1..-1] # remove /
|
|
205
205
|
tag = path.split('/').join('.')
|
|
206
|
-
record_time, record = parse_params(params)
|
|
207
206
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
else
|
|
214
|
-
if @use_204_response
|
|
215
|
-
return RESPONSE_204
|
|
216
|
-
else
|
|
217
|
-
return RESPONSE_200
|
|
218
|
-
end
|
|
207
|
+
mes = Fluent::MultiEventStream.new
|
|
208
|
+
parse_params(params) do |record_time, record|
|
|
209
|
+
if record.nil?
|
|
210
|
+
log.debug { "incoming event is invalid: path=#{path_info} params=#{params.to_json}" }
|
|
211
|
+
next
|
|
219
212
|
end
|
|
220
|
-
end
|
|
221
213
|
|
|
222
|
-
mes = nil
|
|
223
|
-
# Support batched requests
|
|
224
|
-
if record.is_a?(Array)
|
|
225
|
-
mes = Fluent::MultiEventStream.new
|
|
226
|
-
record.each do |single_record|
|
|
227
|
-
add_params_to_record(single_record, params)
|
|
228
|
-
|
|
229
|
-
if param_time = params['time']
|
|
230
|
-
param_time = param_time.to_f
|
|
231
|
-
single_time = param_time.zero? ? Fluent::EventTime.now : @float_time_parser.parse(param_time)
|
|
232
|
-
elsif @custom_parser
|
|
233
|
-
single_time = @custom_parser.parse_time(single_record)
|
|
234
|
-
single_time, single_record = @custom_parser.convert_values(single_time, single_record)
|
|
235
|
-
else
|
|
236
|
-
single_time = convert_time_field(single_record)
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
mes.add(single_time, single_record)
|
|
240
|
-
end
|
|
241
|
-
else
|
|
242
214
|
add_params_to_record(record, params)
|
|
243
215
|
|
|
244
216
|
time = if param_time = params['time']
|
|
245
217
|
param_time = param_time.to_f
|
|
246
218
|
param_time.zero? ? Fluent::EventTime.now : @float_time_parser.parse(param_time)
|
|
247
219
|
else
|
|
248
|
-
|
|
249
|
-
convert_time_field(record)
|
|
250
|
-
else
|
|
251
|
-
record_time
|
|
252
|
-
end
|
|
220
|
+
record_time.nil? ? convert_time_field(record) : record_time
|
|
253
221
|
end
|
|
222
|
+
|
|
223
|
+
mes.add(time, record)
|
|
254
224
|
end
|
|
255
225
|
rescue => e
|
|
256
226
|
if @dump_error_log
|
|
@@ -261,11 +231,7 @@ module Fluent::Plugin
|
|
|
261
231
|
|
|
262
232
|
# TODO server error
|
|
263
233
|
begin
|
|
264
|
-
|
|
265
|
-
router.emit_stream(tag, mes)
|
|
266
|
-
else
|
|
267
|
-
router.emit(tag, time, record)
|
|
268
|
-
end
|
|
234
|
+
router.emit_stream(tag, mes) unless mes.empty?
|
|
269
235
|
rescue => e
|
|
270
236
|
if @dump_error_log
|
|
271
237
|
log.error "failed to emit data", error: e
|
|
@@ -308,20 +274,18 @@ module Fluent::Plugin
|
|
|
308
274
|
def parse_params_default(params)
|
|
309
275
|
if msgpack = params['msgpack']
|
|
310
276
|
@parser_msgpack.parse(msgpack) do |_time, record|
|
|
311
|
-
|
|
277
|
+
yield nil, record
|
|
312
278
|
end
|
|
313
279
|
elsif js = params['json']
|
|
314
280
|
@parser_json.parse(js) do |_time, record|
|
|
315
|
-
|
|
281
|
+
yield nil, record
|
|
316
282
|
end
|
|
317
283
|
elsif ndjson = params['ndjson']
|
|
318
|
-
events = []
|
|
319
284
|
ndjson.split(/\r?\n/).each do |js|
|
|
320
285
|
@parser_json.parse(js) do |_time, record|
|
|
321
|
-
|
|
286
|
+
yield nil, record
|
|
322
287
|
end
|
|
323
288
|
end
|
|
324
|
-
return nil, events
|
|
325
289
|
else
|
|
326
290
|
raise "'json', 'ndjson' or 'msgpack' parameter is required"
|
|
327
291
|
end
|
|
@@ -329,10 +293,9 @@ module Fluent::Plugin
|
|
|
329
293
|
|
|
330
294
|
def parse_params_with_parser(params)
|
|
331
295
|
if content = params[EVENT_RECORD_PARAMETER]
|
|
332
|
-
@custom_parser.parse(content)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
296
|
+
@custom_parser.parse(content) do |time, record|
|
|
297
|
+
yield time, record
|
|
298
|
+
end
|
|
336
299
|
else
|
|
337
300
|
raise "'#{EVENT_RECORD_PARAMETER}' parameter is required"
|
|
338
301
|
end
|
|
@@ -573,6 +536,8 @@ module Fluent::Plugin
|
|
|
573
536
|
params.update WEBrick::HTTPUtils.parse_form_data(@body, boundary)
|
|
574
537
|
elsif /^application\/json/.match?(@content_type)
|
|
575
538
|
params['json'] = @body
|
|
539
|
+
elsif /^application\/csp-report/.match?(@content_type)
|
|
540
|
+
params['json'] = @body
|
|
576
541
|
elsif /^application\/msgpack/.match?(@content_type)
|
|
577
542
|
params['msgpack'] = @body
|
|
578
543
|
elsif /^application\/x-ndjson/.match?(@content_type)
|
|
@@ -580,7 +545,7 @@ module Fluent::Plugin
|
|
|
580
545
|
end
|
|
581
546
|
path_info = uri.path
|
|
582
547
|
|
|
583
|
-
if (@add_query_params)
|
|
548
|
+
if (@add_query_params)
|
|
584
549
|
|
|
585
550
|
query_params = WEBrick::HTTPUtils.parse_query(uri.query)
|
|
586
551
|
|
|
@@ -52,6 +52,7 @@ module Fluent::Plugin
|
|
|
52
52
|
super
|
|
53
53
|
@paths = []
|
|
54
54
|
@tails = {}
|
|
55
|
+
@tails_rotate_wait = {}
|
|
55
56
|
@pf_file = nil
|
|
56
57
|
@pf = nil
|
|
57
58
|
@ignore_list = []
|
|
@@ -64,6 +65,8 @@ module Fluent::Plugin
|
|
|
64
65
|
config_param :path, :string
|
|
65
66
|
desc 'path delimiter used for spliting path config'
|
|
66
67
|
config_param :path_delimiter, :string, default: ','
|
|
68
|
+
desc 'Choose using glob patterns. Adding capabilities to handle [] and ?, and {}.'
|
|
69
|
+
config_param :glob_policy, :enum, list: [:backward_compatible, :extended, :always], default: :backward_compatible
|
|
67
70
|
desc 'The tag of the event.'
|
|
68
71
|
config_param :tag, :string
|
|
69
72
|
desc 'The paths to exclude the files from watcher list.'
|
|
@@ -140,6 +143,14 @@ module Fluent::Plugin
|
|
|
140
143
|
raise Fluent::ConfigError, "either of enable_watch_timer or enable_stat_watcher must be true"
|
|
141
144
|
end
|
|
142
145
|
|
|
146
|
+
if @glob_policy == :always && @path_delimiter == ','
|
|
147
|
+
raise Fluent::ConfigError, "cannot use glob_policy as always with the default path_delimitor: `,\""
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
if @glob_policy == :extended && /\{.*,.*\}/.match(@path) && extended_glob_pattern(@path)
|
|
151
|
+
raise Fluent::ConfigError, "cannot include curly braces with glob patterns in `#{@path}\". Use glob_policy always instead."
|
|
152
|
+
end
|
|
153
|
+
|
|
143
154
|
if RESERVED_CHARS.include?(@path_delimiter)
|
|
144
155
|
rc = RESERVED_CHARS.join(', ')
|
|
145
156
|
raise Fluent::ConfigError, "#{rc} are reserved words: #{@path_delimiter}"
|
|
@@ -267,6 +278,9 @@ module Fluent::Plugin
|
|
|
267
278
|
@shutdown_start_time = Fluent::Clock.now
|
|
268
279
|
# during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
|
|
269
280
|
stop_watchers(existence_path, immediate: true, remove_watcher: false)
|
|
281
|
+
@tails_rotate_wait.keys.each do |tw|
|
|
282
|
+
detach_watcher(tw, @tails_rotate_wait[tw][:ino], false)
|
|
283
|
+
end
|
|
270
284
|
@pf_file.close if @pf_file
|
|
271
285
|
|
|
272
286
|
super
|
|
@@ -275,6 +289,7 @@ module Fluent::Plugin
|
|
|
275
289
|
def close
|
|
276
290
|
super
|
|
277
291
|
# close file handles after all threads stopped (in #close of thread plugin helper)
|
|
292
|
+
# It may be because we need to wait IOHanlder.ready_to_shutdown()
|
|
278
293
|
close_watcher_handles
|
|
279
294
|
end
|
|
280
295
|
|
|
@@ -283,6 +298,28 @@ module Fluent::Plugin
|
|
|
283
298
|
@capability.have_capability?(:effective, :dac_override)
|
|
284
299
|
end
|
|
285
300
|
|
|
301
|
+
def extended_glob_pattern(path)
|
|
302
|
+
path.include?('*') || path.include?('?') || /\[.*\]/.match(path)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Curly braces is not supported with default path_delimiter
|
|
306
|
+
# because the default delimiter of path is ",".
|
|
307
|
+
# This should be collided for wildcard pattern for curly braces and
|
|
308
|
+
# be handled as an error on #configure.
|
|
309
|
+
def use_glob?(path)
|
|
310
|
+
if @glob_policy == :always
|
|
311
|
+
# For future extensions, we decided to use `always' term to handle
|
|
312
|
+
# regular expressions as much as possible.
|
|
313
|
+
# This is because not using `true' as a returning value
|
|
314
|
+
# when choosing :always here.
|
|
315
|
+
extended_glob_pattern(path) || /\{.*,.*\}/.match(path)
|
|
316
|
+
elsif @glob_policy == :extended
|
|
317
|
+
extended_glob_pattern(path)
|
|
318
|
+
elsif @glob_policy == :backward_compatible
|
|
319
|
+
path.include?('*')
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
286
323
|
def expand_paths
|
|
287
324
|
date = Fluent::EventTime.now
|
|
288
325
|
paths = []
|
|
@@ -292,7 +329,7 @@ module Fluent::Plugin
|
|
|
292
329
|
else
|
|
293
330
|
date.to_time.strftime(path)
|
|
294
331
|
end
|
|
295
|
-
if
|
|
332
|
+
if use_glob?(path)
|
|
296
333
|
paths += Dir.glob(path).select { |p|
|
|
297
334
|
begin
|
|
298
335
|
is_file = !File.directory?(p)
|
|
@@ -327,7 +364,7 @@ module Fluent::Plugin
|
|
|
327
364
|
else
|
|
328
365
|
date.to_time.strftime(path)
|
|
329
366
|
end
|
|
330
|
-
|
|
367
|
+
use_glob?(path) ? Dir.glob(path) : path
|
|
331
368
|
}.flatten.uniq
|
|
332
369
|
# filter out non existing files, so in case pattern is without '*' we don't do unnecessary work
|
|
333
370
|
hash = {}
|
|
@@ -516,6 +553,9 @@ module Fluent::Plugin
|
|
|
516
553
|
tw.close
|
|
517
554
|
end
|
|
518
555
|
end
|
|
556
|
+
@tails_rotate_wait.keys.each do |tw|
|
|
557
|
+
tw.close
|
|
558
|
+
end
|
|
519
559
|
end
|
|
520
560
|
|
|
521
561
|
# refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
|
|
@@ -570,10 +610,6 @@ module Fluent::Plugin
|
|
|
570
610
|
detach_watcher_after_rotate_wait(tail_watcher, pe.read_inode)
|
|
571
611
|
end
|
|
572
612
|
|
|
573
|
-
# TailWatcher#close is called by another thread at shutdown phase.
|
|
574
|
-
# It causes 'can't modify string; temporarily locked' error in IOHandler
|
|
575
|
-
# so adding close_io argument to avoid this problem.
|
|
576
|
-
# At shutdown, IOHandler's io will be released automatically after detached the event loop
|
|
577
613
|
def detach_watcher(tw, ino, close_io = true)
|
|
578
614
|
if @follow_inodes && tw.ino != ino
|
|
579
615
|
log.warn("detach_watcher could be detaching an unexpected tail_watcher with a different ino.",
|
|
@@ -604,7 +640,11 @@ module Fluent::Plugin
|
|
|
604
640
|
if @open_on_every_update
|
|
605
641
|
# Detach now because it's already closed, waiting it doesn't make sense.
|
|
606
642
|
detach_watcher(tw, ino)
|
|
607
|
-
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
return if @tails_rotate_wait[tw]
|
|
646
|
+
|
|
647
|
+
if throttling_is_enabled?(tw)
|
|
608
648
|
# When the throttling feature is enabled, it might not reach EOF yet.
|
|
609
649
|
# Should ensure to read all contents before closing it, with keeping throttling.
|
|
610
650
|
start_time_to_wait = Fluent::Clock.now
|
|
@@ -612,14 +652,18 @@ module Fluent::Plugin
|
|
|
612
652
|
elapsed = Fluent::Clock.now - start_time_to_wait
|
|
613
653
|
if tw.eof? && elapsed >= @rotate_wait
|
|
614
654
|
timer.detach
|
|
655
|
+
@tails_rotate_wait.delete(tw)
|
|
615
656
|
detach_watcher(tw, ino)
|
|
616
657
|
end
|
|
617
658
|
end
|
|
659
|
+
@tails_rotate_wait[tw] = { ino: ino, timer: timer }
|
|
618
660
|
else
|
|
619
661
|
# when the throttling feature isn't enabled, just wait @rotate_wait
|
|
620
|
-
timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
|
|
662
|
+
timer = timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
|
|
663
|
+
@tails_rotate_wait.delete(tw)
|
|
621
664
|
detach_watcher(tw, ino)
|
|
622
665
|
end
|
|
666
|
+
@tails_rotate_wait[tw] = { ino: ino, timer: timer }
|
|
623
667
|
end
|
|
624
668
|
end
|
|
625
669
|
|