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
@@ -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 = []
@@ -267,6 +268,9 @@ module Fluent::Plugin
267
268
  @shutdown_start_time = Fluent::Clock.now
268
269
  # during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
269
270
  stop_watchers(existence_path, immediate: true, remove_watcher: false)
271
+ @tails_rotate_wait.keys.each do |tw|
272
+ detach_watcher(tw, @tails_rotate_wait[tw][:ino], false)
273
+ end
270
274
  @pf_file.close if @pf_file
271
275
 
272
276
  super
@@ -275,6 +279,7 @@ module Fluent::Plugin
275
279
  def close
276
280
  super
277
281
  # close file handles after all threads stopped (in #close of thread plugin helper)
282
+ # It may be because we need to wait IOHanlder.ready_to_shutdown()
278
283
  close_watcher_handles
279
284
  end
280
285
 
@@ -370,17 +375,52 @@ module Fluent::Plugin
370
375
  def refresh_watchers
371
376
  target_paths_hash = expand_paths
372
377
  existence_paths_hash = existence_path
373
-
378
+
374
379
  log.debug {
375
380
  target_paths_str = target_paths_hash.collect { |key, target_info| target_info.path }.join(",")
376
381
  existence_paths_str = existence_paths_hash.collect { |key, target_info| target_info.path }.join(",")
377
382
  "tailing paths: target = #{target_paths_str} | existing = #{existence_paths_str}"
378
383
  }
379
384
 
380
- unwatched_hash = existence_paths_hash.reject {|key, value| target_paths_hash.key?(key)}
385
+ if !@follow_inodes
386
+ need_unwatch_in_stop_watchers = true
387
+ else
388
+ # When using @follow_inodes, need this to unwatch the rotated old inode when it disappears.
389
+ # After `update_watcher` detaches an old TailWatcher, the inode is lost from the `@tails`.
390
+ # So that inode can't be contained in `removed_hash`, and can't be unwatched by `stop_watchers`.
391
+ #
392
+ # This logic may work for `@follow_inodes false` too.
393
+ # Just limiting the case to suppress the impact to existing logics.
394
+ @pf&.unwatch_removed_targets(target_paths_hash)
395
+ need_unwatch_in_stop_watchers = false
396
+ end
397
+
398
+ removed_hash = existence_paths_hash.reject {|key, value| target_paths_hash.key?(key)}
381
399
  added_hash = target_paths_hash.reject {|key, value| existence_paths_hash.key?(key)}
382
400
 
383
- stop_watchers(unwatched_hash, immediate: false, unwatched: true) unless unwatched_hash.empty?
401
+ # If an exisiting TailWatcher already follows a target path with the different inode,
402
+ # it means that the TailWatcher following the rotated file still exists. In this case,
403
+ # `refresh_watcher` can't start the new TailWatcher for the new current file. So, we
404
+ # should output a warning log in order to prevent silent collection stops.
405
+ # (Such as https://github.com/fluent/fluentd/pull/4327)
406
+ # (Usually, such a TailWatcher should be removed from `@tails` in `update_watcher`.)
407
+ # (The similar warning may work for `@follow_inodes true` too. Just limiting the case
408
+ # to suppress the impact to existing logics.)
409
+ unless @follow_inodes
410
+ target_paths_hash.each do |path, target|
411
+ next unless @tails.key?(path)
412
+ # We can't use `existence_paths_hash[path].ino` because it is from `TailWatcher.ino`,
413
+ # which is very unstable parameter. (It can be `nil` or old).
414
+ # So, we need to use `TailWatcher.pe.read_inode`.
415
+ existing_watcher_inode = @tails[path].pe.read_inode
416
+ if existing_watcher_inode != target.ino
417
+ log.warn "Could not follow a file (inode: #{target.ino}) because an existing watcher for that filepath follows a different inode: #{existing_watcher_inode} (e.g. keeps watching a already rotated file). If you keep getting this message, please restart Fluentd.",
418
+ filepath: target.path
419
+ end
420
+ end
421
+ end
422
+
423
+ stop_watchers(removed_hash, unwatched: need_unwatch_in_stop_watchers) unless removed_hash.empty?
384
424
  start_watchers(added_hash) unless added_hash.empty?
385
425
  @startup = false if @startup
386
426
  end
@@ -481,11 +521,32 @@ module Fluent::Plugin
481
521
  tw.close
482
522
  end
483
523
  end
524
+ @tails_rotate_wait.keys.each do |tw|
525
+ tw.close
526
+ end
484
527
  end
485
528
 
486
529
  # refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
487
- def update_watcher(target_info, pe)
488
- path = target_info.path
530
+ def update_watcher(tail_watcher, pe, new_inode)
531
+ # TODO we should use another callback for this.
532
+ # To supress impact to existing logics, limit the case to `@follow_inodes`.
533
+ # We may not need `@follow_inodes` condition.
534
+ if @follow_inodes && new_inode.nil?
535
+ # nil inode means the file disappeared, so we only need to stop it.
536
+ @tails.delete(tail_watcher.path)
537
+ # https://github.com/fluent/fluentd/pull/4237#issuecomment-1633358632
538
+ # Because of this problem, log duplication can occur during `rotate_wait`.
539
+ # Need to set `rotate_wait 0` for a workaround.
540
+ # Duplication will occur if `refresh_watcher` is called during the `rotate_wait`.
541
+ # In that case, `refresh_watcher` will add the new TailWatcher to tail the same target,
542
+ # and it causes the log duplication.
543
+ # (Other `detach_watcher_after_rotate_wait` may have the same problem.
544
+ # We need the mechanism not to add duplicated TailWathcer with detaching TailWatcher.)
545
+ detach_watcher_after_rotate_wait(tail_watcher, pe.read_inode)
546
+ return
547
+ end
548
+
549
+ path = tail_watcher.path
489
550
 
490
551
  log.info("detected rotation of #{path}; waiting #{@rotate_wait} seconds")
491
552
 
@@ -499,30 +560,29 @@ module Fluent::Plugin
499
560
  end
500
561
  end
501
562
 
502
- rotated_tw = @tails[path]
563
+ new_target_info = TargetInfo.new(path, new_inode)
503
564
 
504
565
  if @follow_inodes
505
- new_position_entry = @pf[target_info]
506
-
566
+ new_position_entry = @pf[new_target_info]
567
+ # If `refresh_watcher` find the new file before, this will not be zero.
568
+ # In this case, only we have to do is detaching the current tail_watcher.
507
569
  if new_position_entry.read_inode == 0
508
- # When follow_inodes is true, it's not cleaned up by refresh_watcher.
509
- # So it should be unwatched here explicitly.
510
- rotated_tw.unwatched = true if rotated_tw
511
- @tails[path] = setup_watcher(target_info, new_position_entry)
570
+ @tails[path] = setup_watcher(new_target_info, new_position_entry)
512
571
  @tails[path].on_notify
513
572
  end
514
573
  else
515
- @tails[path] = setup_watcher(target_info, pe)
574
+ @tails[path] = setup_watcher(new_target_info, pe)
516
575
  @tails[path].on_notify
517
576
  end
518
- detach_watcher_after_rotate_wait(rotated_tw, pe.read_inode) if rotated_tw
577
+
578
+ detach_watcher_after_rotate_wait(tail_watcher, pe.read_inode)
519
579
  end
520
580
 
521
- # TailWatcher#close is called by another thread at shutdown phase.
522
- # It causes 'can't modify string; temporarily locked' error in IOHandler
523
- # so adding close_io argument to avoid this problem.
524
- # At shutdown, IOHandler's io will be released automatically after detached the event loop
525
581
  def detach_watcher(tw, ino, close_io = true)
582
+ if @follow_inodes && tw.ino != ino
583
+ log.warn("detach_watcher could be detaching an unexpected tail_watcher with a different ino.",
584
+ path: tw.path, actual_ino_in_tw: tw.ino, expect_ino_to_close: ino)
585
+ end
526
586
  tw.watchers.each do |watcher|
527
587
  event_loop_detach(watcher)
528
588
  end
@@ -530,7 +590,7 @@ module Fluent::Plugin
530
590
 
531
591
  tw.close if close_io
532
592
 
533
- if tw.unwatched && @pf
593
+ if @pf && tw.unwatched && (@follow_inode || !@tails[tw.path])
534
594
  target_info = TargetInfo.new(tw.path, ino)
535
595
  @pf.unwatch(target_info)
536
596
  end
@@ -548,7 +608,11 @@ module Fluent::Plugin
548
608
  if @open_on_every_update
549
609
  # Detach now because it's already closed, waiting it doesn't make sense.
550
610
  detach_watcher(tw, ino)
551
- elsif throttling_is_enabled?(tw)
611
+ end
612
+
613
+ return if @tails_rotate_wait[tw]
614
+
615
+ if throttling_is_enabled?(tw)
552
616
  # When the throttling feature is enabled, it might not reach EOF yet.
553
617
  # Should ensure to read all contents before closing it, with keeping throttling.
554
618
  start_time_to_wait = Fluent::Clock.now
@@ -556,14 +620,18 @@ module Fluent::Plugin
556
620
  elapsed = Fluent::Clock.now - start_time_to_wait
557
621
  if tw.eof? && elapsed >= @rotate_wait
558
622
  timer.detach
623
+ @tails_rotate_wait.delete(tw)
559
624
  detach_watcher(tw, ino)
560
625
  end
561
626
  end
627
+ @tails_rotate_wait[tw] = { ino: ino, timer: timer }
562
628
  else
563
629
  # when the throttling feature isn't enabled, just wait @rotate_wait
564
- timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
630
+ timer = timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
631
+ @tails_rotate_wait.delete(tw)
565
632
  detach_watcher(tw, ino)
566
633
  end
634
+ @tails_rotate_wait[tw] = { ino: ino, timer: timer }
567
635
  end
568
636
  end
569
637
 
@@ -778,7 +846,7 @@ module Fluent::Plugin
778
846
  attr_accessor :group_watcher
779
847
 
780
848
  def tag
781
- @parsed_tag ||= @path.tr('/', '.').gsub(/\.+/, '.').gsub(/^\./, '')
849
+ @parsed_tag ||= @path.tr('/', '.').squeeze('.').gsub(/^\./, '')
782
850
  end
783
851
 
784
852
  def register_watcher(watcher)
@@ -874,21 +942,21 @@ module Fluent::Plugin
874
942
 
875
943
  if watcher_needs_update
876
944
  if @follow_inodes
877
- # No need to update a watcher if stat is nil (file not present), because moving to inodes will create
878
- # new watcher, and old watcher will be closed by stop_watcher in refresh_watchers method
879
- # don't want to swap state because we need latest read offset in pos file even after rotate_wait
880
- if stat
881
- target_info = TargetInfo.new(@path, stat.ino)
882
- @update_watcher.call(target_info, @pe)
883
- end
945
+ # If stat is nil (file not present), NEED to stop and discard this watcher.
946
+ # When the file is disappeared but is resurrected soon, then `#refresh_watcher`
947
+ # can't recognize this TailWatcher needs to be stopped.
948
+ # This can happens when the file is rotated.
949
+ # If a notify comes before the new file for the path is created during rotation,
950
+ # then it appears as if the file was resurrected once it disappeared.
951
+ # Don't want to swap state because we need latest read offset in pos file even after rotate_wait
952
+ @update_watcher.call(self, @pe, stat&.ino)
884
953
  else
885
954
  # Permit to handle if stat is nil (file not present).
886
955
  # If a file is mv-ed and a new file is created during
887
956
  # calling `#refresh_watchers`s, and `#refresh_watchers` won't run `#start_watchers`
888
957
  # and `#stop_watchers()` for the path because `target_paths_hash`
889
958
  # always contains the path.
890
- target_info = TargetInfo.new(@path, stat ? stat.ino : nil)
891
- @update_watcher.call(target_info, swap_state(@pe))
959
+ @update_watcher.call(self, swap_state(@pe), stat&.ino)
892
960
  end
893
961
  else
894
962
  @log.info "detected rotation of #{@path}"
@@ -36,6 +36,10 @@ module Fluent::Plugin
36
36
  desc "The field name of the client's address."
37
37
  config_param :source_address_key, :string, default: nil
38
38
 
39
+ # Setting default to nil for backward compatibility
40
+ desc "The max bytes of message."
41
+ config_param :message_length_limit, :size, default: nil
42
+
39
43
  config_param :blocking_timeout, :time, default: 0.5
40
44
 
41
45
  desc 'The payload is read up to this character.'
@@ -102,6 +106,7 @@ module Fluent::Plugin
102
106
 
103
107
  log.info "listening tcp socket", bind: @bind, port: @port
104
108
  del_size = @delimiter.length
109
+ discard_till_next_delimiter = false
105
110
  if @_extract_enabled && @_extract_tag_key
106
111
  server_create(:in_tcp_server_single_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key, send_keepalive_packet: @send_keepalive_packet) do |data, conn|
107
112
  unless check_client(conn)
@@ -116,6 +121,16 @@ module Fluent::Plugin
116
121
  msg = buf[pos...i]
117
122
  pos = i + del_size
118
123
 
124
+ if discard_till_next_delimiter
125
+ discard_till_next_delimiter = false
126
+ next
127
+ end
128
+
129
+ if !@message_length_limit.nil? && @message_length_limit < msg.bytesize
130
+ log.info "The received data is larger than 'message_length_limit', dropped:", limit: @message_length_limit, size: msg.bytesize, head: msg[...32]
131
+ next
132
+ end
133
+
119
134
  @parser.parse(msg) do |time, record|
120
135
  unless time && record
121
136
  log.warn "pattern not matched", message: msg
@@ -131,6 +146,15 @@ module Fluent::Plugin
131
146
  end
132
147
  end
133
148
  buf.slice!(0, pos) if pos > 0
149
+ # If the buffer size exceeds the limit here, it means that the next message will definitely exceed the limit.
150
+ # So we should clear the buffer here. Otherwise, it will keep storing useless data until the next delimiter comes.
151
+ if !@message_length_limit.nil? && @message_length_limit < buf.bytesize
152
+ log.info "The buffer size exceeds 'message_length_limit', cleared:", limit: @message_length_limit, size: buf.bytesize, head: buf[...32]
153
+ buf.clear
154
+ # We should discard the subsequent data until the next delimiter comes.
155
+ discard_till_next_delimiter = true
156
+ next
157
+ end
134
158
  end
135
159
  else
136
160
  server_create(:in_tcp_server_batch_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key, send_keepalive_packet: @send_keepalive_packet) do |data, conn|
@@ -147,6 +171,16 @@ module Fluent::Plugin
147
171
  msg = buf[pos...i]
148
172
  pos = i + del_size
149
173
 
174
+ if discard_till_next_delimiter
175
+ discard_till_next_delimiter = false
176
+ next
177
+ end
178
+
179
+ if !@message_length_limit.nil? && @message_length_limit < msg.bytesize
180
+ log.info "The received data is larger than 'message_length_limit', dropped:", limit: @message_length_limit, size: msg.bytesize, head: msg[...32]
181
+ next
182
+ end
183
+
150
184
  @parser.parse(msg) do |time, record|
151
185
  unless time && record
152
186
  log.warn "pattern not matched", message: msg
@@ -161,6 +195,15 @@ module Fluent::Plugin
161
195
  end
162
196
  router.emit_stream(@tag, es)
163
197
  buf.slice!(0, pos) if pos > 0
198
+ # If the buffer size exceeds the limit here, it means that the next message will definitely exceed the limit.
199
+ # So we should clear the buffer here. Otherwise, it will keep storing useless data until the next delimiter comes.
200
+ if !@message_length_limit.nil? && @message_length_limit < buf.bytesize
201
+ log.info "The buffer size exceeds 'message_length_limit', cleared:", limit: @message_length_limit, size: buf.bytesize, head: buf[...32]
202
+ buf.clear
203
+ # We should discard the subsequent data until the next delimiter comes.
204
+ discard_till_next_delimiter = true
205
+ next
206
+ end
164
207
  end
165
208
  end
166
209
  end
@@ -163,7 +163,7 @@ module Fluent::Plugin
163
163
  0
164
164
  elsif (@child_respawn == 'inf') || (@child_respawn == '-1')
165
165
  -1
166
- elsif @child_respawn =~ /^\d+$/
166
+ elsif /^\d+$/.match?(@child_respawn)
167
167
  @child_respawn.to_i
168
168
  else
169
169
  raise ConfigError, "child_respawn option argument invalid: none(or 0), inf(or -1) or positive number"
@@ -187,7 +187,7 @@ module Fluent::Plugin
187
187
  @rr = 0
188
188
 
189
189
  exit_callback = ->(status){
190
- c = @children.select{|child| child.pid == status.pid }.first
190
+ c = @children.find{|child| child.pid == status.pid }
191
191
  if c
192
192
  unless self.stopped?
193
193
  log.warn "child process exits with error code", code: status.to_i, status: status.exitstatus, signal: status.termsig
@@ -172,6 +172,14 @@ module Fluent::Plugin
172
172
  log.warn "symlink_path is unavailable on Windows platform. disabled."
173
173
  @symlink_path = nil
174
174
  else
175
+ placeholder_validators(:symlink_path, @symlink_path).reject{ |v| v.type == :time }.each do |v|
176
+ begin
177
+ v.validate!
178
+ rescue Fluent::ConfigError => e
179
+ log.warn "#{e}. This means multiple chunks are competing for a single symlink_path, so some logs may not be taken from the symlink."
180
+ end
181
+ end
182
+
175
183
  @buffer.extend SymlinkBufferMixin
176
184
  @buffer.symlink_path = @symlink_path
177
185
  @buffer.output_plugin_for_symlink = self
@@ -426,7 +426,7 @@ module Fluent
426
426
  end
427
427
  @secondary.acts_as_secondary(self)
428
428
  @secondary.configure(secondary_conf)
429
- if (@secondary.class != SecondaryFileOutput) &&
429
+ if (@secondary.class.to_s != "Fluent::Plugin::SecondaryFileOutput") &&
430
430
  (self.class != @secondary.class) &&
431
431
  (@custom_format || @secondary.implement?(:custom_format))
432
432
  log.warn "Use different plugin for secondary. Check the plugin works with primary like secondary_file", primary: self.class.to_s, secondary: @secondary.class.to_s
@@ -824,7 +824,7 @@ module Fluent
824
824
  if str.include?('${tag}')
825
825
  rvalue = rvalue.gsub('${tag}', metadata.tag)
826
826
  end
827
- if str =~ CHUNK_TAG_PLACEHOLDER_PATTERN
827
+ if CHUNK_TAG_PLACEHOLDER_PATTERN.match?(str)
828
828
  hash = {}
829
829
  tag_parts = metadata.tag.split('.')
830
830
  tag_parts.each_with_index do |part, i|
@@ -50,23 +50,15 @@ module Fluent
50
50
  def configure_json_parser(name)
51
51
  case name
52
52
  when :oj
53
- raise LoadError unless Fluent::OjOptions.available?
54
- [Oj.method(:load), Oj::ParseError]
53
+ return [Oj.method(:load), Oj::ParseError] if Fluent::OjOptions.available?
54
+
55
+ log&.info "Oj is not installed, and failing back to Yajl for json parser"
56
+ configure_json_parser(:yajl)
55
57
  when :json then [JSON.method(:load), JSON::ParserError]
56
58
  when :yajl then [Yajl.method(:load), Yajl::ParseError]
57
59
  else
58
60
  raise "BUG: unknown json parser specified: #{name}"
59
61
  end
60
- rescue LoadError => ex
61
- name = :yajl
62
- if log
63
- if /\boj\z/ =~ ex.message
64
- log.info "Oj is not installed, and failing back to Yajl for json parser"
65
- else
66
- log.warn ex.message
67
- end
68
- end
69
- retry
70
62
  end
71
63
 
72
64
  def parse(text)
@@ -99,7 +99,7 @@ module Fluent
99
99
 
100
100
  def shutdown
101
101
  @_event_loop_mutex.synchronize do
102
- @_event_loop_attached_watchers.reverse.each do |w|
102
+ @_event_loop_attached_watchers.reverse_each do |w|
103
103
  if w.attached?
104
104
  begin
105
105
  w.detach
@@ -116,7 +116,7 @@ module Fluent
116
116
  def after_shutdown
117
117
  timeout_at = Fluent::Clock.now + EVENT_LOOP_SHUTDOWN_TIMEOUT
118
118
  @_event_loop_mutex.synchronize do
119
- @_event_loop.watchers.reverse.each do |w|
119
+ @_event_loop.watchers.reverse_each do |w|
120
120
  begin
121
121
  w.detach
122
122
  rescue => e
@@ -119,7 +119,7 @@ module Fluent
119
119
  def self.validate_dot_keys(keys)
120
120
  keys.each { |key|
121
121
  next unless key.is_a?(String)
122
- if /\s+/.match(key)
122
+ if /\s+/.match?(key)
123
123
  raise Fluent::ConfigError, "whitespace character is not allowed in dot notation. Use bracket notation: #{key}"
124
124
  end
125
125
  }
@@ -545,6 +545,10 @@ module Fluent
545
545
  data = @sock.recv(@max_bytes, @flags)
546
546
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR, Errno::ECONNRESET, IOError, Errno::EBADF
547
547
  return
548
+ rescue Errno::EMSGSIZE
549
+ # Windows ONLY: This happens when the data size is larger than `@max_bytes`.
550
+ @log.info "A received data was ignored since it was too large."
551
+ return
548
552
  end
549
553
  @callback.call(data)
550
554
  rescue => e
@@ -558,6 +562,10 @@ module Fluent
558
562
  data, addr = @sock.recvfrom(@max_bytes)
559
563
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR, Errno::ECONNRESET, IOError, Errno::EBADF
560
564
  return
565
+ rescue Errno::EMSGSIZE
566
+ # Windows ONLY: This happens when the data size is larger than `@max_bytes`.
567
+ @log.info "A received data was ignored since it was too large."
568
+ return
561
569
  end
562
570
  @callback.call(data, UDPCallbackSocket.new(@sock, addr, close_socket: @close_socket))
563
571
  rescue => e
@@ -101,16 +101,16 @@ module Fluent
101
101
  end
102
102
 
103
103
  def thread_exist?(title)
104
- @_threads.values.select{|thread| title == thread[:_fluentd_plugin_helper_thread_title] }.size > 0
104
+ @_threads.values.count{|thread| title == thread[:_fluentd_plugin_helper_thread_title] } > 0
105
105
  end
106
106
 
107
107
  def thread_started?(title)
108
- t = @_threads.values.select{|thread| title == thread[:_fluentd_plugin_helper_thread_title] }.first
108
+ t = @_threads.values.find{|thread| title == thread[:_fluentd_plugin_helper_thread_title] }
109
109
  t && t[:_fluentd_plugin_helper_thread_started]
110
110
  end
111
111
 
112
112
  def thread_running?(title)
113
- t = @_threads.values.select{|thread| title == thread[:_fluentd_plugin_helper_thread_title] }.first
113
+ t = @_threads.values.find{|thread| title == thread[:_fluentd_plugin_helper_thread_title] }
114
114
  t && t[:_fluentd_plugin_helper_thread_running]
115
115
  end
116
116
 
@@ -49,7 +49,7 @@ module Fluent
49
49
  # Thread::Backtrace::Location#path returns base filename or absolute path.
50
50
  # #absolute_path returns absolute_path always.
51
51
  # https://bugs.ruby-lang.org/issues/12159
52
- if location.absolute_path =~ /\/test_[^\/]+\.rb$/ # location.path =~ /test_.+\.rb$/
52
+ if /\/test_[^\/]+\.rb$/.match?(location.absolute_path) # location.path =~ /test_.+\.rb$/
53
53
  return true
54
54
  end
55
55
  end
@@ -440,7 +440,7 @@ module Fluent
440
440
  stop_immediately_at_unrecoverable_exit: true,
441
441
  root_dir: params['root_dir'],
442
442
  logger: $log,
443
- log: $log.out,
443
+ log: $log&.out,
444
444
  log_level: params['log_level'],
445
445
  chuser: params['chuser'],
446
446
  chgroup: params['chgroup'],
@@ -697,7 +697,7 @@ module Fluent
697
697
  actual_log_path = @log_path
698
698
 
699
699
  # We need to prepare a unique path for each worker since Windows locks files.
700
- if Fluent.windows? && rotate
700
+ if Fluent.windows? && rotate && @log_path && @log_path != "-"
701
701
  actual_log_path = Fluent::Log.per_process_path(@log_path, process_type, worker_id)
702
702
  end
703
703
 
@@ -62,7 +62,7 @@ module Fluent
62
62
  config_param :time_format, :string, default: '%Y-%m-%d %H:%M:%S %z'
63
63
  config_param :rotate_age, default: nil do |v|
64
64
  if Fluent::Log::LOG_ROTATE_AGE.include?(v)
65
- v.to_sym
65
+ v
66
66
  else
67
67
  begin
68
68
  Integer(v)
@@ -16,6 +16,6 @@
16
16
 
17
17
  module Fluent
18
18
 
19
- VERSION = '1.16.0'
19
+ VERSION = '1.16.7'
20
20
 
21
21
  end
data/lib/fluent/winsvc.rb CHANGED
@@ -63,10 +63,12 @@ begin
63
63
  end
64
64
 
65
65
  def service_stop
66
- set_event(@service_name)
67
- if @pid > 0
68
- Process.waitpid(@pid)
66
+ if @pid <= 0
67
+ set_event(@service_name)
68
+ return
69
69
  end
70
+
71
+ wait_supervisor_finished
70
72
  end
71
73
 
72
74
  def service_paramchange
@@ -91,6 +93,29 @@ begin
91
93
  ev.set
92
94
  ev.close
93
95
  end
96
+
97
+ def repeat_set_event_several_times_until_success(event_name)
98
+ retries = 0
99
+ max_retries = 10
100
+ delay_sec = 3
101
+
102
+ begin
103
+ set_event(event_name)
104
+ rescue Errno::ENOENT
105
+ # This error occurs when the supervisor process has not yet created the event.
106
+ # If STOP is immediately executed, this state will occur.
107
+ # Retry `set_event' to wait for the initialization of the supervisor.
108
+ retries += 1
109
+ raise if max_retries < retries
110
+ sleep(delay_sec)
111
+ retry
112
+ end
113
+ end
114
+
115
+ def wait_supervisor_finished
116
+ repeat_set_event_several_times_until_success(@service_name)
117
+ Process.waitpid(@pid)
118
+ end
94
119
  end
95
120
 
96
121
  FluentdService.new(opts[:service_name]).mainloop
@@ -1,4 +1,3 @@
1
- $LOAD_PATH.unshift(File.expand_path("../../", __FILE__))
2
1
  require "test-unit"
3
2
  require "fluent/test"
4
3
  require "fluent/test/driver/<%= type %>"
@@ -18,7 +18,7 @@ class TestFluentCat < ::Test::Unit::TestCase
18
18
  @primary = create_primary
19
19
  metadata = @primary.buffer.new_metadata
20
20
  @chunk = create_chunk(@primary, metadata, @es)
21
- @port = unused_port
21
+ @port = unused_port(protocol: :all)
22
22
  end
23
23
 
24
24
  def teardown
@@ -87,7 +87,7 @@ class TestFluentCat < ::Test::Unit::TestCase
87
87
  d = create_driver
88
88
  d.run(expect_records: 1) do
89
89
  Open3.pipeline_w("#{ServerEngine.ruby_bin_path} #{FLUENT_CAT_COMMAND} --port #{@port} --format msgpack secondary") do |stdin|
90
- stdin.write(File.read(path))
90
+ stdin.write(File.read(path, File.size(path)))
91
91
  stdin.close
92
92
  end
93
93
  end