fluentd 1.11.5-x64-mingw32 → 1.12.4-x64-mingw32

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/.deepsource.toml +13 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  4. data/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. data/.github/workflows/linux-test.yaml +36 -0
  6. data/.github/workflows/macos-test.yaml +30 -0
  7. data/.github/workflows/stale-actions.yml +22 -0
  8. data/.github/workflows/windows-test.yaml +35 -0
  9. data/.gitlab-ci.yml +41 -19
  10. data/CHANGELOG.md +158 -0
  11. data/MAINTAINERS.md +5 -2
  12. data/README.md +7 -4
  13. data/bin/fluent-cap-ctl +7 -0
  14. data/bin/fluent-ctl +7 -0
  15. data/fluentd.gemspec +6 -4
  16. data/lib/fluent/capability.rb +87 -0
  17. data/lib/fluent/command/bundler_injection.rb +1 -1
  18. data/lib/fluent/command/ca_generate.rb +6 -3
  19. data/lib/fluent/command/cap_ctl.rb +174 -0
  20. data/lib/fluent/command/cat.rb +0 -1
  21. data/lib/fluent/command/ctl.rb +177 -0
  22. data/lib/fluent/command/fluentd.rb +4 -0
  23. data/lib/fluent/command/plugin_config_formatter.rb +18 -2
  24. data/lib/fluent/command/plugin_generator.rb +31 -1
  25. data/lib/fluent/compat/parser.rb +2 -2
  26. data/lib/fluent/config/section.rb +2 -2
  27. data/lib/fluent/config/types.rb +2 -2
  28. data/lib/fluent/env.rb +4 -0
  29. data/lib/fluent/event.rb +3 -13
  30. data/lib/fluent/load.rb +0 -1
  31. data/lib/fluent/plugin.rb +5 -0
  32. data/lib/fluent/plugin/buffer.rb +2 -21
  33. data/lib/fluent/plugin/file_wrapper.rb +39 -3
  34. data/lib/fluent/plugin/formatter.rb +2 -2
  35. data/lib/fluent/plugin/formatter_csv.rb +1 -1
  36. data/lib/fluent/plugin/formatter_hash.rb +1 -1
  37. data/lib/fluent/plugin/formatter_ltsv.rb +5 -5
  38. data/lib/fluent/plugin/formatter_out_file.rb +3 -3
  39. data/lib/fluent/plugin/formatter_single_value.rb +2 -2
  40. data/lib/fluent/plugin/formatter_tsv.rb +2 -2
  41. data/lib/fluent/plugin/in_http.rb +24 -3
  42. data/lib/fluent/plugin/in_monitor_agent.rb +1 -1
  43. data/lib/fluent/plugin/in_tail.rb +129 -41
  44. data/lib/fluent/plugin/in_tail/position_file.rb +53 -14
  45. data/lib/fluent/plugin/in_tcp.rb +1 -0
  46. data/lib/fluent/plugin/out_copy.rb +18 -5
  47. data/lib/fluent/plugin/out_exec_filter.rb +3 -3
  48. data/lib/fluent/plugin/out_forward.rb +61 -28
  49. data/lib/fluent/plugin/out_http.rb +9 -2
  50. data/lib/fluent/plugin/output.rb +18 -10
  51. data/lib/fluent/plugin/parser_csv.rb +2 -2
  52. data/lib/fluent/plugin/parser_syslog.rb +2 -2
  53. data/lib/fluent/plugin/storage_local.rb +4 -4
  54. data/lib/fluent/plugin_helper/http_server/compat/server.rb +1 -1
  55. data/lib/fluent/plugin_helper/inject.rb +4 -2
  56. data/lib/fluent/plugin_helper/retry_state.rb +4 -0
  57. data/lib/fluent/plugin_helper/server.rb +4 -2
  58. data/lib/fluent/plugin_helper/socket_option.rb +2 -2
  59. data/lib/fluent/supervisor.rb +153 -47
  60. data/lib/fluent/system_config.rb +2 -1
  61. data/lib/fluent/time.rb +58 -1
  62. data/lib/fluent/version.rb +1 -1
  63. data/lib/fluent/winsvc.rb +22 -4
  64. data/templates/new_gem/fluent-plugin.gemspec.erb +3 -3
  65. data/templates/plugin_config_formatter/param.md-table.erb +10 -0
  66. data/test/command/test_cap_ctl.rb +100 -0
  67. data/test/command/test_ctl.rb +57 -0
  68. data/test/command/test_fluentd.rb +38 -0
  69. data/test/command/test_plugin_config_formatter.rb +124 -2
  70. data/test/config/test_configurable.rb +1 -1
  71. data/test/plugin/in_tail/test_position_file.rb +100 -26
  72. data/test/plugin/test_file_wrapper.rb +105 -0
  73. data/test/plugin/test_in_exec.rb +1 -1
  74. data/test/plugin/test_in_http.rb +25 -0
  75. data/test/plugin/test_in_tail.rb +503 -42
  76. data/test/plugin/test_out_copy.rb +87 -0
  77. data/test/plugin/test_out_forward.rb +94 -6
  78. data/test/plugin/test_out_http.rb +20 -1
  79. data/test/plugin/test_output.rb +15 -3
  80. data/test/plugin/test_output_as_buffered_backup.rb +2 -0
  81. data/test/plugin/test_parser_csv.rb +14 -0
  82. data/test/plugin/test_parser_syslog.rb +16 -2
  83. data/test/plugin/test_sd_file.rb +1 -1
  84. data/test/plugin_helper/service_discovery/test_manager.rb +1 -1
  85. data/test/plugin_helper/test_child_process.rb +5 -2
  86. data/test/plugin_helper/test_http_server_helper.rb +4 -2
  87. data/test/plugin_helper/test_inject.rb +29 -0
  88. data/test/plugin_helper/test_server.rb +26 -7
  89. data/test/test_capability.rb +74 -0
  90. data/test/test_event.rb +16 -0
  91. data/test/test_formatter.rb +30 -0
  92. data/test/test_output.rb +2 -2
  93. data/test/test_supervisor.rb +133 -10
  94. data/test/test_time_parser.rb +109 -0
  95. metadata +85 -31
  96. data/.travis.yml +0 -57
  97. data/appveyor.yml +0 -28
@@ -338,7 +338,7 @@ module Fluent::Plugin
338
338
  obj.merge!(pe.statistics['output'] || {})
339
339
  end
340
340
 
341
- obj['retry'] = get_retry_info(pe.retry) if opts[:with_retry] and pe.instance_variable_defined?(:@retry)
341
+ obj['retry'] = get_retry_info(pe.retry) if opts[:with_retry] && pe.instance_variable_defined?(:@retry)
342
342
 
343
343
  # include all instance variables if :with_debug_info is set
344
344
  if opts[:with_debug_info]
@@ -22,6 +22,7 @@ require 'fluent/event'
22
22
  require 'fluent/plugin/buffer'
23
23
  require 'fluent/plugin/parser_multiline'
24
24
  require 'fluent/variable_store'
25
+ require 'fluent/capability'
25
26
  require 'fluent/plugin/in_tail/position_file'
26
27
 
27
28
  if Fluent.windows?
@@ -104,6 +105,8 @@ module Fluent::Plugin
104
105
  config_param :ignore_repeated_permission_error, :bool, default: false
105
106
  desc 'Format path with the specified timezone'
106
107
  config_param :path_timezone, :string, default: nil
108
+ desc 'Follow inodes instead of following file names. Guarantees more stable delivery and allows to use * in path pattern with rotating files'
109
+ config_param :follow_inodes, :bool, default: false
107
110
 
108
111
  config_section :parse, required: false, multi: true, init: true, param_name: :parser_configs do
109
112
  config_argument :usage, :string, default: 'in_tail_parser'
@@ -154,6 +157,9 @@ module Fluent::Plugin
154
157
  end
155
158
  @variable_store[@pos_file] = self.plugin_id
156
159
  else
160
+ if @follow_inodes
161
+ raise Fluent::ConfigError, "Can't follow inodes without pos_file configuration parameter"
162
+ end
157
163
  $log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
158
164
  $log.warn "this parameter is highly recommended to save the position to resume tailing."
159
165
  end
@@ -171,6 +177,7 @@ module Fluent::Plugin
171
177
  @dir_perm = system_config.dir_permission || Fluent::DEFAULT_DIR_PERMISSION
172
178
  # parser is already created by parser helper
173
179
  @parser = parser_create(usage: parser_config['usage'] || @parser_configs.first.usage)
180
+ @capability = Fluent::Capability.new(:current_process)
174
181
  end
175
182
 
176
183
  def configure_tag
@@ -214,7 +221,7 @@ module Fluent::Plugin
214
221
  FileUtils.mkdir_p(pos_file_dir, mode: @dir_perm) unless Dir.exist?(pos_file_dir)
215
222
  @pf_file = File.open(@pos_file, File::RDWR|File::CREAT|File::BINARY, @file_perm)
216
223
  @pf_file.sync = true
217
- @pf = PositionFile.load(@pf_file, logger: log)
224
+ @pf = PositionFile.load(@pf_file, @follow_inodes, expand_paths, logger: log)
218
225
 
219
226
  if @pos_file_compaction_interval
220
227
  timer_execute(:in_tail_refresh_compact_pos_file, @pos_file_compaction_interval) do
@@ -238,7 +245,7 @@ module Fluent::Plugin
238
245
 
239
246
  def shutdown
240
247
  # during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
241
- stop_watchers(@tails.keys, immediate: true, remove_watcher: false)
248
+ stop_watchers(existence_path, immediate: true, remove_watcher: false)
242
249
  @pf_file.close if @pf_file
243
250
 
244
251
  super
@@ -250,6 +257,11 @@ module Fluent::Plugin
250
257
  close_watcher_handles
251
258
  end
252
259
 
260
+ def have_read_capability?
261
+ @capability.have_capability?(:effective, :dac_read_search) ||
262
+ @capability.have_capability?(:effective, :dac_override)
263
+ end
264
+
253
265
  def expand_paths
254
266
  date = Fluent::EventTime.now
255
267
  paths = []
@@ -263,7 +275,7 @@ module Fluent::Plugin
263
275
  paths += Dir.glob(path).select { |p|
264
276
  begin
265
277
  is_file = !File.directory?(p)
266
- if File.readable?(p) && is_file
278
+ if (File.readable?(p) || have_read_capability?) && is_file
267
279
  if @limit_recently_modified && File.mtime(p) < (date.to_time - @limit_recently_modified)
268
280
  false
269
281
  else
@@ -296,7 +308,37 @@ module Fluent::Plugin
296
308
  end
297
309
  path.include?('*') ? Dir.glob(path) : path
298
310
  }.flatten.uniq
299
- paths.uniq - excluded
311
+ # filter out non existing files, so in case pattern is without '*' we don't do unnecessary work
312
+ hash = {}
313
+ (paths - excluded).select { |path|
314
+ FileTest.exist?(path)
315
+ }.each { |path|
316
+ # Even we just checked for existence, there is a race condition here as
317
+ # of which stat() might fail with ENOENT. See #3224.
318
+ begin
319
+ target_info = TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
320
+ if @follow_inodes
321
+ hash[target_info.ino] = target_info
322
+ else
323
+ hash[target_info.path] = target_info
324
+ end
325
+ rescue Errno::ENOENT
326
+ $log.warn "expand_paths: stat() for #{path} failed with ENOENT. Skip file."
327
+ end
328
+ }
329
+ hash
330
+ end
331
+
332
+ def existence_path
333
+ hash = {}
334
+ @tails.each_key {|target_info|
335
+ if @follow_inodes
336
+ hash[target_info.ino] = target_info
337
+ else
338
+ hash[target_info.path] = target_info
339
+ end
340
+ }
341
+ hash
300
342
  end
301
343
 
302
344
  # in_tail with '*' path doesn't check rotation file equality at refresh phase.
@@ -305,21 +347,21 @@ module Fluent::Plugin
305
347
  # In such case, you should separate log directory and specify two paths in path parameter.
306
348
  # e.g. path /path/to/dir/*,/path/to/rotated_logs/target_file
307
349
  def refresh_watchers
308
- target_paths = expand_paths
309
- existence_paths = @tails.keys
350
+ target_paths_hash = expand_paths
351
+ existence_paths_hash = existence_path
310
352
 
311
353
  log.debug { "tailing paths: target = #{target_paths.join(",")} | existing = #{existence_paths.join(",")}" }
312
354
 
313
- unwatched = existence_paths - target_paths
314
- added = target_paths - existence_paths
355
+ unwatched_hash = existence_paths_hash.reject {|key, value| target_paths_hash.key?(key)}
356
+ added_hash = target_paths_hash.reject {|key, value| existence_paths_hash.key?(key)}
315
357
 
316
- stop_watchers(unwatched, immediate: false, unwatched: true) unless unwatched.empty?
317
- start_watchers(added) unless added.empty?
358
+ stop_watchers(unwatched_hash, immediate: false, unwatched: true) unless unwatched_hash.empty?
359
+ start_watchers(added_hash) unless added_hash.empty?
318
360
  end
319
361
 
320
- def setup_watcher(path, pe)
362
+ def setup_watcher(target_info, pe)
321
363
  line_buffer_timer_flusher = @multiline_mode ? TailWatcher::LineBufferTimerFlusher.new(log, @multiline_flush_interval, &method(:flush_buffer)) : nil
322
- tw = TailWatcher.new(path, pe, log, @read_from_head, method(:update_watcher), line_buffer_timer_flusher, method(:io_handler))
364
+ tw = TailWatcher.new(target_info, pe, log, @read_from_head, @follow_inodes, method(:update_watcher), line_buffer_timer_flusher, method(:io_handler))
323
365
 
324
366
  if @enable_watch_timer
325
367
  tt = TimerTrigger.new(1, log) { tw.on_notify }
@@ -350,47 +392,60 @@ module Fluent::Plugin
350
392
  raise e
351
393
  end
352
394
 
353
- def start_watchers(paths)
354
- paths.each { |path|
395
+ def start_watchers(targets_info)
396
+ targets_info.each_value { |target_info|
355
397
  pe = nil
356
398
  if @pf
357
- pe = @pf[path]
399
+ pe = @pf[target_info]
358
400
  if @read_from_head && pe.read_inode.zero?
359
401
  begin
360
- pe.update(Fluent::FileWrapper.stat(path).ino, 0)
402
+ pe.update(Fluent::FileWrapper.stat(target_info.path).ino, 0)
361
403
  rescue Errno::ENOENT
362
- $log.warn "#{path} not found. Continuing without tailing it."
404
+ $log.warn "#{target_info.path} not found. Continuing without tailing it."
363
405
  end
364
406
  end
365
407
  end
366
408
 
367
409
  begin
368
- tw = setup_watcher(path, pe)
410
+ tw = setup_watcher(target_info, pe)
369
411
  rescue WatcherSetupError => e
370
- log.warn "Skip #{path} because unexpected setup error happens: #{e}"
412
+ log.warn "Skip #{target_info.path} because unexpected setup error happens: #{e}"
371
413
  next
372
414
  end
373
- @tails[path] = tw
415
+
416
+ begin
417
+ target_info = TargetInfo.new(target_info.path, Fluent::FileWrapper.stat(target_info.path).ino)
418
+ @tails[target_info] = tw
419
+ rescue Errno::ENOENT
420
+ $log.warn "stat() for #{target_info.path} failed with ENOENT. Drop tail watcher for now."
421
+ # explicitly detach and unwatch watcher `tw`.
422
+ tw.unwatched = true
423
+ detach_watcher(tw, target_info.ino, false)
424
+ end
374
425
  }
375
426
  end
376
427
 
377
- def stop_watchers(paths, immediate: false, unwatched: false, remove_watcher: true)
378
- paths.each { |path|
379
- tw = remove_watcher ? @tails.delete(path) : @tails[path]
428
+ def stop_watchers(targets_info, immediate: false, unwatched: false, remove_watcher: true)
429
+ targets_info.each_value { |target_info|
430
+ if remove_watcher
431
+ tw = @tails.delete(target_info)
432
+ else
433
+ tw = @tails[target_info]
434
+ end
380
435
  if tw
381
436
  tw.unwatched = unwatched
382
437
  if immediate
383
- detach_watcher(tw, false)
438
+ detach_watcher(tw, target_info.ino, false)
384
439
  else
385
- detach_watcher_after_rotate_wait(tw)
440
+ detach_watcher_after_rotate_wait(tw, target_info.ino)
386
441
  end
387
442
  end
388
443
  }
389
444
  end
390
445
 
391
446
  def close_watcher_handles
392
- @tails.keys.each do |path|
393
- tw = @tails.delete(path)
447
+ @tails.keys.each do |target_info|
448
+ tw = @tails.delete(target_info)
394
449
  if tw
395
450
  tw.close
396
451
  end
@@ -398,25 +453,39 @@ module Fluent::Plugin
398
453
  end
399
454
 
400
455
  # refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
401
- def update_watcher(path, pe)
402
- log.info("detected rotation of #{path}; waiting #{@rotate_wait} seconds")
456
+ def update_watcher(target_info, pe)
457
+ log.info("detected rotation of #{target_info.path}; waiting #{@rotate_wait} seconds")
403
458
 
404
459
  if @pf
405
- unless pe.read_inode == @pf[path].read_inode
460
+ pe_inode = pe.read_inode
461
+ target_info_from_position_entry = TargetInfo.new(target_info.path, pe_inode)
462
+ unless pe_inode == @pf[target_info_from_position_entry].read_inode
406
463
  log.debug "Skip update_watcher because watcher has been already updated by other inotify event"
407
464
  return
408
465
  end
409
466
  end
410
- rotated_tw = @tails[path]
411
- @tails[path] = setup_watcher(path, pe)
412
- detach_watcher_after_rotate_wait(rotated_tw) if rotated_tw
467
+
468
+ rotated_target_info = TargetInfo.new(target_info.path, pe.read_inode)
469
+ rotated_tw = @tails[rotated_target_info]
470
+ new_target_info = target_info.dup
471
+
472
+ if @follow_inodes
473
+ new_position_entry = @pf[target_info]
474
+
475
+ if new_position_entry.read_inode == 0
476
+ @tails[new_target_info] = setup_watcher(new_target_info, new_position_entry)
477
+ end
478
+ else
479
+ @tails[new_target_info] = setup_watcher(new_target_info, pe)
480
+ end
481
+ detach_watcher_after_rotate_wait(rotated_tw, pe.read_inode) if rotated_tw
413
482
  end
414
483
 
415
484
  # TailWatcher#close is called by another thread at shutdown phase.
416
485
  # It causes 'can't modify string; temporarily locked' error in IOHandler
417
486
  # so adding close_io argument to avoid this problem.
418
487
  # At shutdown, IOHandler's io will be released automatically after detached the event loop
419
- def detach_watcher(tw, close_io = true)
488
+ def detach_watcher(tw, ino, close_io = true)
420
489
  tw.watchers.each do |watcher|
421
490
  event_loop_detach(watcher)
422
491
  end
@@ -425,15 +494,16 @@ module Fluent::Plugin
425
494
  tw.close if close_io
426
495
 
427
496
  if tw.unwatched && @pf
428
- @pf.unwatch(tw.path)
497
+ target_info = TargetInfo.new(tw.path, ino)
498
+ @pf.unwatch(target_info)
429
499
  end
430
500
  end
431
501
 
432
- def detach_watcher_after_rotate_wait(tw)
502
+ def detach_watcher_after_rotate_wait(tw, ino)
433
503
  # Call event_loop_attach/event_loop_detach is high-cost for short-live object.
434
504
  # If this has a problem with large number of files, use @_event_loop directly instead of timer_execute.
435
505
  timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
436
- detach_watcher(tw)
506
+ detach_watcher(tw, ino)
437
507
  end
438
508
  end
439
509
 
@@ -601,10 +671,12 @@ module Fluent::Plugin
601
671
  end
602
672
 
603
673
  class TailWatcher
604
- def initialize(path, pe, log, read_from_head, update_watcher, line_buffer_timer_flusher, io_handler_build)
605
- @path = path
674
+ def initialize(target_info, pe, log, read_from_head, follow_inodes, update_watcher, line_buffer_timer_flusher, io_handler_build)
675
+ @path = target_info.path
676
+ @ino = target_info.ino
606
677
  @pe = pe || MemoryPositionEntry.new
607
678
  @read_from_head = read_from_head
679
+ @follow_inodes = follow_inodes
608
680
  @update_watcher = update_watcher
609
681
  @log = log
610
682
  @rotate_handler = RotateHandler.new(log, &method(:on_rotate))
@@ -614,7 +686,7 @@ module Fluent::Plugin
614
686
  @watchers = []
615
687
  end
616
688
 
617
- attr_reader :path
689
+ attr_reader :path, :ino
618
690
  attr_reader :pe
619
691
  attr_reader :line_buffer_timer_flusher
620
692
  attr_accessor :unwatched # This is used for removing position entry from PositionFile
@@ -709,7 +781,23 @@ module Fluent::Plugin
709
781
  end
710
782
 
711
783
  if watcher_needs_update
712
- @update_watcher.call(@path, swap_state(@pe))
784
+ if @follow_inodes
785
+ # No need to update a watcher if stat is nil (file not present), because moving to inodes will create
786
+ # new watcher, and old watcher will be closed by stop_watcher in refresh_watchers method
787
+ # don't want to swap state because we need latest read offset in pos file even after rotate_wait
788
+ if stat
789
+ target_info = TargetInfo.new(@path, stat)
790
+ @update_watcher.call(target_info, @pe)
791
+ end
792
+ else
793
+ # Permit to handle if stat is nil (file not present).
794
+ # If a file is mv-ed and a new file is created during
795
+ # calling `#refresh_watchers`s, and `#refresh_watchers` won't run `#start_watchers`
796
+ # and `#stop_watchers()` for the path because `target_paths_hash`
797
+ # always contains the path.
798
+ target_info = TargetInfo.new(@path, stat ? stat.ino : nil)
799
+ @update_watcher.call(target_info, swap_state(@pe))
800
+ end
713
801
  else
714
802
  @log.info "detected rotation of #{@path}"
715
803
  @io_handler = io_handler
@@ -22,34 +22,40 @@ module Fluent::Plugin
22
22
  UNWATCHED_POSITION = 0xffffffffffffffff
23
23
  POSITION_FILE_ENTRY_REGEX = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.freeze
24
24
 
25
- def self.load(file, logger:)
26
- pf = new(file, logger: logger)
25
+ def self.load(file, follow_inodes, existing_paths, logger:)
26
+ pf = new(file, follow_inodes, existing_paths, logger: logger)
27
27
  pf.load
28
28
  pf
29
29
  end
30
30
 
31
- def initialize(file, logger: nil)
31
+ def initialize(file, follow_inodes, existing_paths, logger: nil)
32
32
  @file = file
33
33
  @logger = logger
34
34
  @file_mutex = Mutex.new
35
35
  @map = {}
36
+ @follow_inodes = follow_inodes
37
+ @existing_paths = existing_paths
36
38
  end
37
39
 
38
- def [](path)
39
- if m = @map[path]
40
+ def [](target_info)
41
+ if m = @map[@follow_inodes ? target_info.ino : target_info.path]
40
42
  return m
41
43
  end
42
44
 
43
45
  @file_mutex.synchronize {
44
46
  @file.seek(0, IO::SEEK_END)
45
- seek = @file.pos + path.bytesize + 1
46
- @file.write "#{path}\t0000000000000000\t0000000000000000\n"
47
- @map[path] = FilePositionEntry.new(@file, @file_mutex, seek, 0, 0)
47
+ seek = @file.pos + target_info.path.bytesize + 1
48
+ @file.write "#{target_info.path}\t0000000000000000\t0000000000000000\n"
49
+ if @follow_inodes
50
+ @map[target_info.ino] = FilePositionEntry.new(@file, @file_mutex, seek, 0, 0)
51
+ else
52
+ @map[target_info.path] = FilePositionEntry.new(@file, @file_mutex, seek, 0, 0)
53
+ end
48
54
  }
49
55
  end
50
56
 
51
- def unwatch(path)
52
- if (entry = @map.delete(path))
57
+ def unwatch(target_info)
58
+ if (entry = @map.delete(@follow_inodes ? target_info.ino : target_info.path))
53
59
  entry.update_pos(UNWATCHED_POSITION)
54
60
  end
55
61
  end
@@ -69,7 +75,11 @@ module Fluent::Plugin
69
75
  pos = m[2].to_i(16)
70
76
  ino = m[3].to_i(16)
71
77
  seek = @file.pos - line.bytesize + path.bytesize + 1
72
- map[path] = FilePositionEntry.new(@file, @file_mutex, seek, pos, ino)
78
+ if @follow_inodes
79
+ map[ino] = FilePositionEntry.new(@file, @file_mutex, seek, pos, ino)
80
+ else
81
+ map[path] = FilePositionEntry.new(@file, @file_mutex, seek, pos, ino)
82
+ end
73
83
  end
74
84
  end
75
85
 
@@ -94,8 +104,9 @@ module Fluent::Plugin
94
104
  @file.truncate(0)
95
105
  @file.write(entries.values.map(&:to_entry_fmt).join)
96
106
 
97
- entries.each do |path, val|
98
- if (m = @map[path])
107
+ # entry contains path/ino key and value.
108
+ entries.each do |key, val|
109
+ if (m = @map[key])
99
110
  m.seek = val.seek
100
111
  end
101
112
  end
@@ -139,13 +150,25 @@ module Fluent::Plugin
139
150
  @logger.warn("#{path} already exists. use latest one: deleted #{entries[path]}") if @logger
140
151
  end
141
152
 
142
- entries[path] = Entry.new(path, pos, ino, file_pos + path.size + 1)
153
+ if @follow_inodes
154
+ entries[ino] = Entry.new(path, pos, ino, file_pos + path.size + 1)
155
+ else
156
+ entries[path] = Entry.new(path, pos, ino, file_pos + path.size + 1)
157
+ end
143
158
  file_pos += line.size
144
159
  end
145
160
  end
146
161
 
162
+ entries = remove_deleted_files_entries(entries, @existing_paths) if @follow_inodes
147
163
  entries
148
164
  end
165
+
166
+ def remove_deleted_files_entries(existent_entries, existing_paths)
167
+ filtered_entries = existent_entries.select {|file_entry|
168
+ existing_paths.key?(file_entry)
169
+ }
170
+ filtered_entries
171
+ end
149
172
  end
150
173
 
151
174
  Entry = Struct.new(:path, :pos, :ino, :seek) do
@@ -224,5 +247,21 @@ module Fluent::Plugin
224
247
  @inode
225
248
  end
226
249
  end
250
+
251
+ TargetInfo = Struct.new(:path, :ino) do
252
+ def ==(other)
253
+ return false unless other.is_a?(TargetInfo)
254
+ self.path == other.path
255
+ end
256
+
257
+ def hash
258
+ self.path.hash
259
+ end
260
+
261
+ def eql?(other)
262
+ return false unless other.is_a?(TargetInfo)
263
+ self.path == other.path
264
+ end
265
+ end
227
266
  end
228
267
  end