fluentd 1.13.3 → 1.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/{bug_report.yaml → bug_report.yml} +2 -0
- data/.github/ISSUE_TEMPLATE/config.yml +2 -2
- data/.github/ISSUE_TEMPLATE/{feature_request.yaml → feature_request.yml} +1 -0
- data/.github/workflows/stale-actions.yml +11 -9
- data/.github/workflows/test.yml +32 -0
- data/CHANGELOG.md +490 -10
- data/CONTRIBUTING.md +2 -2
- data/MAINTAINERS.md +7 -5
- data/README.md +3 -23
- data/Rakefile +1 -1
- data/SECURITY.md +14 -0
- data/fluentd.gemspec +7 -8
- data/lib/fluent/command/cat.rb +13 -3
- data/lib/fluent/command/ctl.rb +6 -3
- data/lib/fluent/command/fluentd.rb +73 -65
- data/lib/fluent/command/plugin_config_formatter.rb +1 -1
- data/lib/fluent/compat/output.rb +9 -6
- data/lib/fluent/config/dsl.rb +1 -1
- data/lib/fluent/config/error.rb +12 -0
- data/lib/fluent/config/literal_parser.rb +2 -2
- data/lib/fluent/config/parser.rb +1 -1
- data/lib/fluent/config/v1_parser.rb +3 -3
- data/lib/fluent/config/yaml_parser/fluent_value.rb +47 -0
- data/lib/fluent/config/yaml_parser/loader.rb +108 -0
- data/lib/fluent/config/yaml_parser/parser.rb +166 -0
- data/lib/fluent/config/yaml_parser/section_builder.rb +107 -0
- data/lib/fluent/config/yaml_parser.rb +56 -0
- data/lib/fluent/config.rb +14 -1
- data/lib/fluent/counter/server.rb +1 -1
- data/lib/fluent/counter/validator.rb +3 -3
- data/lib/fluent/daemon.rb +2 -4
- data/lib/fluent/engine.rb +1 -1
- data/lib/fluent/env.rb +4 -0
- data/lib/fluent/error.rb +3 -0
- data/lib/fluent/event.rb +8 -4
- data/lib/fluent/event_router.rb +47 -2
- data/lib/fluent/file_wrapper.rb +137 -0
- data/lib/fluent/log/console_adapter.rb +66 -0
- data/lib/fluent/log.rb +44 -5
- data/lib/fluent/match.rb +1 -1
- data/lib/fluent/msgpack_factory.rb +6 -1
- data/lib/fluent/oj_options.rb +1 -2
- data/lib/fluent/plugin/bare_output.rb +49 -8
- data/lib/fluent/plugin/base.rb +26 -9
- data/lib/fluent/plugin/buf_file.rb +34 -5
- data/lib/fluent/plugin/buf_file_single.rb +32 -3
- data/lib/fluent/plugin/buffer/file_chunk.rb +1 -1
- data/lib/fluent/plugin/buffer.rb +216 -70
- data/lib/fluent/plugin/filter.rb +35 -1
- data/lib/fluent/plugin/filter_record_transformer.rb +1 -1
- data/lib/fluent/plugin/in_forward.rb +2 -2
- data/lib/fluent/plugin/in_http.rb +39 -10
- data/lib/fluent/plugin/in_monitor_agent.rb +4 -2
- data/lib/fluent/plugin/in_sample.rb +1 -1
- data/lib/fluent/plugin/in_syslog.rb +13 -1
- data/lib/fluent/plugin/in_tail/group_watch.rb +204 -0
- data/lib/fluent/plugin/in_tail/position_file.rb +33 -33
- data/lib/fluent/plugin/in_tail.rb +216 -84
- data/lib/fluent/plugin/in_tcp.rb +47 -2
- data/lib/fluent/plugin/input.rb +39 -1
- data/lib/fluent/plugin/metrics.rb +119 -0
- data/lib/fluent/plugin/metrics_local.rb +96 -0
- data/lib/fluent/plugin/multi_output.rb +43 -6
- data/lib/fluent/plugin/out_copy.rb +1 -1
- data/lib/fluent/plugin/out_exec_filter.rb +2 -2
- data/lib/fluent/plugin/out_file.rb +20 -2
- data/lib/fluent/plugin/out_forward/ack_handler.rb +19 -4
- data/lib/fluent/plugin/out_forward/socket_cache.rb +2 -0
- data/lib/fluent/plugin/out_forward.rb +17 -9
- data/lib/fluent/plugin/out_secondary_file.rb +39 -22
- data/lib/fluent/plugin/output.rb +167 -78
- data/lib/fluent/plugin/parser.rb +3 -4
- data/lib/fluent/plugin/parser_apache2.rb +1 -1
- data/lib/fluent/plugin/parser_json.rb +1 -1
- data/lib/fluent/plugin/parser_syslog.rb +1 -1
- data/lib/fluent/plugin/storage_local.rb +3 -5
- data/lib/fluent/plugin.rb +10 -1
- data/lib/fluent/plugin_helper/child_process.rb +3 -0
- data/lib/fluent/plugin_helper/event_emitter.rb +8 -1
- data/lib/fluent/plugin_helper/event_loop.rb +2 -2
- data/lib/fluent/plugin_helper/http_server/server.rb +2 -1
- data/lib/fluent/plugin_helper/metrics.rb +129 -0
- data/lib/fluent/plugin_helper/record_accessor.rb +1 -1
- data/lib/fluent/plugin_helper/retry_state.rb +14 -4
- data/lib/fluent/plugin_helper/server.rb +35 -6
- data/lib/fluent/plugin_helper/service_discovery.rb +2 -2
- data/lib/fluent/plugin_helper/socket.rb +13 -2
- data/lib/fluent/plugin_helper/thread.rb +3 -3
- data/lib/fluent/plugin_helper.rb +1 -0
- data/lib/fluent/plugin_id.rb +3 -2
- data/lib/fluent/registry.rb +2 -1
- data/lib/fluent/root_agent.rb +6 -0
- data/lib/fluent/rpc.rb +4 -3
- data/lib/fluent/supervisor.rb +283 -259
- data/lib/fluent/system_config.rb +13 -3
- data/lib/fluent/test/driver/base.rb +11 -5
- data/lib/fluent/test/driver/filter.rb +4 -0
- data/lib/fluent/test/startup_shutdown.rb +6 -8
- data/lib/fluent/time.rb +21 -20
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/win32api.rb +38 -0
- data/lib/fluent/winsvc.rb +5 -8
- data/templates/new_gem/test/helper.rb.erb +0 -1
- data/test/command/test_cat.rb +31 -2
- data/test/command/test_ctl.rb +1 -2
- data/test/command/test_fluentd.rb +209 -24
- data/test/command/test_plugin_config_formatter.rb +0 -1
- data/test/compat/test_parser.rb +6 -6
- data/test/config/test_system_config.rb +13 -11
- data/test/config/test_types.rb +1 -1
- data/test/log/test_console_adapter.rb +110 -0
- data/test/plugin/in_tail/test_io_handler.rb +26 -8
- data/test/plugin/in_tail/test_position_file.rb +48 -59
- data/test/plugin/out_forward/test_ack_handler.rb +39 -0
- data/test/plugin/out_forward/test_socket_cache.rb +26 -1
- data/test/plugin/test_bare_output.rb +14 -1
- data/test/plugin/test_base.rb +133 -1
- data/test/plugin/test_buf_file.rb +62 -23
- data/test/plugin/test_buf_file_single.rb +65 -0
- data/test/plugin/test_buffer.rb +267 -3
- data/test/plugin/test_buffer_chunk.rb +11 -0
- data/test/plugin/test_filter.rb +12 -1
- data/test/plugin/test_filter_parser.rb +1 -1
- data/test/plugin/test_filter_stdout.rb +2 -2
- data/test/plugin/test_in_forward.rb +9 -11
- data/test/plugin/test_in_http.rb +65 -3
- data/test/plugin/test_in_monitor_agent.rb +216 -11
- data/test/plugin/test_in_object_space.rb +9 -3
- data/test/plugin/test_in_syslog.rb +35 -0
- data/test/plugin/test_in_tail.rb +1393 -385
- data/test/plugin/test_in_tcp.rb +87 -2
- data/test/plugin/test_in_udp.rb +28 -0
- data/test/plugin/test_in_unix.rb +2 -2
- data/test/plugin/test_input.rb +12 -1
- data/test/plugin/test_metrics.rb +294 -0
- data/test/plugin/test_metrics_local.rb +96 -0
- data/test/plugin/test_multi_output.rb +25 -1
- data/test/plugin/test_out_exec.rb +6 -4
- data/test/plugin/test_out_exec_filter.rb +6 -2
- data/test/plugin/test_out_file.rb +34 -17
- data/test/plugin/test_out_forward.rb +78 -77
- data/test/plugin/test_out_http.rb +1 -0
- data/test/plugin/test_out_stdout.rb +2 -2
- data/test/plugin/test_output.rb +297 -12
- data/test/plugin/test_output_as_buffered.rb +44 -44
- data/test/plugin/test_output_as_buffered_compress.rb +32 -18
- data/test/plugin/test_output_as_buffered_retries.rb +54 -7
- data/test/plugin/test_output_as_buffered_secondary.rb +4 -4
- data/test/plugin/test_parser_regexp.rb +1 -6
- data/test/plugin/test_parser_syslog.rb +1 -1
- data/test/plugin_helper/test_cert_option.rb +1 -1
- data/test/plugin_helper/test_child_process.rb +38 -16
- data/test/plugin_helper/test_event_emitter.rb +29 -0
- data/test/plugin_helper/test_http_server_helper.rb +1 -1
- data/test/plugin_helper/test_metrics.rb +137 -0
- data/test/plugin_helper/test_retry_state.rb +602 -38
- data/test/plugin_helper/test_server.rb +78 -6
- data/test/plugin_helper/test_timer.rb +2 -2
- data/test/test_config.rb +191 -24
- data/test/test_event_router.rb +17 -0
- data/test/test_file_wrapper.rb +53 -0
- data/test/test_formatter.rb +24 -21
- data/test/test_log.rb +122 -40
- data/test/test_msgpack_factory.rb +32 -0
- data/test/test_plugin_classes.rb +102 -0
- data/test/test_root_agent.rb +30 -1
- data/test/test_supervisor.rb +477 -257
- data/test/test_time_parser.rb +22 -0
- metadata +55 -34
- data/.drone.yml +0 -35
- data/.github/workflows/issue-auto-closer.yml +0 -12
- data/.github/workflows/linux-test.yaml +0 -36
- data/.github/workflows/macos-test.yaml +0 -30
- data/.github/workflows/windows-test.yaml +0 -46
- data/.gitlab-ci.yml +0 -103
- data/lib/fluent/plugin/file_wrapper.rb +0 -187
- data/test/plugin/test_file_wrapper.rb +0 -126
- data/test/test_logger_initializer.rb +0 -46
|
@@ -24,20 +24,19 @@ require 'fluent/plugin/parser_multiline'
|
|
|
24
24
|
require 'fluent/variable_store'
|
|
25
25
|
require 'fluent/capability'
|
|
26
26
|
require 'fluent/plugin/in_tail/position_file'
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
require_relative 'file_wrapper'
|
|
30
|
-
else
|
|
31
|
-
Fluent::FileWrapper = File
|
|
32
|
-
end
|
|
27
|
+
require 'fluent/plugin/in_tail/group_watch'
|
|
28
|
+
require 'fluent/file_wrapper'
|
|
33
29
|
|
|
34
30
|
module Fluent::Plugin
|
|
35
31
|
class TailInput < Fluent::Plugin::Input
|
|
32
|
+
include GroupWatch
|
|
33
|
+
|
|
36
34
|
Fluent::Plugin.register_input('tail', self)
|
|
37
35
|
|
|
38
36
|
helpers :timer, :event_loop, :parser, :compat_parameters
|
|
39
37
|
|
|
40
38
|
RESERVED_CHARS = ['/', '*', '%'].freeze
|
|
39
|
+
MetricsInfo = Struct.new(:opened, :closed, :rotated)
|
|
41
40
|
|
|
42
41
|
class WatcherSetupError < StandardError
|
|
43
42
|
def initialize(msg)
|
|
@@ -53,10 +52,13 @@ module Fluent::Plugin
|
|
|
53
52
|
super
|
|
54
53
|
@paths = []
|
|
55
54
|
@tails = {}
|
|
55
|
+
@tails_rotate_wait = {}
|
|
56
56
|
@pf_file = nil
|
|
57
57
|
@pf = nil
|
|
58
58
|
@ignore_list = []
|
|
59
59
|
@shutdown_start_time = nil
|
|
60
|
+
@metrics = nil
|
|
61
|
+
@startup = true
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
desc 'The paths to read. Multiple paths can be specified, separated by comma.'
|
|
@@ -110,6 +112,8 @@ module Fluent::Plugin
|
|
|
110
112
|
config_param :path_timezone, :string, default: nil
|
|
111
113
|
desc 'Follow inodes instead of following file names. Guarantees more stable delivery and allows to use * in path pattern with rotating files'
|
|
112
114
|
config_param :follow_inodes, :bool, default: false
|
|
115
|
+
desc 'Maximum length of line. The longer line is just skipped.'
|
|
116
|
+
config_param :max_line_size, :size, default: nil
|
|
113
117
|
|
|
114
118
|
config_section :parse, required: false, multi: true, init: true, param_name: :parser_configs do
|
|
115
119
|
config_argument :usage, :string, default: 'in_tail_parser'
|
|
@@ -191,6 +195,10 @@ module Fluent::Plugin
|
|
|
191
195
|
@read_bytes_limit_per_second = min_bytes
|
|
192
196
|
end
|
|
193
197
|
end
|
|
198
|
+
opened_file_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_opened_total", help_text: "Total number of opened files")
|
|
199
|
+
closed_file_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_closed_total", help_text: "Total number of closed files")
|
|
200
|
+
rotated_file_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_rotated_total", help_text: "Total number of rotated files")
|
|
201
|
+
@metrics = MetricsInfo.new(opened_file_metrics, closed_file_metrics, rotated_file_metrics)
|
|
194
202
|
end
|
|
195
203
|
|
|
196
204
|
def configure_tag
|
|
@@ -260,6 +268,9 @@ module Fluent::Plugin
|
|
|
260
268
|
@shutdown_start_time = Fluent::Clock.now
|
|
261
269
|
# during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
|
|
262
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
|
|
263
274
|
@pf_file.close if @pf_file
|
|
264
275
|
|
|
265
276
|
super
|
|
@@ -268,6 +279,7 @@ module Fluent::Plugin
|
|
|
268
279
|
def close
|
|
269
280
|
super
|
|
270
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()
|
|
271
283
|
close_watcher_handles
|
|
272
284
|
end
|
|
273
285
|
|
|
@@ -345,11 +357,11 @@ module Fluent::Plugin
|
|
|
345
357
|
|
|
346
358
|
def existence_path
|
|
347
359
|
hash = {}
|
|
348
|
-
@tails.
|
|
360
|
+
@tails.each {|path, tw|
|
|
349
361
|
if @follow_inodes
|
|
350
|
-
hash[
|
|
362
|
+
hash[tw.ino] = TargetInfo.new(tw.path, tw.ino)
|
|
351
363
|
else
|
|
352
|
-
hash[
|
|
364
|
+
hash[tw.path] = TargetInfo.new(tw.path, tw.ino)
|
|
353
365
|
end
|
|
354
366
|
}
|
|
355
367
|
hash
|
|
@@ -364,18 +376,59 @@ module Fluent::Plugin
|
|
|
364
376
|
target_paths_hash = expand_paths
|
|
365
377
|
existence_paths_hash = existence_path
|
|
366
378
|
|
|
367
|
-
log.debug {
|
|
379
|
+
log.debug {
|
|
380
|
+
target_paths_str = target_paths_hash.collect { |key, target_info| target_info.path }.join(",")
|
|
381
|
+
existence_paths_str = existence_paths_hash.collect { |key, target_info| target_info.path }.join(",")
|
|
382
|
+
"tailing paths: target = #{target_paths_str} | existing = #{existence_paths_str}"
|
|
383
|
+
}
|
|
384
|
+
|
|
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
|
|
368
397
|
|
|
369
|
-
|
|
398
|
+
removed_hash = existence_paths_hash.reject {|key, value| target_paths_hash.key?(key)}
|
|
370
399
|
added_hash = target_paths_hash.reject {|key, value| existence_paths_hash.key?(key)}
|
|
371
400
|
|
|
372
|
-
|
|
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?
|
|
373
424
|
start_watchers(added_hash) unless added_hash.empty?
|
|
425
|
+
@startup = false if @startup
|
|
374
426
|
end
|
|
375
427
|
|
|
376
428
|
def setup_watcher(target_info, pe)
|
|
377
429
|
line_buffer_timer_flusher = @multiline_mode ? TailWatcher::LineBufferTimerFlusher.new(log, @multiline_flush_interval, &method(:flush_buffer)) : nil
|
|
378
|
-
|
|
430
|
+
read_from_head = !@startup || @read_from_head
|
|
431
|
+
tw = TailWatcher.new(target_info, pe, log, read_from_head, @follow_inodes, method(:update_watcher), line_buffer_timer_flusher, method(:io_handler), @metrics)
|
|
379
432
|
|
|
380
433
|
if @enable_watch_timer
|
|
381
434
|
tt = TimerTrigger.new(1, log) { tw.on_notify }
|
|
@@ -383,7 +436,7 @@ module Fluent::Plugin
|
|
|
383
436
|
end
|
|
384
437
|
|
|
385
438
|
if @enable_stat_watcher
|
|
386
|
-
tt = StatWatcher.new(path, log) { tw.on_notify }
|
|
439
|
+
tt = StatWatcher.new(target_info.path, log) { tw.on_notify }
|
|
387
440
|
tw.register_watcher(tt)
|
|
388
441
|
end
|
|
389
442
|
|
|
@@ -391,6 +444,8 @@ module Fluent::Plugin
|
|
|
391
444
|
event_loop_attach(watcher)
|
|
392
445
|
end
|
|
393
446
|
|
|
447
|
+
tw.group_watcher = add_path_to_group_watcher(target_info.path)
|
|
448
|
+
|
|
394
449
|
tw
|
|
395
450
|
rescue => e
|
|
396
451
|
if tw
|
|
@@ -405,36 +460,31 @@ module Fluent::Plugin
|
|
|
405
460
|
end
|
|
406
461
|
|
|
407
462
|
def construct_watcher(target_info)
|
|
463
|
+
path = target_info.path
|
|
464
|
+
|
|
465
|
+
# The file might be rotated or removed after collecting paths, so check inode again here.
|
|
466
|
+
begin
|
|
467
|
+
target_info.ino = Fluent::FileWrapper.stat(path).ino
|
|
468
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
469
|
+
$log.warn "stat() for #{path} failed. Continuing without tailing it."
|
|
470
|
+
return
|
|
471
|
+
end
|
|
472
|
+
|
|
408
473
|
pe = nil
|
|
409
474
|
if @pf
|
|
410
475
|
pe = @pf[target_info]
|
|
411
|
-
if @read_from_head && pe.read_inode.zero?
|
|
412
|
-
begin
|
|
413
|
-
pe.update(Fluent::FileWrapper.stat(target_info.path).ino, 0)
|
|
414
|
-
rescue Errno::ENOENT, Errno::EACCES
|
|
415
|
-
$log.warn "stat() for #{target_info.path} failed. Continuing without tailing it."
|
|
416
|
-
end
|
|
417
|
-
end
|
|
476
|
+
pe.update(target_info.ino, 0) if @read_from_head && pe.read_inode.zero?
|
|
418
477
|
end
|
|
419
478
|
|
|
420
479
|
begin
|
|
421
480
|
tw = setup_watcher(target_info, pe)
|
|
422
481
|
rescue WatcherSetupError => e
|
|
423
|
-
log.warn "Skip #{
|
|
482
|
+
log.warn "Skip #{path} because unexpected setup error happens: #{e}"
|
|
424
483
|
return
|
|
425
484
|
end
|
|
426
485
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
@tails.delete(target_info)
|
|
430
|
-
@tails[target_info] = tw
|
|
431
|
-
tw.on_notify
|
|
432
|
-
rescue Errno::ENOENT, Errno::EACCES => e
|
|
433
|
-
$log.warn "stat() for #{target_info.path} failed with #{e.class.name}. Drop tail watcher for now."
|
|
434
|
-
# explicitly detach and unwatch watcher `tw`.
|
|
435
|
-
tw.unwatched = true
|
|
436
|
-
detach_watcher(tw, target_info.ino, false)
|
|
437
|
-
end
|
|
486
|
+
@tails[path] = tw
|
|
487
|
+
tw.on_notify
|
|
438
488
|
end
|
|
439
489
|
|
|
440
490
|
def start_watchers(targets_info)
|
|
@@ -446,10 +496,12 @@ module Fluent::Plugin
|
|
|
446
496
|
|
|
447
497
|
def stop_watchers(targets_info, immediate: false, unwatched: false, remove_watcher: true)
|
|
448
498
|
targets_info.each_value { |target_info|
|
|
499
|
+
remove_path_from_group_watcher(target_info.path)
|
|
500
|
+
|
|
449
501
|
if remove_watcher
|
|
450
|
-
tw = @tails.delete(target_info)
|
|
502
|
+
tw = @tails.delete(target_info.path)
|
|
451
503
|
else
|
|
452
|
-
tw = @tails[target_info]
|
|
504
|
+
tw = @tails[target_info.path]
|
|
453
505
|
end
|
|
454
506
|
if tw
|
|
455
507
|
tw.unwatched = unwatched
|
|
@@ -463,57 +515,74 @@ module Fluent::Plugin
|
|
|
463
515
|
end
|
|
464
516
|
|
|
465
517
|
def close_watcher_handles
|
|
466
|
-
@tails.keys.each do |
|
|
467
|
-
tw = @tails.delete(
|
|
518
|
+
@tails.keys.each do |path|
|
|
519
|
+
tw = @tails.delete(path)
|
|
468
520
|
if tw
|
|
469
521
|
tw.close
|
|
470
522
|
end
|
|
471
523
|
end
|
|
524
|
+
@tails_rotate_wait.keys.each do |tw|
|
|
525
|
+
tw.close
|
|
526
|
+
end
|
|
472
527
|
end
|
|
473
528
|
|
|
474
529
|
# refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
|
|
475
|
-
def update_watcher(
|
|
476
|
-
|
|
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
|
|
550
|
+
|
|
551
|
+
log.info("detected rotation of #{path}; waiting #{@rotate_wait} seconds")
|
|
477
552
|
|
|
478
553
|
if @pf
|
|
479
554
|
pe_inode = pe.read_inode
|
|
480
|
-
target_info_from_position_entry = TargetInfo.new(
|
|
555
|
+
target_info_from_position_entry = TargetInfo.new(path, pe_inode)
|
|
481
556
|
unless pe_inode == @pf[target_info_from_position_entry].read_inode
|
|
482
|
-
log.
|
|
557
|
+
log.warn "Skip update_watcher because watcher has been already updated by other inotify event",
|
|
558
|
+
path: path, inode: pe.read_inode, inode_in_pos_file: @pf[target_info_from_position_entry].read_inode
|
|
483
559
|
return
|
|
484
560
|
end
|
|
485
561
|
end
|
|
486
562
|
|
|
487
|
-
|
|
488
|
-
rotated_tw = @tails[rotated_target_info]
|
|
489
|
-
new_target_info = target_info.dup
|
|
563
|
+
new_target_info = TargetInfo.new(path, new_inode)
|
|
490
564
|
|
|
491
565
|
if @follow_inodes
|
|
492
|
-
new_position_entry = @pf[
|
|
493
|
-
|
|
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.
|
|
494
569
|
if new_position_entry.read_inode == 0
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
rotated_tw.unwatched = true
|
|
498
|
-
# Make sure to delete old key, it has a different ino while the hash key is same.
|
|
499
|
-
@tails.delete(rotated_target_info)
|
|
500
|
-
@tails[new_target_info] = setup_watcher(new_target_info, new_position_entry)
|
|
501
|
-
@tails[new_target_info].on_notify
|
|
570
|
+
@tails[path] = setup_watcher(new_target_info, new_position_entry)
|
|
571
|
+
@tails[path].on_notify
|
|
502
572
|
end
|
|
503
573
|
else
|
|
504
|
-
|
|
505
|
-
@tails.
|
|
506
|
-
@tails[new_target_info] = setup_watcher(new_target_info, pe)
|
|
507
|
-
@tails[new_target_info].on_notify
|
|
574
|
+
@tails[path] = setup_watcher(new_target_info, pe)
|
|
575
|
+
@tails[path].on_notify
|
|
508
576
|
end
|
|
509
|
-
|
|
577
|
+
|
|
578
|
+
detach_watcher_after_rotate_wait(tail_watcher, pe.read_inode)
|
|
510
579
|
end
|
|
511
580
|
|
|
512
|
-
# TailWatcher#close is called by another thread at shutdown phase.
|
|
513
|
-
# It causes 'can't modify string; temporarily locked' error in IOHandler
|
|
514
|
-
# so adding close_io argument to avoid this problem.
|
|
515
|
-
# At shutdown, IOHandler's io will be released automatically after detached the event loop
|
|
516
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
|
|
517
586
|
tw.watchers.each do |watcher|
|
|
518
587
|
event_loop_detach(watcher)
|
|
519
588
|
end
|
|
@@ -521,24 +590,29 @@ module Fluent::Plugin
|
|
|
521
590
|
|
|
522
591
|
tw.close if close_io
|
|
523
592
|
|
|
524
|
-
if tw.unwatched && @
|
|
593
|
+
if @pf && tw.unwatched && (@follow_inode || !@tails[tw.path])
|
|
525
594
|
target_info = TargetInfo.new(tw.path, ino)
|
|
526
595
|
@pf.unwatch(target_info)
|
|
527
596
|
end
|
|
528
597
|
end
|
|
529
598
|
|
|
599
|
+
def throttling_is_enabled?(tw)
|
|
600
|
+
return true if @read_bytes_limit_per_second > 0
|
|
601
|
+
return true if tw.group_watcher && tw.group_watcher.limit >= 0
|
|
602
|
+
false
|
|
603
|
+
end
|
|
604
|
+
|
|
530
605
|
def detach_watcher_after_rotate_wait(tw, ino)
|
|
531
606
|
# Call event_loop_attach/event_loop_detach is high-cost for short-live object.
|
|
532
607
|
# If this has a problem with large number of files, use @_event_loop directly instead of timer_execute.
|
|
533
608
|
if @open_on_every_update
|
|
534
609
|
# Detach now because it's already closed, waiting it doesn't make sense.
|
|
535
610
|
detach_watcher(tw, ino)
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
else
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
return if @tails_rotate_wait[tw]
|
|
614
|
+
|
|
615
|
+
if throttling_is_enabled?(tw)
|
|
542
616
|
# When the throttling feature is enabled, it might not reach EOF yet.
|
|
543
617
|
# Should ensure to read all contents before closing it, with keeping throttling.
|
|
544
618
|
start_time_to_wait = Fluent::Clock.now
|
|
@@ -546,9 +620,18 @@ module Fluent::Plugin
|
|
|
546
620
|
elapsed = Fluent::Clock.now - start_time_to_wait
|
|
547
621
|
if tw.eof? && elapsed >= @rotate_wait
|
|
548
622
|
timer.detach
|
|
623
|
+
@tails_rotate_wait.delete(tw)
|
|
549
624
|
detach_watcher(tw, ino)
|
|
550
625
|
end
|
|
551
626
|
end
|
|
627
|
+
@tails_rotate_wait[tw] = { ino: ino, timer: timer }
|
|
628
|
+
else
|
|
629
|
+
# when the throttling feature isn't enabled, just wait @rotate_wait
|
|
630
|
+
timer = timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
|
|
631
|
+
@tails_rotate_wait.delete(tw)
|
|
632
|
+
detach_watcher(tw, ino)
|
|
633
|
+
end
|
|
634
|
+
@tails_rotate_wait[tw] = { ino: ino, timer: timer }
|
|
552
635
|
end
|
|
553
636
|
end
|
|
554
637
|
|
|
@@ -581,6 +664,14 @@ module Fluent::Plugin
|
|
|
581
664
|
|
|
582
665
|
# @return true if no error or unrecoverable error happens in emit action. false if got BufferOverflowError
|
|
583
666
|
def receive_lines(lines, tail_watcher)
|
|
667
|
+
lines = lines.reject do |line|
|
|
668
|
+
skip_line = @max_line_size ? line.bytesize > @max_line_size : false
|
|
669
|
+
if skip_line
|
|
670
|
+
log.warn "received line length is longer than #{@max_line_size}"
|
|
671
|
+
log.debug "skipped line: #{line.chomp}"
|
|
672
|
+
end
|
|
673
|
+
skip_line
|
|
674
|
+
end
|
|
584
675
|
es = @receive_handler.call(lines, tail_watcher)
|
|
585
676
|
unless es.empty?
|
|
586
677
|
tag = if @tag_prefix || @tag_suffix
|
|
@@ -670,6 +761,19 @@ module Fluent::Plugin
|
|
|
670
761
|
es
|
|
671
762
|
end
|
|
672
763
|
|
|
764
|
+
def statistics
|
|
765
|
+
stats = super
|
|
766
|
+
|
|
767
|
+
stats = {
|
|
768
|
+
'input' => stats["input"].merge({
|
|
769
|
+
'opened_file_count' => @metrics.opened.get,
|
|
770
|
+
'closed_file_count' => @metrics.closed.get,
|
|
771
|
+
'rotated_file_count' => @metrics.rotated.get,
|
|
772
|
+
})
|
|
773
|
+
}
|
|
774
|
+
stats
|
|
775
|
+
end
|
|
776
|
+
|
|
673
777
|
private
|
|
674
778
|
|
|
675
779
|
def io_handler(watcher, path)
|
|
@@ -682,6 +786,7 @@ module Fluent::Plugin
|
|
|
682
786
|
open_on_every_update: @open_on_every_update,
|
|
683
787
|
from_encoding: @from_encoding,
|
|
684
788
|
encoding: @encoding,
|
|
789
|
+
metrics: @metrics,
|
|
685
790
|
&method(:receive_lines)
|
|
686
791
|
)
|
|
687
792
|
end
|
|
@@ -717,7 +822,7 @@ module Fluent::Plugin
|
|
|
717
822
|
end
|
|
718
823
|
|
|
719
824
|
class TailWatcher
|
|
720
|
-
def initialize(target_info, pe, log, read_from_head, follow_inodes, update_watcher, line_buffer_timer_flusher, io_handler_build)
|
|
825
|
+
def initialize(target_info, pe, log, read_from_head, follow_inodes, update_watcher, line_buffer_timer_flusher, io_handler_build, metrics)
|
|
721
826
|
@path = target_info.path
|
|
722
827
|
@ino = target_info.ino
|
|
723
828
|
@pe = pe || MemoryPositionEntry.new
|
|
@@ -729,6 +834,7 @@ module Fluent::Plugin
|
|
|
729
834
|
@line_buffer_timer_flusher = line_buffer_timer_flusher
|
|
730
835
|
@io_handler = nil
|
|
731
836
|
@io_handler_build = io_handler_build
|
|
837
|
+
@metrics = metrics
|
|
732
838
|
@watchers = []
|
|
733
839
|
end
|
|
734
840
|
|
|
@@ -737,9 +843,10 @@ module Fluent::Plugin
|
|
|
737
843
|
attr_reader :line_buffer_timer_flusher
|
|
738
844
|
attr_accessor :unwatched # This is used for removing position entry from PositionFile
|
|
739
845
|
attr_reader :watchers
|
|
846
|
+
attr_accessor :group_watcher
|
|
740
847
|
|
|
741
848
|
def tag
|
|
742
|
-
@parsed_tag ||= @path.tr('/', '.').
|
|
849
|
+
@parsed_tag ||= @path.tr('/', '.').squeeze('.').gsub(/^\./, '')
|
|
743
850
|
end
|
|
744
851
|
|
|
745
852
|
def register_watcher(watcher)
|
|
@@ -762,7 +869,7 @@ module Fluent::Plugin
|
|
|
762
869
|
end
|
|
763
870
|
|
|
764
871
|
def eof?
|
|
765
|
-
@io_handler.eof?
|
|
872
|
+
@io_handler.nil? || @io_handler.eof?
|
|
766
873
|
end
|
|
767
874
|
|
|
768
875
|
def on_notify
|
|
@@ -835,26 +942,27 @@ module Fluent::Plugin
|
|
|
835
942
|
|
|
836
943
|
if watcher_needs_update
|
|
837
944
|
if @follow_inodes
|
|
838
|
-
#
|
|
839
|
-
#
|
|
840
|
-
#
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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)
|
|
845
953
|
else
|
|
846
954
|
# Permit to handle if stat is nil (file not present).
|
|
847
955
|
# If a file is mv-ed and a new file is created during
|
|
848
956
|
# calling `#refresh_watchers`s, and `#refresh_watchers` won't run `#start_watchers`
|
|
849
957
|
# and `#stop_watchers()` for the path because `target_paths_hash`
|
|
850
958
|
# always contains the path.
|
|
851
|
-
|
|
852
|
-
@update_watcher.call(target_info, swap_state(@pe))
|
|
959
|
+
@update_watcher.call(self, swap_state(@pe), stat&.ino)
|
|
853
960
|
end
|
|
854
961
|
else
|
|
855
962
|
@log.info "detected rotation of #{@path}"
|
|
856
963
|
@io_handler = io_handler
|
|
857
964
|
end
|
|
965
|
+
@metrics.rotated.inc
|
|
858
966
|
end
|
|
859
967
|
end
|
|
860
968
|
|
|
@@ -934,7 +1042,7 @@ module Fluent::Plugin
|
|
|
934
1042
|
|
|
935
1043
|
attr_accessor :shutdown_timeout
|
|
936
1044
|
|
|
937
|
-
def initialize(watcher, path:, read_lines_limit:, read_bytes_limit_per_second:, log:, open_on_every_update:, from_encoding: nil, encoding: nil, &receive_lines)
|
|
1045
|
+
def initialize(watcher, path:, read_lines_limit:, read_bytes_limit_per_second:, log:, open_on_every_update:, from_encoding: nil, encoding: nil, metrics:, &receive_lines)
|
|
938
1046
|
@watcher = watcher
|
|
939
1047
|
@path = path
|
|
940
1048
|
@read_lines_limit = read_lines_limit
|
|
@@ -953,10 +1061,15 @@ module Fluent::Plugin
|
|
|
953
1061
|
@shutdown_timeout = SHUTDOWN_TIMEOUT
|
|
954
1062
|
@shutdown_mutex = Mutex.new
|
|
955
1063
|
@eof = false
|
|
1064
|
+
@metrics = metrics
|
|
956
1065
|
|
|
957
1066
|
@log.info "following tail of #{@path}"
|
|
958
1067
|
end
|
|
959
1068
|
|
|
1069
|
+
def group_watcher
|
|
1070
|
+
@watcher.group_watcher
|
|
1071
|
+
end
|
|
1072
|
+
|
|
960
1073
|
def on_notify
|
|
961
1074
|
@notify_mutex.synchronize { handle_notify }
|
|
962
1075
|
end
|
|
@@ -972,6 +1085,7 @@ module Fluent::Plugin
|
|
|
972
1085
|
if @io && !@io.closed?
|
|
973
1086
|
@io.close
|
|
974
1087
|
@io = nil
|
|
1088
|
+
@metrics.closed.inc
|
|
975
1089
|
end
|
|
976
1090
|
end
|
|
977
1091
|
|
|
@@ -1013,6 +1127,7 @@ module Fluent::Plugin
|
|
|
1013
1127
|
|
|
1014
1128
|
def handle_notify
|
|
1015
1129
|
return if limit_bytes_per_second_reached?
|
|
1130
|
+
return if group_watcher&.limit_lines_reached?(@path)
|
|
1016
1131
|
|
|
1017
1132
|
with_io do |io|
|
|
1018
1133
|
begin
|
|
@@ -1022,17 +1137,26 @@ module Fluent::Plugin
|
|
|
1022
1137
|
begin
|
|
1023
1138
|
while true
|
|
1024
1139
|
@start_reading_time ||= Fluent::Clock.now
|
|
1140
|
+
group_watcher&.update_reading_time(@path)
|
|
1141
|
+
|
|
1025
1142
|
data = io.readpartial(BYTES_TO_READ, @iobuf)
|
|
1026
1143
|
@eof = false
|
|
1027
1144
|
@number_bytes_read += data.bytesize
|
|
1028
1145
|
@fifo << data
|
|
1146
|
+
|
|
1147
|
+
n_lines_before_read = @lines.size
|
|
1029
1148
|
@fifo.read_lines(@lines)
|
|
1149
|
+
group_watcher&.update_lines_read(@path, @lines.size - n_lines_before_read)
|
|
1030
1150
|
|
|
1031
|
-
|
|
1151
|
+
group_watcher_limit = group_watcher&.limit_lines_reached?(@path)
|
|
1152
|
+
@log.debug "Reading Limit exceeded #{@path} #{group_watcher.number_lines_read}" if group_watcher_limit
|
|
1153
|
+
|
|
1154
|
+
if group_watcher_limit || limit_bytes_per_second_reached? || should_shutdown_now?
|
|
1032
1155
|
# Just get out from tailing loop.
|
|
1033
1156
|
read_more = false
|
|
1034
1157
|
break
|
|
1035
1158
|
end
|
|
1159
|
+
|
|
1036
1160
|
if @lines.size >= @read_lines_limit
|
|
1037
1161
|
# not to use too much memory in case the file is very large
|
|
1038
1162
|
read_more = true
|
|
@@ -1059,11 +1183,15 @@ module Fluent::Plugin
|
|
|
1059
1183
|
def open
|
|
1060
1184
|
io = Fluent::FileWrapper.open(@path)
|
|
1061
1185
|
io.seek(@watcher.pe.read_pos + @fifo.bytesize)
|
|
1186
|
+
@metrics.opened.inc
|
|
1062
1187
|
io
|
|
1063
1188
|
rescue RangeError
|
|
1064
1189
|
io.close if io
|
|
1065
1190
|
raise WatcherSetupError, "seek error with #{@path}: file position = #{@watcher.pe.read_pos.to_s(16)}, reading bytesize = #{@fifo.bytesize.to_s(16)}"
|
|
1066
|
-
rescue Errno::
|
|
1191
|
+
rescue Errno::EACCES => e
|
|
1192
|
+
@log.warn "#{e}"
|
|
1193
|
+
nil
|
|
1194
|
+
rescue Errno::ENOENT
|
|
1067
1195
|
nil
|
|
1068
1196
|
end
|
|
1069
1197
|
|
|
@@ -1108,6 +1236,10 @@ module Fluent::Plugin
|
|
|
1108
1236
|
def opened?
|
|
1109
1237
|
false
|
|
1110
1238
|
end
|
|
1239
|
+
|
|
1240
|
+
def eof?
|
|
1241
|
+
true
|
|
1242
|
+
end
|
|
1111
1243
|
end
|
|
1112
1244
|
|
|
1113
1245
|
class RotateHandler
|