fluentd 1.13.3 → 1.16.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|