fluentd 1.11.3 → 1.12.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.
Potentially problematic release.
This version of fluentd might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- data/.github/ISSUE_TEMPLATE/config.yml +5 -0
- data/.github/workflows/stale-actions.yml +22 -0
- data/.travis.yml +22 -2
- data/CHANGELOG.md +66 -0
- data/README.md +1 -1
- data/appveyor.yml +3 -0
- data/bin/fluent-cap-ctl +7 -0
- data/bin/fluent-ctl +7 -0
- data/fluentd.gemspec +2 -1
- data/lib/fluent/capability.rb +87 -0
- data/lib/fluent/command/cap_ctl.rb +174 -0
- data/lib/fluent/command/ctl.rb +177 -0
- data/lib/fluent/command/plugin_config_formatter.rb +2 -1
- data/lib/fluent/env.rb +4 -0
- data/lib/fluent/plugin.rb +5 -0
- data/lib/fluent/plugin/buffer.rb +2 -21
- data/lib/fluent/plugin/formatter.rb +24 -0
- data/lib/fluent/plugin/formatter_csv.rb +1 -1
- data/lib/fluent/plugin/formatter_hash.rb +3 -1
- data/lib/fluent/plugin/formatter_json.rb +3 -1
- data/lib/fluent/plugin/formatter_ltsv.rb +5 -3
- data/lib/fluent/plugin/formatter_out_file.rb +6 -4
- data/lib/fluent/plugin/formatter_single_value.rb +4 -2
- data/lib/fluent/plugin/formatter_tsv.rb +4 -2
- data/lib/fluent/plugin/in_http.rb +23 -2
- data/lib/fluent/plugin/in_tail.rb +109 -41
- data/lib/fluent/plugin/in_tail/position_file.rb +39 -14
- data/lib/fluent/plugin/in_tcp.rb +1 -0
- data/lib/fluent/plugin/out_http.rb +20 -2
- data/lib/fluent/plugin/output.rb +14 -6
- data/lib/fluent/plugin_helper/http_server/compat/server.rb +1 -1
- data/lib/fluent/plugin_helper/inject.rb +4 -1
- data/lib/fluent/plugin_helper/retry_state.rb +4 -0
- data/lib/fluent/supervisor.rb +140 -43
- data/lib/fluent/time.rb +1 -0
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/winsvc.rb +22 -4
- data/test/command/test_binlog_reader.rb +22 -6
- data/test/command/test_cap_ctl.rb +100 -0
- data/test/command/test_ctl.rb +57 -0
- data/test/command/test_plugin_config_formatter.rb +57 -2
- data/test/plugin/in_tail/test_position_file.rb +45 -25
- data/test/plugin/test_filter_stdout.rb +6 -1
- data/test/plugin/test_formatter_hash.rb +6 -3
- data/test/plugin/test_formatter_json.rb +14 -4
- data/test/plugin/test_formatter_ltsv.rb +13 -5
- data/test/plugin/test_formatter_out_file.rb +35 -14
- data/test/plugin/test_formatter_single_value.rb +12 -6
- data/test/plugin/test_formatter_tsv.rb +12 -4
- data/test/plugin/test_in_http.rb +25 -0
- data/test/plugin/test_in_tail.rb +430 -30
- data/test/plugin/test_out_file.rb +23 -18
- data/test/plugin/test_output.rb +12 -0
- data/test/plugin/test_parser_syslog.rb +2 -2
- data/test/plugin_helper/test_compat_parameters.rb +7 -2
- data/test/plugin_helper/test_inject.rb +42 -0
- data/test/test_capability.rb +74 -0
- data/test/test_formatter.rb +34 -10
- data/test/test_output.rb +6 -1
- data/test/test_supervisor.rb +119 -1
- metadata +33 -4
| @@ -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( | 
| 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,31 @@ module Fluent::Plugin | |
| 296 308 | 
             
                           end
         | 
| 297 309 | 
             
                    path.include?('*') ? Dir.glob(path) : path
         | 
| 298 310 | 
             
                  }.flatten.uniq
         | 
| 299 | 
            -
                   | 
| 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 | 
            +
                    target_info = TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
         | 
| 317 | 
            +
                    if @follow_inodes
         | 
| 318 | 
            +
                      hash[target_info.ino] = target_info
         | 
| 319 | 
            +
                    else
         | 
| 320 | 
            +
                      hash[target_info.path] = target_info
         | 
| 321 | 
            +
                    end
         | 
| 322 | 
            +
                  }
         | 
| 323 | 
            +
                  hash
         | 
| 324 | 
            +
                end
         | 
| 325 | 
            +
             | 
| 326 | 
            +
                def existence_path
         | 
| 327 | 
            +
                  hash = {}
         | 
| 328 | 
            +
                  @tails.each_key {|target_info|
         | 
| 329 | 
            +
                    if @follow_inodes
         | 
| 330 | 
            +
                      hash[target_info.ino] = target_info
         | 
| 331 | 
            +
                    else
         | 
| 332 | 
            +
                      hash[target_info.path] = target_info
         | 
| 333 | 
            +
                    end
         | 
| 334 | 
            +
                  }
         | 
| 335 | 
            +
                  hash
         | 
| 300 336 | 
             
                end
         | 
| 301 337 |  | 
| 302 338 | 
             
                # in_tail with '*' path doesn't check rotation file equality at refresh phase.
         | 
| @@ -305,21 +341,21 @@ module Fluent::Plugin | |
| 305 341 | 
             
                # In such case, you should separate log directory and specify two paths in path parameter.
         | 
| 306 342 | 
             
                # e.g. path /path/to/dir/*,/path/to/rotated_logs/target_file
         | 
| 307 343 | 
             
                def refresh_watchers
         | 
| 308 | 
            -
                   | 
| 309 | 
            -
                   | 
| 344 | 
            +
                  target_paths_hash = expand_paths
         | 
| 345 | 
            +
                  existence_paths_hash = existence_path
         | 
| 310 346 |  | 
| 311 347 | 
             
                  log.debug { "tailing paths: target = #{target_paths.join(",")} | existing = #{existence_paths.join(",")}" }
         | 
| 312 348 |  | 
| 313 | 
            -
                   | 
| 314 | 
            -
                   | 
| 349 | 
            +
                  unwatched_hash = existence_paths_hash.reject {|key, value| target_paths_hash.key?(key)}
         | 
| 350 | 
            +
                  added_hash = target_paths_hash.reject {|key, value| existence_paths_hash.key?(key)}
         | 
| 315 351 |  | 
| 316 | 
            -
                  stop_watchers( | 
| 317 | 
            -
                  start_watchers( | 
| 352 | 
            +
                  stop_watchers(unwatched_hash, immediate: false, unwatched: true) unless unwatched_hash.empty?
         | 
| 353 | 
            +
                  start_watchers(added_hash) unless added_hash.empty?
         | 
| 318 354 | 
             
                end
         | 
| 319 355 |  | 
| 320 | 
            -
                def setup_watcher( | 
| 356 | 
            +
                def setup_watcher(target_info, pe)
         | 
| 321 357 | 
             
                  line_buffer_timer_flusher = @multiline_mode ? TailWatcher::LineBufferTimerFlusher.new(log, @multiline_flush_interval, &method(:flush_buffer)) : nil
         | 
| 322 | 
            -
                  tw = TailWatcher.new( | 
| 358 | 
            +
                  tw = TailWatcher.new(target_info, pe, log, @read_from_head, @follow_inodes, method(:update_watcher), line_buffer_timer_flusher, method(:io_handler))
         | 
| 323 359 |  | 
| 324 360 | 
             
                  if @enable_watch_timer
         | 
| 325 361 | 
             
                    tt = TimerTrigger.new(1, log) { tw.on_notify }
         | 
| @@ -350,47 +386,52 @@ module Fluent::Plugin | |
| 350 386 | 
             
                  raise e
         | 
| 351 387 | 
             
                end
         | 
| 352 388 |  | 
| 353 | 
            -
                def start_watchers( | 
| 354 | 
            -
                   | 
| 389 | 
            +
                def start_watchers(targets_info)
         | 
| 390 | 
            +
                  targets_info.each_value { |target_info|
         | 
| 355 391 | 
             
                    pe = nil
         | 
| 356 392 | 
             
                    if @pf
         | 
| 357 | 
            -
                      pe = @pf[ | 
| 393 | 
            +
                      pe = @pf[target_info]
         | 
| 358 394 | 
             
                      if @read_from_head && pe.read_inode.zero?
         | 
| 359 395 | 
             
                        begin
         | 
| 360 | 
            -
                          pe.update(Fluent::FileWrapper.stat(path).ino, 0)
         | 
| 396 | 
            +
                          pe.update(Fluent::FileWrapper.stat(target_info.path).ino, 0)
         | 
| 361 397 | 
             
                        rescue Errno::ENOENT
         | 
| 362 | 
            -
                          $log.warn "#{path} not found. Continuing without tailing it."
         | 
| 398 | 
            +
                          $log.warn "#{target_info.path} not found. Continuing without tailing it."
         | 
| 363 399 | 
             
                        end
         | 
| 364 400 | 
             
                      end
         | 
| 365 401 | 
             
                    end
         | 
| 366 402 |  | 
| 367 403 | 
             
                    begin
         | 
| 368 | 
            -
                      tw = setup_watcher( | 
| 404 | 
            +
                      tw = setup_watcher(target_info, pe)
         | 
| 369 405 | 
             
                    rescue WatcherSetupError => e
         | 
| 370 | 
            -
                      log.warn "Skip #{path} because unexpected setup error happens: #{e}"
         | 
| 406 | 
            +
                      log.warn "Skip #{target_info.path} because unexpected setup error happens: #{e}"
         | 
| 371 407 | 
             
                      next
         | 
| 372 408 | 
             
                    end
         | 
| 373 | 
            -
                     | 
| 409 | 
            +
                    target_info = TargetInfo.new(target_info.path, Fluent::FileWrapper.stat(target_info.path).ino)
         | 
| 410 | 
            +
                    @tails[target_info] = tw
         | 
| 374 411 | 
             
                  }
         | 
| 375 412 | 
             
                end
         | 
| 376 413 |  | 
| 377 | 
            -
                def stop_watchers( | 
| 378 | 
            -
                   | 
| 379 | 
            -
                     | 
| 414 | 
            +
                def stop_watchers(targets_info, immediate: false, unwatched: false, remove_watcher: true)
         | 
| 415 | 
            +
                  targets_info.each_value { |target_info|
         | 
| 416 | 
            +
                    if remove_watcher
         | 
| 417 | 
            +
                      tw = @tails.delete(target_info)
         | 
| 418 | 
            +
                    else
         | 
| 419 | 
            +
                      tw = @tails[target_info]
         | 
| 420 | 
            +
                    end
         | 
| 380 421 | 
             
                    if tw
         | 
| 381 422 | 
             
                      tw.unwatched = unwatched
         | 
| 382 423 | 
             
                      if immediate
         | 
| 383 | 
            -
                        detach_watcher(tw, false)
         | 
| 424 | 
            +
                        detach_watcher(tw, target_info.ino, false)
         | 
| 384 425 | 
             
                      else
         | 
| 385 | 
            -
                        detach_watcher_after_rotate_wait(tw)
         | 
| 426 | 
            +
                        detach_watcher_after_rotate_wait(tw, target_info.ino)
         | 
| 386 427 | 
             
                      end
         | 
| 387 428 | 
             
                    end
         | 
| 388 429 | 
             
                  }
         | 
| 389 430 | 
             
                end
         | 
| 390 431 |  | 
| 391 432 | 
             
                def close_watcher_handles
         | 
| 392 | 
            -
                  @tails.keys.each do | | 
| 393 | 
            -
                    tw = @tails.delete( | 
| 433 | 
            +
                  @tails.keys.each do |target_info|
         | 
| 434 | 
            +
                    tw = @tails.delete(target_info)
         | 
| 394 435 | 
             
                    if tw
         | 
| 395 436 | 
             
                      tw.close
         | 
| 396 437 | 
             
                    end
         | 
| @@ -398,25 +439,39 @@ module Fluent::Plugin | |
| 398 439 | 
             
                end
         | 
| 399 440 |  | 
| 400 441 | 
             
                # refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
         | 
| 401 | 
            -
                def update_watcher( | 
| 402 | 
            -
                  log.info("detected rotation of #{path}; waiting #{@rotate_wait} seconds")
         | 
| 442 | 
            +
                def update_watcher(target_info, pe)
         | 
| 443 | 
            +
                  log.info("detected rotation of #{target_info.path}; waiting #{@rotate_wait} seconds")
         | 
| 403 444 |  | 
| 404 445 | 
             
                  if @pf
         | 
| 405 | 
            -
                     | 
| 446 | 
            +
                    pe_inode = pe.read_inode
         | 
| 447 | 
            +
                    target_info_from_position_entry = TargetInfo.new(target_info.path, pe_inode)
         | 
| 448 | 
            +
                    unless pe_inode == @pf[target_info_from_position_entry].read_inode
         | 
| 406 449 | 
             
                      log.debug "Skip update_watcher because watcher has been already updated by other inotify event"
         | 
| 407 450 | 
             
                      return
         | 
| 408 451 | 
             
                    end
         | 
| 409 452 | 
             
                  end
         | 
| 410 | 
            -
             | 
| 411 | 
            -
                   | 
| 412 | 
            -
                   | 
| 453 | 
            +
             | 
| 454 | 
            +
                  rotated_target_info = TargetInfo.new(target_info.path, pe.read_inode)
         | 
| 455 | 
            +
                  rotated_tw = @tails[rotated_target_info]
         | 
| 456 | 
            +
                  new_target_info = target_info.dup
         | 
| 457 | 
            +
             | 
| 458 | 
            +
                  if @follow_inodes
         | 
| 459 | 
            +
                    new_position_entry = @pf[target_info]
         | 
| 460 | 
            +
             | 
| 461 | 
            +
                    if new_position_entry.read_inode == 0
         | 
| 462 | 
            +
                      @tails[new_target_info] = setup_watcher(new_target_info, new_position_entry)
         | 
| 463 | 
            +
                    end
         | 
| 464 | 
            +
                  else
         | 
| 465 | 
            +
                    @tails[new_target_info] = setup_watcher(new_target_info, pe)
         | 
| 466 | 
            +
                  end
         | 
| 467 | 
            +
                  detach_watcher_after_rotate_wait(rotated_tw, pe.read_inode) if rotated_tw
         | 
| 413 468 | 
             
                end
         | 
| 414 469 |  | 
| 415 470 | 
             
                # TailWatcher#close is called by another thread at shutdown phase.
         | 
| 416 471 | 
             
                # It causes 'can't modify string; temporarily locked' error in IOHandler
         | 
| 417 472 | 
             
                # so adding close_io argument to avoid this problem.
         | 
| 418 473 | 
             
                # At shutdown, IOHandler's io will be released automatically after detached the event loop
         | 
| 419 | 
            -
                def detach_watcher(tw, close_io = true)
         | 
| 474 | 
            +
                def detach_watcher(tw, ino, close_io = true)
         | 
| 420 475 | 
             
                  tw.watchers.each do |watcher|
         | 
| 421 476 | 
             
                    event_loop_detach(watcher)
         | 
| 422 477 | 
             
                  end
         | 
| @@ -425,15 +480,16 @@ module Fluent::Plugin | |
| 425 480 | 
             
                  tw.close if close_io
         | 
| 426 481 |  | 
| 427 482 | 
             
                  if tw.unwatched && @pf
         | 
| 428 | 
            -
                     | 
| 483 | 
            +
                    target_info = TargetInfo.new(tw.path, ino)
         | 
| 484 | 
            +
                    @pf.unwatch(target_info)
         | 
| 429 485 | 
             
                  end
         | 
| 430 486 | 
             
                end
         | 
| 431 487 |  | 
| 432 | 
            -
                def detach_watcher_after_rotate_wait(tw)
         | 
| 488 | 
            +
                def detach_watcher_after_rotate_wait(tw, ino)
         | 
| 433 489 | 
             
                  # Call event_loop_attach/event_loop_detach is high-cost for short-live object.
         | 
| 434 490 | 
             
                  # If this has a problem with large number of files, use @_event_loop directly instead of timer_execute.
         | 
| 435 491 | 
             
                  timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
         | 
| 436 | 
            -
                    detach_watcher(tw)
         | 
| 492 | 
            +
                    detach_watcher(tw, ino)
         | 
| 437 493 | 
             
                  end
         | 
| 438 494 | 
             
                end
         | 
| 439 495 |  | 
| @@ -601,10 +657,12 @@ module Fluent::Plugin | |
| 601 657 | 
             
                end
         | 
| 602 658 |  | 
| 603 659 | 
             
                class TailWatcher
         | 
| 604 | 
            -
                  def initialize( | 
| 605 | 
            -
                    @path = path
         | 
| 660 | 
            +
                  def initialize(target_info, pe, log, read_from_head, follow_inodes, update_watcher, line_buffer_timer_flusher, io_handler_build)
         | 
| 661 | 
            +
                    @path = target_info.path
         | 
| 662 | 
            +
                    @ino = target_info.ino
         | 
| 606 663 | 
             
                    @pe = pe || MemoryPositionEntry.new
         | 
| 607 664 | 
             
                    @read_from_head = read_from_head
         | 
| 665 | 
            +
                    @follow_inodes = follow_inodes
         | 
| 608 666 | 
             
                    @update_watcher = update_watcher
         | 
| 609 667 | 
             
                    @log = log
         | 
| 610 668 | 
             
                    @rotate_handler = RotateHandler.new(log, &method(:on_rotate))
         | 
| @@ -614,7 +672,7 @@ module Fluent::Plugin | |
| 614 672 | 
             
                    @watchers = []
         | 
| 615 673 | 
             
                  end
         | 
| 616 674 |  | 
| 617 | 
            -
                  attr_reader :path
         | 
| 675 | 
            +
                  attr_reader :path, :ino
         | 
| 618 676 | 
             
                  attr_reader :pe
         | 
| 619 677 | 
             
                  attr_reader :line_buffer_timer_flusher
         | 
| 620 678 | 
             
                  attr_accessor :unwatched  # This is used for removing position entry from PositionFile
         | 
| @@ -709,7 +767,17 @@ module Fluent::Plugin | |
| 709 767 | 
             
                      end
         | 
| 710 768 |  | 
| 711 769 | 
             
                      if watcher_needs_update
         | 
| 712 | 
            -
                         | 
| 770 | 
            +
                        # No need to update a watcher if stat is nil (file not present), because moving to inodes will create
         | 
| 771 | 
            +
                        # new watcher, and old watcher will be closed by stop_watcher in refresh_watchers method
         | 
| 772 | 
            +
                        if stat
         | 
| 773 | 
            +
                          target_info = TargetInfo.new(@path, stat.ino)
         | 
| 774 | 
            +
                          if @follow_inodes
         | 
| 775 | 
            +
                            # don't want to swap state because we need latest read offset in pos file even after rotate_wait
         | 
| 776 | 
            +
                            @update_watcher.call(target_info, @pe)
         | 
| 777 | 
            +
                          else
         | 
| 778 | 
            +
                            @update_watcher.call(target_info, swap_state(@pe))
         | 
| 779 | 
            +
                          end
         | 
| 780 | 
            +
                        end
         | 
| 713 781 | 
             
                      else
         | 
| 714 782 | 
             
                        @log.info "detected rotation of #{@path}"
         | 
| 715 783 | 
             
                        @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 []( | 
| 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 | 
            -
                       | 
| 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( | 
| 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 | 
            -
                         | 
| 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 | 
            -
                         | 
| 98 | 
            -
             | 
| 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 | 
            -
                         | 
| 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,7 @@ module Fluent::Plugin | |
| 224 247 | 
             
                    @inode
         | 
| 225 248 | 
             
                  end
         | 
| 226 249 | 
             
                end
         | 
| 250 | 
            +
             | 
| 251 | 
            +
                TargetInfo = Struct.new(:path, :ino)
         | 
| 227 252 | 
             
              end
         | 
| 228 253 | 
             
            end
         | 
    
        data/lib/fluent/plugin/in_tcp.rb
    CHANGED
    
    | @@ -98,6 +98,7 @@ module Fluent::Plugin | |
| 98 98 | 
             
                def start
         | 
| 99 99 | 
             
                  super
         | 
| 100 100 |  | 
| 101 | 
            +
                  log.info "listening tcp socket", bind: @bind, port: @port
         | 
| 101 102 | 
             
                  del_size = @delimiter.length
         | 
| 102 103 | 
             
                  if @_extract_enabled && @_extract_tag_key
         | 
| 103 104 | 
             
                    server_create(:in_tcp_server_single_emit, @port, bind: @bind, resolve_name: !!@source_hostname_key) do |data, conn|
         | 
| @@ -21,6 +21,16 @@ require 'fluent/tls' | |
| 21 21 | 
             
            require 'fluent/plugin/output'
         | 
| 22 22 | 
             
            require 'fluent/plugin_helper/socket'
         | 
| 23 23 |  | 
| 24 | 
            +
            # patch Net::HTTP to support extra_chain_cert which was added in Ruby feature #9758.
         | 
| 25 | 
            +
            # see: https://github.com/ruby/ruby/commit/31af0dafba6d3769d2a39617c0dddedb97883712
         | 
| 26 | 
            +
            unless Net::HTTP::SSL_IVNAMES.include?(:@extra_chain_cert)
         | 
| 27 | 
            +
              class Net::HTTP
         | 
| 28 | 
            +
                SSL_IVNAMES << :@extra_chain_cert
         | 
| 29 | 
            +
                SSL_ATTRIBUTES << :extra_chain_cert
         | 
| 30 | 
            +
                attr_accessor :extra_chain_cert
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| 33 | 
            +
             | 
| 24 34 | 
             
            module Fluent::Plugin
         | 
| 25 35 | 
             
              class HTTPOutput < Output
         | 
| 26 36 | 
             
                Fluent::Plugin.register_output('http', self)
         | 
| @@ -171,7 +181,15 @@ module Fluent::Plugin | |
| 171 181 | 
             
                    end
         | 
| 172 182 | 
             
                    if @tls_client_cert_path
         | 
| 173 183 | 
             
                      raise Fluent::ConfigError, "tls_client_cert_path is wrong: #{@tls_client_cert_path}" unless File.file?(@tls_client_cert_path)
         | 
| 174 | 
            -
             | 
| 184 | 
            +
             | 
| 185 | 
            +
                      bundle = File.read(@tls_client_cert_path)
         | 
| 186 | 
            +
                      bundle_certs = bundle.scan(/-----BEGIN CERTIFICATE-----(?:.|\n)+?-----END CERTIFICATE-----/)
         | 
| 187 | 
            +
                      opt[:cert] = OpenSSL::X509::Certificate.new(bundle_certs[0])
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                      intermediate_certs = bundle_certs[1..-1]
         | 
| 190 | 
            +
                      if intermediate_certs
         | 
| 191 | 
            +
                        opt[:extra_chain_cert] = intermediate_certs.map { |cert| OpenSSL::X509::Certificate.new(cert) }
         | 
| 192 | 
            +
                      end
         | 
| 175 193 | 
             
                    end
         | 
| 176 194 | 
             
                    if @tls_private_key_path
         | 
| 177 195 | 
             
                      raise Fluent::ConfigError, "tls_private_key_path is wrong: #{@tls_private_key_path}" unless File.file?(@tls_private_key_path)
         | 
| @@ -215,7 +233,7 @@ module Fluent::Plugin | |
| 215 233 | 
             
                    req.basic_auth(@auth.username, @auth.password)
         | 
| 216 234 | 
             
                  end
         | 
| 217 235 | 
             
                  set_headers(req)
         | 
| 218 | 
            -
                  req.body = @json_array ? "[#{chunk.read.chop | 
| 236 | 
            +
                  req.body = @json_array ? "[#{chunk.read.chop}]" : chunk.read
         | 
| 219 237 | 
             
                  req
         | 
| 220 238 | 
             
                end
         | 
| 221 239 |  |