fluentd 1.16.0 → 1.16.7

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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/{bug_report.yaml → bug_report.yml} +1 -0
  3. data/.github/ISSUE_TEMPLATE/{feature_request.yaml → feature_request.yml} +1 -0
  4. data/.github/workflows/stale-actions.yml +24 -0
  5. data/.github/workflows/test.yml +39 -0
  6. data/CHANGELOG.md +161 -0
  7. data/CONTRIBUTING.md +1 -1
  8. data/MAINTAINERS.md +3 -3
  9. data/README.md +0 -2
  10. data/SECURITY.md +5 -9
  11. data/fluentd.gemspec +10 -2
  12. data/lib/fluent/command/ctl.rb +2 -2
  13. data/lib/fluent/command/fluentd.rb +1 -1
  14. data/lib/fluent/command/plugin_config_formatter.rb +1 -1
  15. data/lib/fluent/config/dsl.rb +1 -1
  16. data/lib/fluent/config/v1_parser.rb +2 -2
  17. data/lib/fluent/config/yaml_parser/parser.rb +4 -0
  18. data/lib/fluent/counter/server.rb +1 -1
  19. data/lib/fluent/counter/validator.rb +3 -3
  20. data/lib/fluent/engine.rb +1 -1
  21. data/lib/fluent/event.rb +8 -4
  22. data/lib/fluent/log.rb +9 -0
  23. data/lib/fluent/match.rb +1 -1
  24. data/lib/fluent/msgpack_factory.rb +6 -1
  25. data/lib/fluent/plugin/base.rb +1 -1
  26. data/lib/fluent/plugin/buffer.rb +96 -87
  27. data/lib/fluent/plugin/filter_record_transformer.rb +1 -1
  28. data/lib/fluent/plugin/in_forward.rb +1 -1
  29. data/lib/fluent/plugin/in_http.rb +8 -8
  30. data/lib/fluent/plugin/in_sample.rb +1 -1
  31. data/lib/fluent/plugin/in_tail/position_file.rb +32 -18
  32. data/lib/fluent/plugin/in_tail.rb +99 -31
  33. data/lib/fluent/plugin/in_tcp.rb +43 -0
  34. data/lib/fluent/plugin/out_exec_filter.rb +2 -2
  35. data/lib/fluent/plugin/out_file.rb +8 -0
  36. data/lib/fluent/plugin/output.rb +2 -2
  37. data/lib/fluent/plugin/parser_json.rb +4 -12
  38. data/lib/fluent/plugin_helper/event_loop.rb +2 -2
  39. data/lib/fluent/plugin_helper/record_accessor.rb +1 -1
  40. data/lib/fluent/plugin_helper/server.rb +8 -0
  41. data/lib/fluent/plugin_helper/thread.rb +3 -3
  42. data/lib/fluent/plugin_id.rb +1 -1
  43. data/lib/fluent/supervisor.rb +2 -2
  44. data/lib/fluent/system_config.rb +1 -1
  45. data/lib/fluent/version.rb +1 -1
  46. data/lib/fluent/winsvc.rb +28 -3
  47. data/templates/new_gem/test/helper.rb.erb +0 -1
  48. data/test/command/test_cat.rb +2 -2
  49. data/test/command/test_fluentd.rb +65 -12
  50. data/test/config/test_system_config.rb +2 -2
  51. data/test/helper.rb +27 -7
  52. data/test/plugin/in_tail/test_io_handler.rb +13 -14
  53. data/test/plugin/in_tail/test_position_file.rb +37 -8
  54. data/test/plugin/out_forward/test_ack_handler.rb +3 -3
  55. data/test/plugin/out_forward/test_socket_cache.rb +3 -3
  56. data/test/plugin/test_base.rb +1 -1
  57. data/test/plugin/test_buffer.rb +110 -0
  58. data/test/plugin/test_buffer_chunk.rb +11 -0
  59. data/test/plugin/test_in_forward.rb +11 -10
  60. data/test/plugin/test_in_http.rb +1 -1
  61. data/test/plugin/test_in_monitor_agent.rb +6 -6
  62. data/test/plugin/test_in_syslog.rb +25 -18
  63. data/test/plugin/test_in_tail.rb +639 -0
  64. data/test/plugin/test_in_tcp.rb +75 -5
  65. data/test/plugin/test_in_udp.rb +35 -1
  66. data/test/plugin/test_in_unix.rb +2 -2
  67. data/test/plugin/test_multi_output.rb +1 -1
  68. data/test/plugin/test_out_exec_filter.rb +14 -9
  69. data/test/plugin/test_out_file.rb +24 -4
  70. data/test/plugin/test_out_forward.rb +36 -42
  71. data/test/plugin/test_out_stream.rb +1 -1
  72. data/test/plugin/test_output.rb +12 -12
  73. data/test/plugin/test_output_as_buffered.rb +44 -44
  74. data/test/plugin/test_output_as_buffered_compress.rb +32 -18
  75. data/test/plugin/test_output_as_buffered_retries.rb +1 -1
  76. data/test/plugin/test_output_as_buffered_secondary.rb +2 -2
  77. data/test/plugin/test_parser_json.rb +31 -0
  78. data/test/plugin_helper/test_child_process.rb +15 -5
  79. data/test/plugin_helper/test_http_server_helper.rb +1 -1
  80. data/test/plugin_helper/test_server.rb +110 -38
  81. data/test/plugin_helper/test_socket.rb +1 -1
  82. data/test/test_config.rb +6 -0
  83. data/test/test_event_router.rb +2 -2
  84. data/test/test_log.rb +38 -1
  85. data/test/test_msgpack_factory.rb +32 -0
  86. data/test/test_supervisor.rb +13 -0
  87. metadata +44 -17
  88. data/.github/workflows/linux-test.yaml +0 -36
  89. data/.github/workflows/macos-test.yaml +0 -34
  90. data/.github/workflows/windows-test.yaml +0 -49
@@ -417,7 +417,7 @@ module Fluent
417
417
  if c.staged? && (enqueue || chunk_size_full?(c))
418
418
  m = c.metadata
419
419
  enqueue_chunk(m)
420
- if unstaged_chunks[m]
420
+ if unstaged_chunks[m] && !unstaged_chunks[m].empty?
421
421
  u = unstaged_chunks[m].pop
422
422
  u.synchronize do
423
423
  if u.unstaged? && !chunk_size_full?(u)
@@ -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
- c = if staged_chunk_used
753
- # Staging new chunk here is bad idea:
754
- # Recovering whole state including newly staged chunks is much harder than current implementation.
755
- modified_metadata = modified_metadata.dup_next
756
- generate_chunk(modified_metadata)
757
- else
758
- synchronize { @stage[modified_metadata] ||= generate_chunk(modified_metadata).staged! }
759
- end
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
- chunk.synchronize do
770
- raise ShouldRetry unless chunk.writable?
771
- staged_chunk_used = true if chunk.staged?
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
- if split.size == 1 # Check BufferChunkOverflowError
780
- determined_bytesize = nil
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
- if determined_bytesize && determined_bytesize > @chunk_limit_size
790
- # It is a obvious case that BufferChunkOverflowError should be raised here.
791
- # But if it raises here, already processed 'split' or
792
- # the proceeding 'split' will be lost completely.
793
- # So it is a last resort to delay raising such a exception
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
- if determined_bytesize.nil? || chunk.bytesize + determined_bytesize > @chunk_limit_size
800
- # The split will (might) cause size over so keep already processed
801
- # 'split' content here (allow performance regression a bit).
802
- chunk.commit
803
- committed_bytesize = chunk.bytesize
804
- end
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 format
808
- chunk.concat(formatted_split, split.size)
809
- else
810
- chunk.append(split, compress: @compress)
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 chunk_size_over?(chunk) # split size is larger than difference between size_full? and size_over?
815
- chunk.rollback
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
- if split.size == 1 # Check BufferChunkOverflowError again
819
- if adding_bytes > @chunk_limit_size
820
- errors << "concatenated/appended a #{adding_bytes} bytes record (nth: #{writing_splits_index}) is larger than buffer chunk limit size (#{@chunk_limit_size})"
821
- writing_splits_index += 1
822
- next
823
- else
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
- if chunk_size_full?(chunk) || split.size == 1
835
- enqueue_chunk_before_retry = true
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
- splits_count *= 10
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
- raise ShouldRetry
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
- writing_splits_index += 1
841
+ raise ShouldRetry
842
+ end
843
+
844
+ writing_splits_index += 1
844
845
 
845
- if chunk_size_full?(chunk)
846
- break
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
- block.call(chunk, chunk.bytesize - original_bytesize, errors)
855
- errors = []
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 |mc|
860
- mc.rollback rescue nil
861
- if mc.unstaged?
862
- mc.purge rescue nil
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 = [
@@ -316,7 +316,7 @@ module Fluent::Plugin
316
316
  end
317
317
 
318
318
  (Object.instance_methods).each do |m|
319
- undef_method m unless m.to_s =~ /^__|respond_to_missing\?|object_id|public_methods|instance_eval|method_missing|define_singleton_method|respond_to\?|new_ostruct_member|^class$/
319
+ undef_method m unless /^__|respond_to_missing\?|object_id|public_methods|instance_eval|method_missing|define_singleton_method|respond_to\?|new_ostruct_member|^class$/.match?(m.to_s)
320
320
  end
321
321
  end
322
322
  end
@@ -430,7 +430,7 @@ module Fluent::Plugin
430
430
  end
431
431
  _ping, hostname, shared_key_salt, shared_key_hexdigest, username, password_digest = message
432
432
 
433
- node = @nodes.select{|n| n[:address].include?(remote_addr) rescue false }.first
433
+ node = @nodes.find{|n| n[:address].include?(remote_addr) rescue false }
434
434
  if !node && !@security.allow_anonymous_source
435
435
  log.warn "Anonymous client disallowed", address: remote_addr, hostname: hostname
436
436
  return false, "anonymous source host '#{remote_addr}' denied", nil
@@ -428,7 +428,7 @@ module Fluent::Plugin
428
428
  @content_type = ""
429
429
  @content_encoding = ""
430
430
  headers.each_pair {|k,v|
431
- @env["HTTP_#{k.gsub('-','_').upcase}"] = v
431
+ @env["HTTP_#{k.tr('-','_').upcase}"] = v
432
432
  case k
433
433
  when /\AExpect\z/i
434
434
  expect = v
@@ -439,9 +439,9 @@ module Fluent::Plugin
439
439
  when /\AContent-Encoding\Z/i
440
440
  @content_encoding = v
441
441
  when /\AConnection\Z/i
442
- if v =~ /close/i
442
+ if /close/i.match?(v)
443
443
  @keep_alive = false
444
- elsif v =~ /Keep-alive/i
444
+ elsif /Keep-alive/i.match?(v)
445
445
  @keep_alive = true
446
446
  end
447
447
  when /\AOrigin\Z/i
@@ -566,16 +566,16 @@ module Fluent::Plugin
566
566
 
567
567
  if @format_name != 'default'
568
568
  params[EVENT_RECORD_PARAMETER] = @body
569
- elsif @content_type =~ /^application\/x-www-form-urlencoded/
569
+ elsif /^application\/x-www-form-urlencoded/.match?(@content_type)
570
570
  params.update WEBrick::HTTPUtils.parse_query(@body)
571
571
  elsif @content_type =~ /^multipart\/form-data; boundary=(.+)/
572
572
  boundary = WEBrick::HTTPUtils.dequote($1)
573
573
  params.update WEBrick::HTTPUtils.parse_form_data(@body, boundary)
574
- elsif @content_type =~ /^application\/json/
574
+ elsif /^application\/json/.match?(@content_type)
575
575
  params['json'] = @body
576
- elsif @content_type =~ /^application\/msgpack/
576
+ elsif /^application\/msgpack/.match?(@content_type)
577
577
  params['msgpack'] = @body
578
- elsif @content_type =~ /^application\/x-ndjson/
578
+ elsif /^application\/x-ndjson/.match?(@content_type)
579
579
  params['ndjson'] = @body
580
580
  end
581
581
  path_info = uri.path
@@ -585,7 +585,7 @@ module Fluent::Plugin
585
585
  query_params = WEBrick::HTTPUtils.parse_query(uri.query)
586
586
 
587
587
  query_params.each_pair {|k,v|
588
- params["QUERY_#{k.gsub('-','_').upcase}"] = v
588
+ params["QUERY_#{k.tr('-','_').upcase}"] = v
589
589
  }
590
590
  end
591
591
 
@@ -64,7 +64,7 @@ module Fluent::Plugin
64
64
  def configure(conf)
65
65
  super
66
66
  @sample_index = 0
67
- config = conf.elements.select{|e| e.name == 'storage' }.first
67
+ config = conf.elements.find{|e| e.name == 'storage' }
68
68
  @storage = storage_create(usage: 'suspend', conf: config, default_type: DEFAULT_STORAGE_TYPE)
69
69
  end
70
70
 
@@ -53,10 +53,16 @@ module Fluent::Plugin
53
53
  }
54
54
  end
55
55
 
56
+ def unwatch_removed_targets(existing_targets)
57
+ @map.reject { |key, entry|
58
+ existing_targets.key?(key)
59
+ }.each_key { |key|
60
+ unwatch_key(key)
61
+ }
62
+ end
63
+
56
64
  def unwatch(target_info)
57
- if (entry = @map.delete(@follow_inodes ? target_info.ino : target_info.path))
58
- entry.update_pos(UNWATCHED_POSITION)
59
- end
65
+ unwatch_key(@follow_inodes ? target_info.ino : target_info.path)
60
66
  end
61
67
 
62
68
  def load(existing_targets = nil)
@@ -96,6 +102,7 @@ module Fluent::Plugin
96
102
  end
97
103
 
98
104
  entries = fetch_compacted_entries
105
+ @logger&.debug "Compacted entries: ", entries.keys
99
106
 
100
107
  @file_mutex.synchronize do
101
108
  if last_modified == @file.mtime && size == @file.size
@@ -117,17 +124,31 @@ module Fluent::Plugin
117
124
 
118
125
  private
119
126
 
127
+ def unwatch_key(key)
128
+ if (entry = @map.delete(key))
129
+ entry.update_pos(UNWATCHED_POSITION)
130
+ end
131
+ end
132
+
120
133
  def compact(existing_targets = nil)
121
134
  @file_mutex.synchronize do
122
- entries = fetch_compacted_entries(existing_targets).values.map(&:to_entry_fmt)
135
+ entries = fetch_compacted_entries
136
+ @logger&.debug "Compacted entries: ", entries.keys
137
+
138
+ if existing_targets
139
+ entries = remove_deleted_files_entries(entries, existing_targets)
140
+ @logger&.debug "Remove missing entries.",
141
+ existing_targets: existing_targets.keys,
142
+ entries_after_removing: entries.keys
143
+ end
123
144
 
124
145
  @file.pos = 0
125
146
  @file.truncate(0)
126
- @file.write(entries.join)
147
+ @file.write(entries.values.map(&:to_entry_fmt).join)
127
148
  end
128
149
  end
129
150
 
130
- def fetch_compacted_entries(existing_targets = nil)
151
+ def fetch_compacted_entries
131
152
  entries = {}
132
153
 
133
154
  @file.pos = 0
@@ -145,31 +166,24 @@ module Fluent::Plugin
145
166
  if pos == UNWATCHED_POSITION
146
167
  @logger.debug "Remove unwatched line from pos_file: #{line}" if @logger
147
168
  else
148
- if entries.include?(path)
149
- @logger.warn("#{path} already exists. use latest one: deleted #{entries[path]}") if @logger
150
- end
151
-
152
169
  if @follow_inodes
170
+ @logger&.warn("#{path} (inode: #{ino}) already exists. use latest one: deleted #{entries[ino]}") if entries.include?(ino)
153
171
  entries[ino] = Entry.new(path, pos, ino, file_pos + path.bytesize + 1)
154
172
  else
173
+ @logger&.warn("#{path} already exists. use latest one: deleted #{entries[path]}") if entries.include?(path)
155
174
  entries[path] = Entry.new(path, pos, ino, file_pos + path.bytesize + 1)
156
175
  end
157
176
  file_pos += line.size
158
177
  end
159
178
  end
160
179
 
161
- entries = remove_deleted_files_entries(entries, existing_targets)
162
180
  entries
163
181
  end
164
182
 
165
183
  def remove_deleted_files_entries(existent_entries, existing_targets)
166
- if existing_targets
167
- existent_entries.select { |path_or_ino|
168
- existing_targets.key?(path_or_ino)
169
- }
170
- else
171
- existent_entries
172
- end
184
+ existent_entries.select { |path_or_ino|
185
+ existing_targets.key?(path_or_ino)
186
+ }
173
187
  end
174
188
  end
175
189