fluentd 1.20.0-aarch64-mingw-ucrt
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 +7 -0
- data/.rubocop.yml +116 -0
- data/ADOPTERS.md +5 -0
- data/AGENTS.md +46 -0
- data/AUTHORS +2 -0
- data/CHANGELOG.md +3324 -0
- data/CONTRIBUTING.md +45 -0
- data/GOVERNANCE.md +55 -0
- data/GithubWorkflow.md +78 -0
- data/LICENSE +202 -0
- data/MAINTAINERS.md +19 -0
- data/README.md +73 -0
- data/Rakefile +89 -0
- data/SECURITY.md +47 -0
- data/bin/fluent-binlog-reader +7 -0
- data/bin/fluent-ca-generate +6 -0
- data/bin/fluent-cap-ctl +7 -0
- data/bin/fluent-cat +5 -0
- data/bin/fluent-ctl +7 -0
- data/bin/fluent-debug +5 -0
- data/bin/fluent-gem +9 -0
- data/bin/fluent-plugin-config-format +5 -0
- data/bin/fluent-plugin-generate +5 -0
- data/bin/fluentd +15 -0
- data/code-of-conduct.md +3 -0
- data/docs/SECURITY_AUDIT.pdf +0 -0
- data/example/copy_roundrobin.conf +39 -0
- data/example/counter.conf +18 -0
- data/example/filter_stdout.conf +22 -0
- data/example/in_forward.conf +14 -0
- data/example/in_forward_client.conf +37 -0
- data/example/in_forward_shared_key.conf +15 -0
- data/example/in_forward_tls.conf +14 -0
- data/example/in_forward_users.conf +24 -0
- data/example/in_forward_workers.conf +21 -0
- data/example/in_http.conf +16 -0
- data/example/in_out_forward.conf +17 -0
- data/example/in_sample_blocks.conf +17 -0
- data/example/in_sample_with_compression.conf +23 -0
- data/example/in_syslog.conf +15 -0
- data/example/in_tail.conf +14 -0
- data/example/in_tcp.conf +13 -0
- data/example/in_udp.conf +13 -0
- data/example/logevents.conf +25 -0
- data/example/multi_filters.conf +61 -0
- data/example/out_copy.conf +20 -0
- data/example/out_exec_filter.conf +42 -0
- data/example/out_file.conf +13 -0
- data/example/out_forward.conf +35 -0
- data/example/out_forward_buf_file.conf +23 -0
- data/example/out_forward_client.conf +109 -0
- data/example/out_forward_heartbeat_none.conf +16 -0
- data/example/out_forward_sd.conf +17 -0
- data/example/out_forward_shared_key.conf +36 -0
- data/example/out_forward_tls.conf +18 -0
- data/example/out_forward_users.conf +65 -0
- data/example/out_null.conf +36 -0
- data/example/sd.yaml +8 -0
- data/example/secondary_file.conf +42 -0
- data/example/suppress_config_dump.conf +7 -0
- data/example/v0_12_filter.conf +78 -0
- data/example/v1_literal_example.conf +36 -0
- data/example/worker_section.conf +36 -0
- data/fluent.conf +139 -0
- data/lib/fluent/agent.rb +168 -0
- data/lib/fluent/capability.rb +87 -0
- data/lib/fluent/clock.rb +66 -0
- data/lib/fluent/command/binlog_reader.rb +244 -0
- data/lib/fluent/command/bundler_injection.rb +45 -0
- data/lib/fluent/command/ca_generate.rb +184 -0
- data/lib/fluent/command/cap_ctl.rb +174 -0
- data/lib/fluent/command/cat.rb +365 -0
- data/lib/fluent/command/ctl.rb +180 -0
- data/lib/fluent/command/debug.rb +103 -0
- data/lib/fluent/command/fluentd.rb +385 -0
- data/lib/fluent/command/plugin_config_formatter.rb +308 -0
- data/lib/fluent/command/plugin_generator.rb +365 -0
- data/lib/fluent/compat/call_super_mixin.rb +76 -0
- data/lib/fluent/compat/detach_process_mixin.rb +33 -0
- data/lib/fluent/compat/exec_util.rb +144 -0
- data/lib/fluent/compat/file_util.rb +54 -0
- data/lib/fluent/compat/filter.rb +68 -0
- data/lib/fluent/compat/formatter.rb +117 -0
- data/lib/fluent/compat/formatter_utils.rb +85 -0
- data/lib/fluent/compat/handle_tag_and_time_mixin.rb +62 -0
- data/lib/fluent/compat/handle_tag_name_mixin.rb +53 -0
- data/lib/fluent/compat/input.rb +49 -0
- data/lib/fluent/compat/output.rb +721 -0
- data/lib/fluent/compat/output_chain.rb +60 -0
- data/lib/fluent/compat/parser.rb +310 -0
- data/lib/fluent/compat/parser_utils.rb +40 -0
- data/lib/fluent/compat/propagate_default.rb +62 -0
- data/lib/fluent/compat/record_filter_mixin.rb +34 -0
- data/lib/fluent/compat/set_tag_key_mixin.rb +50 -0
- data/lib/fluent/compat/set_time_key_mixin.rb +69 -0
- data/lib/fluent/compat/socket_util.rb +165 -0
- data/lib/fluent/compat/string_util.rb +34 -0
- data/lib/fluent/compat/structured_format_mixin.rb +26 -0
- data/lib/fluent/compat/type_converter.rb +90 -0
- data/lib/fluent/config/basic_parser.rb +122 -0
- data/lib/fluent/config/configure_proxy.rb +424 -0
- data/lib/fluent/config/dsl.rb +152 -0
- data/lib/fluent/config/element.rb +265 -0
- data/lib/fluent/config/error.rb +44 -0
- data/lib/fluent/config/literal_parser.rb +305 -0
- data/lib/fluent/config/parser.rb +119 -0
- data/lib/fluent/config/section.rb +272 -0
- data/lib/fluent/config/types.rb +257 -0
- data/lib/fluent/config/v1_parser.rb +200 -0
- data/lib/fluent/config/yaml_parser/fluent_value.rb +47 -0
- data/lib/fluent/config/yaml_parser/loader.rb +124 -0
- data/lib/fluent/config/yaml_parser/parser.rb +179 -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 +89 -0
- data/lib/fluent/configurable.rb +202 -0
- data/lib/fluent/counter/base_socket.rb +44 -0
- data/lib/fluent/counter/client.rb +297 -0
- data/lib/fluent/counter/error.rb +86 -0
- data/lib/fluent/counter/mutex_hash.rb +163 -0
- data/lib/fluent/counter/server.rb +273 -0
- data/lib/fluent/counter/store.rb +205 -0
- data/lib/fluent/counter/validator.rb +145 -0
- data/lib/fluent/counter.rb +23 -0
- data/lib/fluent/daemon.rb +14 -0
- data/lib/fluent/daemonizer.rb +88 -0
- data/lib/fluent/engine.rb +269 -0
- data/lib/fluent/env.rb +45 -0
- data/lib/fluent/error.rb +37 -0
- data/lib/fluent/event.rb +369 -0
- data/lib/fluent/event_router.rb +315 -0
- data/lib/fluent/ext_monitor_require.rb +28 -0
- data/lib/fluent/file_wrapper.rb +137 -0
- data/lib/fluent/filter.rb +21 -0
- data/lib/fluent/fluent_log_event_router.rb +139 -0
- data/lib/fluent/formatter.rb +23 -0
- data/lib/fluent/input.rb +21 -0
- data/lib/fluent/label.rb +46 -0
- data/lib/fluent/load.rb +33 -0
- data/lib/fluent/log/console_adapter.rb +66 -0
- data/lib/fluent/log.rb +775 -0
- data/lib/fluent/match.rb +187 -0
- data/lib/fluent/mixin.rb +31 -0
- data/lib/fluent/msgpack_factory.rb +111 -0
- data/lib/fluent/oj_options.rb +61 -0
- data/lib/fluent/output.rb +29 -0
- data/lib/fluent/output_chain.rb +23 -0
- data/lib/fluent/parser.rb +23 -0
- data/lib/fluent/plugin/bare_output.rb +88 -0
- data/lib/fluent/plugin/base.rb +219 -0
- data/lib/fluent/plugin/buf_file.rb +256 -0
- data/lib/fluent/plugin/buf_file_single.rb +272 -0
- data/lib/fluent/plugin/buf_memory.rb +34 -0
- data/lib/fluent/plugin/buffer/chunk.rb +313 -0
- data/lib/fluent/plugin/buffer/file_chunk.rb +432 -0
- data/lib/fluent/plugin/buffer/file_single_chunk.rb +325 -0
- data/lib/fluent/plugin/buffer/memory_chunk.rb +91 -0
- data/lib/fluent/plugin/buffer.rb +1063 -0
- data/lib/fluent/plugin/compressable.rb +87 -0
- data/lib/fluent/plugin/exec_util.rb +22 -0
- data/lib/fluent/plugin/extractor.rb +132 -0
- data/lib/fluent/plugin/file_util.rb +22 -0
- data/lib/fluent/plugin/filter.rb +119 -0
- data/lib/fluent/plugin/filter_grep.rb +189 -0
- data/lib/fluent/plugin/filter_parser.rb +124 -0
- data/lib/fluent/plugin/filter_record_transformer.rb +325 -0
- data/lib/fluent/plugin/filter_stdout.rb +53 -0
- data/lib/fluent/plugin/formatter.rb +75 -0
- data/lib/fluent/plugin/formatter_csv.rb +92 -0
- data/lib/fluent/plugin/formatter_hash.rb +35 -0
- data/lib/fluent/plugin/formatter_json.rb +62 -0
- data/lib/fluent/plugin/formatter_ltsv.rb +44 -0
- data/lib/fluent/plugin/formatter_msgpack.rb +33 -0
- data/lib/fluent/plugin/formatter_out_file.rb +55 -0
- data/lib/fluent/plugin/formatter_single_value.rb +36 -0
- data/lib/fluent/plugin/formatter_stdout.rb +76 -0
- data/lib/fluent/plugin/formatter_tsv.rb +40 -0
- data/lib/fluent/plugin/in_debug_agent.rb +71 -0
- data/lib/fluent/plugin/in_dummy.rb +18 -0
- data/lib/fluent/plugin/in_exec.rb +124 -0
- data/lib/fluent/plugin/in_forward.rb +489 -0
- data/lib/fluent/plugin/in_gc_stat.rb +72 -0
- data/lib/fluent/plugin/in_http.rb +737 -0
- data/lib/fluent/plugin/in_monitor_agent.rb +406 -0
- data/lib/fluent/plugin/in_object_space.rb +93 -0
- data/lib/fluent/plugin/in_sample.rb +148 -0
- data/lib/fluent/plugin/in_syslog.rb +346 -0
- data/lib/fluent/plugin/in_tail/compatibility.rb +111 -0
- data/lib/fluent/plugin/in_tail/dir_watcher.rb +713 -0
- data/lib/fluent/plugin/in_tail/group_watch.rb +204 -0
- data/lib/fluent/plugin/in_tail/io_handler.rb +391 -0
- data/lib/fluent/plugin/in_tail/line_feeder.rb +230 -0
- data/lib/fluent/plugin/in_tail/position_file.rb +269 -0
- data/lib/fluent/plugin/in_tail/stat_watcher.rb +52 -0
- data/lib/fluent/plugin/in_tail/tail_watcher.rb +276 -0
- data/lib/fluent/plugin/in_tail/worker_pool.rb +298 -0
- data/lib/fluent/plugin/in_tail.rb +917 -0
- data/lib/fluent/plugin/in_tcp.rb +230 -0
- data/lib/fluent/plugin/in_udp.rb +101 -0
- data/lib/fluent/plugin/in_unix.rb +197 -0
- data/lib/fluent/plugin/input.rb +71 -0
- data/lib/fluent/plugin/metrics.rb +119 -0
- data/lib/fluent/plugin/metrics_local.rb +96 -0
- data/lib/fluent/plugin/multi_output.rb +179 -0
- data/lib/fluent/plugin/out_buffer.rb +40 -0
- data/lib/fluent/plugin/out_copy.rb +120 -0
- data/lib/fluent/plugin/out_exec.rb +105 -0
- data/lib/fluent/plugin/out_exec_filter.rb +319 -0
- data/lib/fluent/plugin/out_file.rb +371 -0
- data/lib/fluent/plugin/out_forward/ack_handler.rb +176 -0
- data/lib/fluent/plugin/out_forward/connection_manager.rb +131 -0
- data/lib/fluent/plugin/out_forward/error.rb +28 -0
- data/lib/fluent/plugin/out_forward/failure_detector.rb +84 -0
- data/lib/fluent/plugin/out_forward/handshake_protocol.rb +125 -0
- data/lib/fluent/plugin/out_forward/load_balancer.rb +114 -0
- data/lib/fluent/plugin/out_forward/socket_cache.rb +177 -0
- data/lib/fluent/plugin/out_forward.rb +865 -0
- data/lib/fluent/plugin/out_http.rb +419 -0
- data/lib/fluent/plugin/out_null.rb +74 -0
- data/lib/fluent/plugin/out_relabel.rb +32 -0
- data/lib/fluent/plugin/out_roundrobin.rb +84 -0
- data/lib/fluent/plugin/out_secondary_file.rb +148 -0
- data/lib/fluent/plugin/out_stdout.rb +81 -0
- data/lib/fluent/plugin/out_stream.rb +130 -0
- data/lib/fluent/plugin/output.rb +1688 -0
- data/lib/fluent/plugin/owned_by_mixin.rb +41 -0
- data/lib/fluent/plugin/parser.rb +274 -0
- data/lib/fluent/plugin/parser_apache.rb +28 -0
- data/lib/fluent/plugin/parser_apache2.rb +88 -0
- data/lib/fluent/plugin/parser_apache_error.rb +26 -0
- data/lib/fluent/plugin/parser_csv.rb +124 -0
- data/lib/fluent/plugin/parser_json.rb +118 -0
- data/lib/fluent/plugin/parser_ltsv.rb +49 -0
- data/lib/fluent/plugin/parser_msgpack.rb +72 -0
- data/lib/fluent/plugin/parser_multiline.rb +152 -0
- data/lib/fluent/plugin/parser_nginx.rb +28 -0
- data/lib/fluent/plugin/parser_none.rb +36 -0
- data/lib/fluent/plugin/parser_regexp.rb +68 -0
- data/lib/fluent/plugin/parser_syslog.rb +543 -0
- data/lib/fluent/plugin/parser_tsv.rb +47 -0
- data/lib/fluent/plugin/sd_file.rb +157 -0
- data/lib/fluent/plugin/sd_srv.rb +135 -0
- data/lib/fluent/plugin/sd_static.rb +58 -0
- data/lib/fluent/plugin/service_discovery.rb +65 -0
- data/lib/fluent/plugin/socket_util.rb +22 -0
- data/lib/fluent/plugin/storage.rb +84 -0
- data/lib/fluent/plugin/storage_local.rb +166 -0
- data/lib/fluent/plugin/string_util.rb +22 -0
- data/lib/fluent/plugin.rb +206 -0
- data/lib/fluent/plugin_helper/cert_option.rb +199 -0
- data/lib/fluent/plugin_helper/child_process.rb +369 -0
- data/lib/fluent/plugin_helper/compat_parameters.rb +343 -0
- data/lib/fluent/plugin_helper/counter.rb +51 -0
- data/lib/fluent/plugin_helper/event_emitter.rb +112 -0
- data/lib/fluent/plugin_helper/event_loop.rb +170 -0
- data/lib/fluent/plugin_helper/extract.rb +104 -0
- data/lib/fluent/plugin_helper/formatter.rb +147 -0
- data/lib/fluent/plugin_helper/http_server/app.rb +81 -0
- data/lib/fluent/plugin_helper/http_server/methods.rb +35 -0
- data/lib/fluent/plugin_helper/http_server/request.rb +53 -0
- data/lib/fluent/plugin_helper/http_server/router.rb +54 -0
- data/lib/fluent/plugin_helper/http_server/server.rb +116 -0
- data/lib/fluent/plugin_helper/http_server/ssl_context_builder.rb +41 -0
- data/lib/fluent/plugin_helper/http_server.rb +129 -0
- data/lib/fluent/plugin_helper/inject.rb +154 -0
- data/lib/fluent/plugin_helper/metrics.rb +136 -0
- data/lib/fluent/plugin_helper/parser.rb +147 -0
- data/lib/fluent/plugin_helper/record_accessor.rb +207 -0
- data/lib/fluent/plugin_helper/retry_state.rb +219 -0
- data/lib/fluent/plugin_helper/server.rb +840 -0
- data/lib/fluent/plugin_helper/service_discovery/manager.rb +146 -0
- data/lib/fluent/plugin_helper/service_discovery/round_robin_balancer.rb +43 -0
- data/lib/fluent/plugin_helper/service_discovery.rb +125 -0
- data/lib/fluent/plugin_helper/socket.rb +288 -0
- data/lib/fluent/plugin_helper/socket_option.rb +98 -0
- data/lib/fluent/plugin_helper/storage.rb +349 -0
- data/lib/fluent/plugin_helper/thread.rb +180 -0
- data/lib/fluent/plugin_helper/timer.rb +92 -0
- data/lib/fluent/plugin_helper.rb +75 -0
- data/lib/fluent/plugin_id.rb +93 -0
- data/lib/fluent/process.rb +22 -0
- data/lib/fluent/registry.rb +117 -0
- data/lib/fluent/root_agent.rb +468 -0
- data/lib/fluent/rpc.rb +95 -0
- data/lib/fluent/source_only_buffer_agent.rb +102 -0
- data/lib/fluent/static_config_analysis.rb +195 -0
- data/lib/fluent/supervisor.rb +1347 -0
- data/lib/fluent/system_config.rb +213 -0
- data/lib/fluent/test/base.rb +83 -0
- data/lib/fluent/test/driver/base.rb +231 -0
- data/lib/fluent/test/driver/base_owned.rb +83 -0
- data/lib/fluent/test/driver/base_owner.rb +135 -0
- data/lib/fluent/test/driver/event_feeder.rb +98 -0
- data/lib/fluent/test/driver/filter.rb +61 -0
- data/lib/fluent/test/driver/formatter.rb +30 -0
- data/lib/fluent/test/driver/input.rb +31 -0
- data/lib/fluent/test/driver/multi_output.rb +53 -0
- data/lib/fluent/test/driver/output.rb +102 -0
- data/lib/fluent/test/driver/parser.rb +30 -0
- data/lib/fluent/test/driver/storage.rb +30 -0
- data/lib/fluent/test/driver/test_event_router.rb +45 -0
- data/lib/fluent/test/filter_test.rb +77 -0
- data/lib/fluent/test/formatter_test.rb +65 -0
- data/lib/fluent/test/helpers.rb +138 -0
- data/lib/fluent/test/input_test.rb +174 -0
- data/lib/fluent/test/log.rb +79 -0
- data/lib/fluent/test/output_test.rb +156 -0
- data/lib/fluent/test/parser_test.rb +70 -0
- data/lib/fluent/test/startup_shutdown.rb +44 -0
- data/lib/fluent/test.rb +61 -0
- data/lib/fluent/time.rb +513 -0
- data/lib/fluent/timezone.rb +171 -0
- data/lib/fluent/tls.rb +105 -0
- data/lib/fluent/unique_id.rb +39 -0
- data/lib/fluent/variable_store.rb +40 -0
- data/lib/fluent/version.rb +21 -0
- data/lib/fluent/win32api.rb +38 -0
- data/lib/fluent/winsvc.rb +130 -0
- data/templates/new_gem/.gitignore +8 -0
- data/templates/new_gem/Gemfile +3 -0
- data/templates/new_gem/README.md.erb +43 -0
- data/templates/new_gem/Rakefile +13 -0
- data/templates/new_gem/fluent-plugin.gemspec.erb +28 -0
- data/templates/new_gem/lib/fluent/plugin/filter.rb.erb +14 -0
- data/templates/new_gem/lib/fluent/plugin/formatter.rb.erb +14 -0
- data/templates/new_gem/lib/fluent/plugin/input.rb.erb +11 -0
- data/templates/new_gem/lib/fluent/plugin/output.rb.erb +11 -0
- data/templates/new_gem/lib/fluent/plugin/parser.rb.erb +15 -0
- data/templates/new_gem/lib/fluent/plugin/storage.rb.erb +40 -0
- data/templates/new_gem/test/helper.rb.erb +7 -0
- data/templates/new_gem/test/plugin/test_filter.rb.erb +18 -0
- data/templates/new_gem/test/plugin/test_formatter.rb.erb +18 -0
- data/templates/new_gem/test/plugin/test_input.rb.erb +18 -0
- data/templates/new_gem/test/plugin/test_output.rb.erb +18 -0
- data/templates/new_gem/test/plugin/test_parser.rb.erb +18 -0
- data/templates/new_gem/test/plugin/test_storage.rb.erb +18 -0
- data/templates/plugin_config_formatter/param.md-compact.erb +25 -0
- data/templates/plugin_config_formatter/param.md-table.erb +10 -0
- data/templates/plugin_config_formatter/param.md.erb +34 -0
- data/templates/plugin_config_formatter/section.md.erb +12 -0
- metadata +996 -0
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Fluentd
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
require 'cool.io'
|
|
18
|
+
|
|
19
|
+
require 'fluent/plugin/input'
|
|
20
|
+
require 'fluent/config/error'
|
|
21
|
+
require 'fluent/event'
|
|
22
|
+
require 'fluent/plugin/buffer'
|
|
23
|
+
require 'fluent/ext_monitor_require'
|
|
24
|
+
require 'fluent/plugin/parser_multiline'
|
|
25
|
+
require 'fluent/variable_store'
|
|
26
|
+
require 'fluent/capability'
|
|
27
|
+
require 'fluent/plugin/in_tail/position_file'
|
|
28
|
+
require 'fluent/plugin/in_tail/group_watch'
|
|
29
|
+
require 'fluent/plugin/in_tail/stat_watcher'
|
|
30
|
+
require 'fluent/plugin/in_tail/dir_watcher'
|
|
31
|
+
require 'fluent/plugin/in_tail/io_handler'
|
|
32
|
+
require 'fluent/plugin/in_tail/tail_watcher'
|
|
33
|
+
require 'fluent/plugin/in_tail/line_feeder'
|
|
34
|
+
require 'fluent/plugin/in_tail/compatibility'
|
|
35
|
+
require 'fluent/plugin/in_tail/worker_pool'
|
|
36
|
+
require 'fluent/file_wrapper'
|
|
37
|
+
|
|
38
|
+
module Fluent::Plugin
|
|
39
|
+
class TailInput < Fluent::Plugin::Input
|
|
40
|
+
include GroupWatch
|
|
41
|
+
include Compatibility
|
|
42
|
+
include DirWatcher
|
|
43
|
+
|
|
44
|
+
Fluent::Plugin.register_input('tail', self)
|
|
45
|
+
|
|
46
|
+
helpers :timer, :event_loop, :parser, :compat_parameters
|
|
47
|
+
|
|
48
|
+
RESERVED_CHARS = ['/', '*', '%'].freeze
|
|
49
|
+
MetricsInfo = Struct.new(:opened, :closed, :rotated, :throttled, :tracked)
|
|
50
|
+
|
|
51
|
+
class WatcherSetupError < StandardError
|
|
52
|
+
def initialize(msg)
|
|
53
|
+
@message = msg
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def to_s
|
|
57
|
+
@message
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def initialize
|
|
62
|
+
super
|
|
63
|
+
@paths = []
|
|
64
|
+
@tails = {}
|
|
65
|
+
@tails_rotate_wait = {}
|
|
66
|
+
@pf_file = nil
|
|
67
|
+
@watcher_refresh_monitor = Monitor.new
|
|
68
|
+
@pf = nil
|
|
69
|
+
@ignore_list = []
|
|
70
|
+
@shutdown_start_time = nil
|
|
71
|
+
@metrics = nil
|
|
72
|
+
@startup = true
|
|
73
|
+
@capability = Fluent::Capability.new(:current_process)
|
|
74
|
+
initialize_dir_watchers
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
desc 'The paths to read. Multiple paths can be specified, separated by comma.'
|
|
78
|
+
config_param :path, :string
|
|
79
|
+
desc 'path delimiter used for splitting path config'
|
|
80
|
+
config_param :path_delimiter, :string, default: ','
|
|
81
|
+
desc 'Choose using glob patterns. Adding capabilities to handle [] and ?, and {}.'
|
|
82
|
+
config_param :glob_policy, :enum, list: [:backward_compatible, :extended, :always], default: :backward_compatible
|
|
83
|
+
desc 'The tag of the event.'
|
|
84
|
+
config_param :tag, :string
|
|
85
|
+
desc 'The paths to exclude the files from watcher list.'
|
|
86
|
+
config_param :exclude_path, :array, default: []
|
|
87
|
+
desc 'Specify interval to keep reference to old file when rotate a file.'
|
|
88
|
+
config_param :rotate_wait, :time, default: 5
|
|
89
|
+
desc 'Fluentd will record the position it last read into this file.'
|
|
90
|
+
config_param :pos_file, :string, default: nil
|
|
91
|
+
desc 'The cleanup interval of pos file'
|
|
92
|
+
config_param :pos_file_compaction_interval, :time, default: nil
|
|
93
|
+
desc 'Start to read the logs from the head of file, not bottom.'
|
|
94
|
+
config_param :read_from_head, :bool, default: false
|
|
95
|
+
# When the program deletes log file and re-creates log file with same filename after passed refresh_interval,
|
|
96
|
+
# in_tail may raise a pos_file related error. This is a known issue but there is no such program on production.
|
|
97
|
+
# If we find such program / application, we will fix the problem.
|
|
98
|
+
desc 'The interval of refreshing the list of watch file.'
|
|
99
|
+
config_param :refresh_interval, :time, default: 60
|
|
100
|
+
desc 'The number of reading lines at each IO.'
|
|
101
|
+
config_param :read_lines_limit, :integer, default: 1000
|
|
102
|
+
desc 'The number of threads used to parse tailed files. Set to 1 to keep parsing synchronous; 2 or more enables worker parsing. The default is 1.'
|
|
103
|
+
config_param :num_threads, :integer, default: 1
|
|
104
|
+
desc 'The number of reading bytes per second'
|
|
105
|
+
config_param :read_bytes_limit_per_second, :size, default: -1
|
|
106
|
+
desc 'The interval of flushing the buffer for multiline format'
|
|
107
|
+
config_param :multiline_flush_interval, :time, default: nil
|
|
108
|
+
desc 'Enable the option to emit unmatched lines.'
|
|
109
|
+
config_param :emit_unmatched_lines, :bool, default: false
|
|
110
|
+
desc 'Enable the additional watch timer.'
|
|
111
|
+
config_param :enable_watch_timer, :bool, default: true
|
|
112
|
+
desc 'Enable stat watchers for individual files, using inotify where available.'
|
|
113
|
+
config_param :enable_stat_watcher, :bool, default: true
|
|
114
|
+
desc 'Watch directories to discover files before refresh_interval. May use inotify regardless of enable_stat_watcher.'
|
|
115
|
+
config_param :enable_dir_watcher, :bool, default: false
|
|
116
|
+
desc 'Maximum number of directories watched at once. When exceeded, rotate the watched subset at each periodic refresh.'
|
|
117
|
+
config_param :dir_watcher_limit, :integer, default: 100
|
|
118
|
+
desc 'The encoding of the input.'
|
|
119
|
+
config_param :encoding, :string, default: nil
|
|
120
|
+
desc "The original encoding of the input. If set, in_tail tries to encode string from this to 'encoding'. Must be set with 'encoding'. "
|
|
121
|
+
config_param :from_encoding, :string, default: nil
|
|
122
|
+
desc 'Add the log path being tailed to records. Specify the field name to be used.'
|
|
123
|
+
config_param :path_key, :string, default: nil
|
|
124
|
+
desc 'Open and close the file on every update instead of leaving it open until it gets rotated.'
|
|
125
|
+
config_param :open_on_every_update, :bool, default: false
|
|
126
|
+
desc 'Limit the watching files that the modification time is within the specified time range (when use \'*\' in path).'
|
|
127
|
+
config_param :limit_recently_modified, :time, default: nil
|
|
128
|
+
desc 'Enable the option to skip the refresh of watching list on startup.'
|
|
129
|
+
config_param :skip_refresh_on_startup, :bool, default: false
|
|
130
|
+
desc 'Ignore repeated permission error logs'
|
|
131
|
+
config_param :ignore_repeated_permission_error, :bool, default: false
|
|
132
|
+
desc 'Format path with the specified timezone'
|
|
133
|
+
config_param :path_timezone, :string, default: nil
|
|
134
|
+
desc 'Follow inodes instead of following file names. Guarantees more stable delivery and allows to use * in path pattern with rotating files'
|
|
135
|
+
config_param :follow_inodes, :bool, default: false
|
|
136
|
+
desc 'Maximum length of line. The longer line is just skipped.'
|
|
137
|
+
config_param :max_line_size, :size, default: nil
|
|
138
|
+
|
|
139
|
+
config_section :parse, required: false, multi: true, init: true, param_name: :parser_configs do
|
|
140
|
+
config_argument :usage, :string, default: 'in_tail_parser'
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
attr_reader :paths
|
|
144
|
+
|
|
145
|
+
def configure(conf)
|
|
146
|
+
@variable_store = Fluent::VariableStore.fetch_or_build(:in_tail)
|
|
147
|
+
compat_parameters_convert(conf, :parser)
|
|
148
|
+
parser_config = conf.elements('parse').first
|
|
149
|
+
unless parser_config
|
|
150
|
+
raise Fluent::ConfigError, "<parse> section is required."
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
(1..Fluent::Plugin::MultilineParser::FORMAT_MAX_NUM).each do |n|
|
|
154
|
+
parser_config["format#{n}"] = conf["format#{n}"] if conf["format#{n}"]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
parser_config['unmatched_lines'] = conf['emit_unmatched_lines']
|
|
158
|
+
|
|
159
|
+
super
|
|
160
|
+
|
|
161
|
+
if @dir_watcher_limit <= 0
|
|
162
|
+
raise Fluent::ConfigError, "dir_watcher_limit must be greater than zero"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
if !@enable_watch_timer && !@enable_stat_watcher
|
|
166
|
+
raise Fluent::ConfigError, "either of enable_watch_timer or enable_stat_watcher must be true"
|
|
167
|
+
end
|
|
168
|
+
raise Fluent::ConfigError, "'num_threads' must be greater than 0" unless @num_threads.positive?
|
|
169
|
+
|
|
170
|
+
if @enable_dir_watcher && !@enable_stat_watcher
|
|
171
|
+
log.info(
|
|
172
|
+
'enable_stat_watcher is disabled for files,' +
|
|
173
|
+
' but enable_dir_watcher still uses directory stat watchers' +
|
|
174
|
+
' and may use inotify'
|
|
175
|
+
)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
if @glob_policy == :always && @path_delimiter == ','
|
|
179
|
+
raise Fluent::ConfigError, "cannot use glob_policy as always with the default path_delimiter: `,\""
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
if @glob_policy == :extended && /\{.*,.*\}/.match?(@path) && extended_glob_pattern(@path)
|
|
183
|
+
raise Fluent::ConfigError, "cannot include curly braces with glob patterns in `#{@path}\". Use glob_policy always instead."
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
if RESERVED_CHARS.include?(@path_delimiter)
|
|
187
|
+
rc = RESERVED_CHARS.join(', ')
|
|
188
|
+
raise Fluent::ConfigError, "#{rc} are reserved words: #{@path_delimiter}"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
@paths = @path.split(@path_delimiter).map(&:strip).uniq
|
|
192
|
+
if @paths.empty?
|
|
193
|
+
raise Fluent::ConfigError, "tail: 'path' parameter is required on tail input"
|
|
194
|
+
end
|
|
195
|
+
if @path_timezone
|
|
196
|
+
Fluent::Timezone.validate!(@path_timezone)
|
|
197
|
+
@path_formatters = @paths.map{|path| [path, Fluent::Timezone.formatter(@path_timezone, path)]}.to_h
|
|
198
|
+
@exclude_path_formatters = @exclude_path.map{|path| [path, Fluent::Timezone.formatter(@path_timezone, path)]}.to_h
|
|
199
|
+
end
|
|
200
|
+
check_dir_permission unless Fluent.windows?
|
|
201
|
+
|
|
202
|
+
# TODO: Use plugin_root_dir and storage plugin to store positions if available
|
|
203
|
+
if @pos_file
|
|
204
|
+
if @variable_store.key?(@pos_file) && !called_in_test?
|
|
205
|
+
plugin_id_using_this_path = @variable_store[@pos_file]
|
|
206
|
+
raise Fluent::ConfigError, "Other 'in_tail' plugin already use same pos_file path: plugin_id = #{plugin_id_using_this_path}, pos_file path = #{@pos_file}"
|
|
207
|
+
end
|
|
208
|
+
@variable_store[@pos_file] = self.plugin_id
|
|
209
|
+
else
|
|
210
|
+
if @follow_inodes
|
|
211
|
+
raise Fluent::ConfigError, "Can't follow inodes without pos_file configuration parameter"
|
|
212
|
+
end
|
|
213
|
+
log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
|
|
214
|
+
log.warn "this parameter is highly recommended to save the position to resume tailing."
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
configure_tag
|
|
218
|
+
configure_encoding
|
|
219
|
+
|
|
220
|
+
@multiline_mode = parser_config["@type"].include?("multiline")
|
|
221
|
+
@file_perm = system_config.file_permission || Fluent::DEFAULT_FILE_PERMISSION
|
|
222
|
+
@dir_perm = system_config.dir_permission || Fluent::DEFAULT_DIR_PERMISSION
|
|
223
|
+
# parser is already created by parser helper
|
|
224
|
+
@parser = parser_create(usage: parser_config['usage'] || @parser_configs.first.usage)
|
|
225
|
+
if @read_bytes_limit_per_second > 0
|
|
226
|
+
if !@enable_watch_timer
|
|
227
|
+
raise Fluent::ConfigError, "Need to enable watch timer when using log throttling feature"
|
|
228
|
+
end
|
|
229
|
+
min_bytes = TailWatcher::IOHandler::BYTES_TO_READ
|
|
230
|
+
if @read_bytes_limit_per_second < min_bytes
|
|
231
|
+
log.warn "Should specify greater equal than #{min_bytes}. Use #{min_bytes} for read_bytes_limit_per_second"
|
|
232
|
+
@read_bytes_limit_per_second = min_bytes
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
opened_file_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_opened_total", help_text: "Total number of opened files")
|
|
237
|
+
closed_file_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_closed_total", help_text: "Total number of closed files")
|
|
238
|
+
rotated_file_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_rotated_total", help_text: "Total number of rotated files")
|
|
239
|
+
throttling_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_throttled_total", help_text: "Total number of times throttling occurs per file when throttling enabled")
|
|
240
|
+
# The metrics for currently tracking files. Since the value may decrease, it cannot be represented using the counter type, so 'prefer_gauge: true' is used instead.
|
|
241
|
+
tracked_file_metrics = metrics_create(namespace: "fluentd", subsystem: "input", name: "files_tracked_count", help_text: "Number of tracked files", prefer_gauge: true)
|
|
242
|
+
|
|
243
|
+
@metrics = MetricsInfo.new(opened_file_metrics, closed_file_metrics, rotated_file_metrics, throttling_metrics, tracked_file_metrics)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def check_dir_permission
|
|
247
|
+
expand_paths_raw.select { |path|
|
|
248
|
+
not File.exist?(path)
|
|
249
|
+
}.each { |path|
|
|
250
|
+
inaccessible_dir = Pathname.new(File.expand_path(path))
|
|
251
|
+
.ascend
|
|
252
|
+
.reverse_each
|
|
253
|
+
.find { |p| p.directory? && !p.executable? }
|
|
254
|
+
if inaccessible_dir
|
|
255
|
+
log.warn "Skip #{path} because '#{inaccessible_dir}' lacks execute permission."
|
|
256
|
+
end
|
|
257
|
+
}
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def configure_tag
|
|
261
|
+
if @tag.index('*')
|
|
262
|
+
@tag_prefix, @tag_suffix = @tag.split('*')
|
|
263
|
+
@tag_prefix ||= ''
|
|
264
|
+
@tag_suffix ||= ''
|
|
265
|
+
else
|
|
266
|
+
@tag_prefix = nil
|
|
267
|
+
@tag_suffix = nil
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def configure_encoding
|
|
272
|
+
unless @encoding
|
|
273
|
+
if @from_encoding
|
|
274
|
+
raise Fluent::ConfigError, "tail: 'from_encoding' parameter must be specified with 'encoding' parameter."
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
@encoding = parse_encoding_param(@encoding) if @encoding
|
|
279
|
+
@from_encoding = parse_encoding_param(@from_encoding) if @from_encoding
|
|
280
|
+
if @encoding && (@encoding == @from_encoding)
|
|
281
|
+
log.warn "'encoding' and 'from_encoding' are same encoding. No effect"
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def parse_encoding_param(encoding_name)
|
|
286
|
+
begin
|
|
287
|
+
Encoding.find(encoding_name) if encoding_name
|
|
288
|
+
rescue ArgumentError => e
|
|
289
|
+
raise Fluent::ConfigError, e.message
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def start
|
|
294
|
+
super
|
|
295
|
+
|
|
296
|
+
@line_feeder = build_line_feeder
|
|
297
|
+
configure_worker_mode if @num_threads > 1
|
|
298
|
+
|
|
299
|
+
if @pos_file
|
|
300
|
+
pos_file_dir = File.dirname(@pos_file)
|
|
301
|
+
FileUtils.mkdir_p(pos_file_dir, mode: @dir_perm) unless Dir.exist?(pos_file_dir)
|
|
302
|
+
@pf_file = File.open(@pos_file, File::RDWR|File::CREAT|File::BINARY, @file_perm)
|
|
303
|
+
@pf_file.sync = true
|
|
304
|
+
@pf = PositionFile.load(@pf_file, @follow_inodes, expand_paths, logger: log)
|
|
305
|
+
|
|
306
|
+
if @pos_file_compaction_interval
|
|
307
|
+
timer_execute(:in_tail_refresh_compact_pos_file, @pos_file_compaction_interval) do
|
|
308
|
+
log.info('Clean up the pos file')
|
|
309
|
+
@pf.try_compact
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
refresh_watchers unless @skip_refresh_on_startup
|
|
315
|
+
update_dir_watchers
|
|
316
|
+
timer_execute(:in_tail_refresh_watchers, @refresh_interval, &method(:refresh_watchers_on_timer))
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def stop
|
|
320
|
+
if @variable_store
|
|
321
|
+
@variable_store.delete(@pos_file)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
super
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def shutdown
|
|
328
|
+
@shutdown_start_time = Fluent::Clock.now
|
|
329
|
+
# Initial reads run as part of a refresh. Signal their timeout before
|
|
330
|
+
# waiting for the refresh, so a large file cannot delay shutdown forever.
|
|
331
|
+
(@tails.values + @tails_rotate_wait.keys).uniq.each do |tw|
|
|
332
|
+
tw.ready_to_shutdown(@shutdown_start_time)
|
|
333
|
+
end
|
|
334
|
+
# A refresh or a rotation handling may yield during file operations. Let it
|
|
335
|
+
# finish before detaching watchers or closing the position file.
|
|
336
|
+
@watcher_refresh_monitor.synchronize {}
|
|
337
|
+
# The event loop is still running at this point, so the watchers added to
|
|
338
|
+
# detect new files must be detached before the position file is closed:
|
|
339
|
+
# a change noticed by them would ask for refreshing the watchers, and
|
|
340
|
+
# #refresh_watchers writes the position file.
|
|
341
|
+
detach_dir_watchers
|
|
342
|
+
detach_refresh_timer
|
|
343
|
+
# during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
|
|
344
|
+
stop_watchers(existence_path, immediate: true, remove_watcher: false)
|
|
345
|
+
@tails_rotate_wait.dup.each do |tw, rotation|
|
|
346
|
+
event_loop_detach(rotation[:timer]) if @worker_pool
|
|
347
|
+
detach_watcher(tw, rotation[:ino], false)
|
|
348
|
+
end
|
|
349
|
+
shutdown_worker_pool
|
|
350
|
+
@pf_file.close if @pf_file && !@worker_pool
|
|
351
|
+
|
|
352
|
+
super
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def close
|
|
356
|
+
super
|
|
357
|
+
@worker_completion_watcher&.close
|
|
358
|
+
# close file handles after all threads stopped (in #close of thread plugin helper)
|
|
359
|
+
# It may be because we need to wait IOHandler.ready_to_shutdown()
|
|
360
|
+
close_watcher_handles
|
|
361
|
+
@pf_file.close if @pf_file && !@pf_file.closed?
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def have_read_capability?
|
|
365
|
+
@capability.have_capability?(:effective, :dac_read_search) ||
|
|
366
|
+
@capability.have_capability?(:effective, :dac_override)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# TailInput builds its LineFeeder in start, after the configure of a plugin
|
|
370
|
+
# which inherits TailInput is done, so that the parameters set by that
|
|
371
|
+
# plugin are used.
|
|
372
|
+
def build_line_feeder
|
|
373
|
+
LineFeeder.new(
|
|
374
|
+
parser: @parser,
|
|
375
|
+
router_provider: method(:router),
|
|
376
|
+
log: log,
|
|
377
|
+
tag: @tag,
|
|
378
|
+
tag_prefix: @tag_prefix,
|
|
379
|
+
tag_suffix: @tag_suffix,
|
|
380
|
+
path_key: @path_key,
|
|
381
|
+
emit_unmatched_lines: @emit_unmatched_lines,
|
|
382
|
+
multiline_mode: @multiline_mode,
|
|
383
|
+
parse_handler: method(@multiline_mode ? :parse_multilines : :parse_singleline),
|
|
384
|
+
convert_handler: method(:convert_line_to_event),
|
|
385
|
+
flush_handler: method(:flush_buffer),
|
|
386
|
+
)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def setup_watcher(target_info, pe)
|
|
390
|
+
# A plugin may build a watcher before start. The LineFeeder of that
|
|
391
|
+
# moment has the same parameters, because the configure of every plugin
|
|
392
|
+
# is done before the watchers are built.
|
|
393
|
+
@line_feeder ||= build_line_feeder
|
|
394
|
+
file_feed = @line_feeder.new_file_feed(flush_interval: @multiline_flush_interval)
|
|
395
|
+
read_from_head = !@startup || @read_from_head
|
|
396
|
+
tw = TailWatcher.new(target_info, pe, log, read_from_head, @follow_inodes, method(:update_watcher), file_feed, method(:io_handler), @metrics)
|
|
397
|
+
tw.ready_to_shutdown(@shutdown_start_time) if @shutdown_start_time
|
|
398
|
+
|
|
399
|
+
if @enable_watch_timer
|
|
400
|
+
tt = TimerTrigger.new(1, log) { tw.on_notify }
|
|
401
|
+
tw.register_watcher(tt)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
if @enable_stat_watcher
|
|
405
|
+
tt = StatWatcher.new(target_info.path, log) { tw.on_notify }
|
|
406
|
+
tw.register_watcher(tt)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
tw.watchers.each do |watcher|
|
|
410
|
+
event_loop_attach(watcher)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
tw.group_watcher = add_path_to_group_watcher(target_info.path, tw)
|
|
414
|
+
|
|
415
|
+
tw
|
|
416
|
+
rescue => e
|
|
417
|
+
if tw
|
|
418
|
+
tw.watchers.each do |watcher|
|
|
419
|
+
event_loop_detach(watcher)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
tw.detach(@shutdown_start_time)
|
|
423
|
+
tw.close
|
|
424
|
+
end
|
|
425
|
+
raise e
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def construct_watcher(target_info)
|
|
429
|
+
path = target_info.path
|
|
430
|
+
|
|
431
|
+
# The file might be rotated or removed after collecting paths, so check inode again here.
|
|
432
|
+
begin
|
|
433
|
+
target_info.ino = Fluent::FileWrapper.stat(path).ino
|
|
434
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
435
|
+
log.warn "stat() for #{path} failed. Continuing without tailing it."
|
|
436
|
+
return
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
pe = nil
|
|
440
|
+
if @pf
|
|
441
|
+
pe = @pf[target_info]
|
|
442
|
+
pe.update(target_info.ino, 0) if @read_from_head && pe.read_inode.zero?
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
begin
|
|
446
|
+
tw = setup_watcher(target_info, pe)
|
|
447
|
+
rescue WatcherSetupError => e
|
|
448
|
+
log.warn "Skip #{path} because unexpected setup error happens: #{e}"
|
|
449
|
+
return
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
@tails[path] = tw
|
|
453
|
+
tw.ready_to_shutdown(@shutdown_start_time) if @shutdown_start_time
|
|
454
|
+
tw.on_notify
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def start_watchers(targets_info)
|
|
458
|
+
targets_info.each_value {|target_info|
|
|
459
|
+
construct_watcher(target_info)
|
|
460
|
+
break if before_shutdown?
|
|
461
|
+
}
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def stop_watchers(targets_info, immediate: false, unwatched: false, remove_watcher: true)
|
|
465
|
+
targets_info.each_value { |target_info|
|
|
466
|
+
if remove_watcher
|
|
467
|
+
tw = @tails.delete(target_info.path)
|
|
468
|
+
else
|
|
469
|
+
tw = @tails[target_info.path]
|
|
470
|
+
end
|
|
471
|
+
if tw
|
|
472
|
+
tw.unwatched = unwatched
|
|
473
|
+
if immediate
|
|
474
|
+
detach_watcher(tw, target_info.ino, false)
|
|
475
|
+
else
|
|
476
|
+
detach_watcher_after_rotate_wait(tw, target_info.ino)
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
}
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def close_watcher_handles
|
|
483
|
+
if @worker_pool
|
|
484
|
+
close_worker_watcher_handles
|
|
485
|
+
return
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
@tails.keys.each do |path|
|
|
489
|
+
tw = @tails.delete(path)
|
|
490
|
+
if tw
|
|
491
|
+
tw.close
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
@tails_rotate_wait.keys.each do |tw|
|
|
495
|
+
tw.close
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def update_watcher(tail_watcher, pe, new_inode)
|
|
500
|
+
@watcher_refresh_monitor.synchronize do
|
|
501
|
+
return if @shutdown_start_time
|
|
502
|
+
|
|
503
|
+
update_watcher_raw(tail_watcher, pe, new_inode)
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
# refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
|
|
508
|
+
def update_watcher_raw(tail_watcher, pe, new_inode)
|
|
509
|
+
# TODO we should use another callback for this.
|
|
510
|
+
# To suppress impact to existing logics, limit the case to `@follow_inodes`.
|
|
511
|
+
# We may not need `@follow_inodes` condition.
|
|
512
|
+
if @follow_inodes && new_inode.nil?
|
|
513
|
+
# nil inode means the file disappeared, so we only need to stop it.
|
|
514
|
+
@tails.delete(tail_watcher.path)
|
|
515
|
+
detach_watcher_after_rotate_wait(tail_watcher, pe.read_inode)
|
|
516
|
+
return
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
path = tail_watcher.path
|
|
520
|
+
|
|
521
|
+
log.info("detected rotation of #{path}; waiting #{@rotate_wait} seconds")
|
|
522
|
+
|
|
523
|
+
if @pf
|
|
524
|
+
pe_inode = pe.read_inode
|
|
525
|
+
target_info_from_position_entry = TargetInfo.new(path, pe_inode)
|
|
526
|
+
unless pe_inode == @pf[target_info_from_position_entry].read_inode
|
|
527
|
+
log.warn "Skip update_watcher because watcher has been already updated by other inotify event",
|
|
528
|
+
path: path, inode: pe.read_inode, inode_in_pos_file: @pf[target_info_from_position_entry].read_inode
|
|
529
|
+
return
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
new_target_info = TargetInfo.new(path, new_inode)
|
|
534
|
+
|
|
535
|
+
if @follow_inodes
|
|
536
|
+
new_position_entry = @pf[new_target_info]
|
|
537
|
+
# If `refresh_watcher` find the new file before, this will not be zero.
|
|
538
|
+
# In this case, only we have to do is detaching the current tail_watcher.
|
|
539
|
+
if new_position_entry.read_inode == 0
|
|
540
|
+
@tails[path] = setup_watcher(new_target_info, new_position_entry)
|
|
541
|
+
@tails[path].ready_to_shutdown(@shutdown_start_time) if @shutdown_start_time
|
|
542
|
+
@tails[path].on_notify
|
|
543
|
+
end
|
|
544
|
+
else
|
|
545
|
+
@tails[path] = setup_watcher(new_target_info, pe)
|
|
546
|
+
@tails[path].ready_to_shutdown(@shutdown_start_time) if @shutdown_start_time
|
|
547
|
+
@tails[path].on_notify
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
detach_watcher_after_rotate_wait(tail_watcher, pe.read_inode)
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def detach_watcher(tw, ino, close_io = true)
|
|
554
|
+
if @follow_inodes && tw.ino != ino
|
|
555
|
+
log.warn("detach_watcher could be detaching an unexpected tail_watcher with a different ino.",
|
|
556
|
+
path: tw.path, actual_ino_in_tw: tw.ino, expect_ino_to_close: ino)
|
|
557
|
+
end
|
|
558
|
+
tw.watchers.each do |watcher|
|
|
559
|
+
event_loop_detach(watcher)
|
|
560
|
+
end
|
|
561
|
+
tw.detach(@shutdown_start_time)
|
|
562
|
+
|
|
563
|
+
# The final read in detach may itself submit a worker batch.
|
|
564
|
+
defer_worker_completion = tw.pending? || @worker_waiting_watchers&.include?(tw)
|
|
565
|
+
if close_io && defer_worker_completion
|
|
566
|
+
tw.defer_close
|
|
567
|
+
@worker_detached_watchers << tw unless @worker_detached_watchers.include?(tw)
|
|
568
|
+
end
|
|
569
|
+
tw.close if close_io
|
|
570
|
+
|
|
571
|
+
# A watcher waiting for `rotate_wait` cannot read once its path leaves the group watcher.
|
|
572
|
+
tw.group_watcher&.delete(tw.path, tw)
|
|
573
|
+
|
|
574
|
+
if @pf && tw.unwatched && (@follow_inodes || !@tails[tw.path])
|
|
575
|
+
target_info = TargetInfo.new(tw.path, ino)
|
|
576
|
+
if defer_worker_completion
|
|
577
|
+
@worker_deferred_unwatch[tw] = target_info
|
|
578
|
+
else
|
|
579
|
+
@pf.unwatch(target_info)
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
def throttling_is_enabled?(tw)
|
|
585
|
+
return true if @read_bytes_limit_per_second > 0
|
|
586
|
+
return true if tw.group_watcher && tw.group_watcher.limit >= 0
|
|
587
|
+
false
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def detach_watcher_after_rotate_wait(tw, ino)
|
|
591
|
+
# Call event_loop_attach/event_loop_detach is high-cost for short-live object.
|
|
592
|
+
# If this has a problem with large number of files, use @_event_loop directly instead of timer_execute.
|
|
593
|
+
if @open_on_every_update
|
|
594
|
+
# Detach now because it's already closed, waiting it doesn't make sense.
|
|
595
|
+
detach_watcher(tw, ino)
|
|
596
|
+
return
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
return if @tails_rotate_wait[tw]
|
|
600
|
+
|
|
601
|
+
if throttling_is_enabled?(tw)
|
|
602
|
+
# When the throttling feature is enabled, it might not reach EOF yet.
|
|
603
|
+
# Should ensure to read all contents before closing it, with keeping throttling.
|
|
604
|
+
start_time_to_wait = Fluent::Clock.now
|
|
605
|
+
timer = timer_execute(:in_tail_close_watcher, 1, repeat: true) do
|
|
606
|
+
# Without the watch timer, nothing else notifies a watcher whose path has been rotated away.
|
|
607
|
+
unless @enable_watch_timer
|
|
608
|
+
begin
|
|
609
|
+
tw.read_more
|
|
610
|
+
rescue => e
|
|
611
|
+
log.error e.to_s
|
|
612
|
+
log.error_backtrace
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
elapsed = Fluent::Clock.now - start_time_to_wait
|
|
616
|
+
if tw.eof? && elapsed >= @rotate_wait
|
|
617
|
+
timer.detach
|
|
618
|
+
@tails_rotate_wait.delete(tw)
|
|
619
|
+
detach_watcher(tw, ino)
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
@tails_rotate_wait[tw] = { ino: ino, timer: timer }
|
|
623
|
+
else
|
|
624
|
+
# when the throttling feature isn't enabled, just wait @rotate_wait
|
|
625
|
+
timer = timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
|
|
626
|
+
@tails_rotate_wait.delete(tw)
|
|
627
|
+
detach_watcher(tw, ino)
|
|
628
|
+
end
|
|
629
|
+
@tails_rotate_wait[tw] = { ino: ino, timer: timer }
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def statistics
|
|
634
|
+
stats = super
|
|
635
|
+
|
|
636
|
+
stats = {
|
|
637
|
+
'input' => stats["input"].merge({
|
|
638
|
+
'opened_file_count' => @metrics.opened.get,
|
|
639
|
+
'closed_file_count' => @metrics.closed.get,
|
|
640
|
+
'rotated_file_count' => @metrics.rotated.get,
|
|
641
|
+
'throttled_log_count' => @metrics.throttled.get,
|
|
642
|
+
'tracked_file_count' => @metrics.tracked.get,
|
|
643
|
+
})
|
|
644
|
+
}
|
|
645
|
+
if @worker_pool
|
|
646
|
+
stats['input'].merge!(
|
|
647
|
+
'worker_pending_batch_count' => @worker_metrics[:pending_batches].get,
|
|
648
|
+
'worker_pending_bytes' => @worker_metrics[:pending_bytes].get,
|
|
649
|
+
'worker_parse_error_count' => @worker_metrics[:parse_errors].get,
|
|
650
|
+
)
|
|
651
|
+
end
|
|
652
|
+
stats
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
private
|
|
656
|
+
|
|
657
|
+
def io_handler(watcher, path)
|
|
658
|
+
opts = {
|
|
659
|
+
path: path,
|
|
660
|
+
log: log,
|
|
661
|
+
read_lines_limit: @read_lines_limit,
|
|
662
|
+
read_bytes_limit_per_second: @read_bytes_limit_per_second,
|
|
663
|
+
open_on_every_update: @open_on_every_update,
|
|
664
|
+
metrics: @metrics,
|
|
665
|
+
max_line_size: @max_line_size,
|
|
666
|
+
}
|
|
667
|
+
unless @encoding.nil?
|
|
668
|
+
if @from_encoding.nil?
|
|
669
|
+
opts[:encoding] = @encoding
|
|
670
|
+
else
|
|
671
|
+
opts[:encoding] = @from_encoding
|
|
672
|
+
opts[:encoding_to_convert] = @encoding
|
|
673
|
+
end
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
TailWatcher::IOHandler.new(
|
|
677
|
+
watcher,
|
|
678
|
+
**opts,
|
|
679
|
+
&method(:receive_lines)
|
|
680
|
+
)
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
# Reopen TailInput to keep worker-mode setup and lifecycle helpers grouped
|
|
685
|
+
# separately from the synchronous file-watching implementation above.
|
|
686
|
+
class TailInput
|
|
687
|
+
WORKER_COMPATIBILITY = WorkerCompatibility.new(self)
|
|
688
|
+
WORKER_PARSER_TYPES = %w[
|
|
689
|
+
none regexp json csv tsv ltsv msgpack apache apache2 apache_error nginx syslog
|
|
690
|
+
].freeze
|
|
691
|
+
WORKER_QUEUE_BYTES = 64 * 1024 * 1024
|
|
692
|
+
WorkerBatch = Struct.new(:watcher, :lines)
|
|
693
|
+
|
|
694
|
+
private
|
|
695
|
+
|
|
696
|
+
def configure_worker_mode
|
|
697
|
+
incompatibility_reasons = worker_incompatibility_reasons
|
|
698
|
+
@worker_compatible = incompatibility_reasons.empty?
|
|
699
|
+
if @worker_compatible
|
|
700
|
+
setup_worker_pool
|
|
701
|
+
log.info "in_tail worker parsing enabled with #{@num_threads} threads"
|
|
702
|
+
else
|
|
703
|
+
log.warn "in_tail worker parsing is unavailable; using synchronous parsing (#{incompatibility_reasons.join(', ')})"
|
|
704
|
+
end
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
def worker_incompatibility_reasons
|
|
708
|
+
reasons = WORKER_COMPATIBILITY.incompatible_hooks(self).map { |hook| "overridden hook: #{hook}" }
|
|
709
|
+
reasons << 'multiline parsing is enabled' if @multiline_mode
|
|
710
|
+
reasons << 'open_on_every_update is enabled' if @open_on_every_update
|
|
711
|
+
parser_type = @parser_configs.first[:@type]
|
|
712
|
+
reasons << "unsupported parser: #{parser_type}" unless WORKER_PARSER_TYPES.include?(parser_type)
|
|
713
|
+
parser_usage = @parser_configs.first.usage
|
|
714
|
+
reasons << 'the configured parser instance was replaced' unless @parser.equal?(@_parsers[parser_usage])
|
|
715
|
+
reasons
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
def create_worker_parsers(num_threads)
|
|
719
|
+
return [] unless @worker_compatible
|
|
720
|
+
raise ArgumentError, 'worker count must be positive' unless num_threads.positive?
|
|
721
|
+
|
|
722
|
+
parser_config = @parser_configs.first.corresponding_config_element
|
|
723
|
+
Array.new(num_threads) do |index|
|
|
724
|
+
usage = "__in_tail_worker_#{object_id}_#{index}"
|
|
725
|
+
parser_create(usage: usage, conf: parser_config)
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
def setup_worker_pool
|
|
730
|
+
@worker_metrics = {
|
|
731
|
+
pending_batches: metrics_create(namespace: 'fluentd', subsystem: 'input', name: 'worker_pending_batches',
|
|
732
|
+
help_text: 'Number of worker batches awaiting completion or acknowledgment',
|
|
733
|
+
prefer_gauge: true),
|
|
734
|
+
pending_bytes: metrics_create(namespace: 'fluentd', subsystem: 'input', name: 'worker_pending_bytes',
|
|
735
|
+
help_text: 'Estimated bytes accounted for by pending worker batches',
|
|
736
|
+
prefer_gauge: true),
|
|
737
|
+
parse_errors: metrics_create(namespace: 'fluentd', subsystem: 'input', name: 'worker_parse_errors_total',
|
|
738
|
+
help_text: 'Total number of worker parse errors'),
|
|
739
|
+
}
|
|
740
|
+
@worker_parsers = create_worker_parsers(@num_threads)
|
|
741
|
+
@worker_line_feeders = @worker_parsers.map { |parser| build_worker_line_feeder(parser) }
|
|
742
|
+
@worker_waiting_watchers = []
|
|
743
|
+
@worker_deferred_unwatch = {}
|
|
744
|
+
@worker_detached_watchers = []
|
|
745
|
+
@worker_completion_mutex = Mutex.new
|
|
746
|
+
@worker_completions_stopped = false
|
|
747
|
+
@worker_completion_watcher = WorkerCompletionWatcher.new(log: log) { process_worker_completions }
|
|
748
|
+
event_loop_attach(@worker_completion_watcher)
|
|
749
|
+
@worker_pool = WorkerPool.new(
|
|
750
|
+
num_threads: @num_threads,
|
|
751
|
+
task_limit: @num_threads * 2,
|
|
752
|
+
byte_limit: WORKER_QUEUE_BYTES,
|
|
753
|
+
thread_create: ->(title, &block) { Thread.new { Thread.current.name = title.to_s if Thread.current.respond_to?(:name=); block.call } },
|
|
754
|
+
notify: @worker_completion_watcher.method(:signal)
|
|
755
|
+
) do |index, batch|
|
|
756
|
+
@worker_line_feeders[index].parse(batch.lines.map(&:dup), batch.watcher)
|
|
757
|
+
end
|
|
758
|
+
@worker_pool.start
|
|
759
|
+
rescue
|
|
760
|
+
event_loop_detach(@worker_completion_watcher) if @worker_completion_watcher&.attached?
|
|
761
|
+
@worker_completion_watcher&.close
|
|
762
|
+
raise
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
def build_worker_line_feeder(parser)
|
|
766
|
+
LineFeeder.new(
|
|
767
|
+
parser: parser,
|
|
768
|
+
router_provider: method(:router),
|
|
769
|
+
log: log,
|
|
770
|
+
tag: @tag,
|
|
771
|
+
tag_prefix: @tag_prefix,
|
|
772
|
+
tag_suffix: @tag_suffix,
|
|
773
|
+
path_key: @path_key,
|
|
774
|
+
emit_unmatched_lines: @emit_unmatched_lines,
|
|
775
|
+
multiline_mode: false
|
|
776
|
+
)
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
def async_receive_lines(lines, watcher)
|
|
780
|
+
return @line_feeder.feed_lines(lines, watcher) unless @worker_pool.statistics[:state] == :running
|
|
781
|
+
|
|
782
|
+
bytes = lines.sum(&:bytesize) * 3
|
|
783
|
+
batch = WorkerBatch.new(watcher, lines)
|
|
784
|
+
if @worker_pool.submit(watcher, batch, bytes: bytes)
|
|
785
|
+
update_worker_metrics
|
|
786
|
+
@worker_waiting_watchers.delete(watcher)
|
|
787
|
+
TailWatcher::IOHandler::ASYNC_PENDING
|
|
788
|
+
else
|
|
789
|
+
@worker_waiting_watchers << watcher unless @worker_waiting_watchers.include?(watcher)
|
|
790
|
+
false
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
def process_worker_completions
|
|
795
|
+
@worker_completion_mutex.synchronize do
|
|
796
|
+
drain_worker_completions(resume: true) unless @worker_completions_stopped
|
|
797
|
+
end
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
def drain_worker_completions(resume:)
|
|
801
|
+
while (result = @worker_pool.next_result)
|
|
802
|
+
begin
|
|
803
|
+
complete_worker_result(result, resume: resume)
|
|
804
|
+
rescue => error
|
|
805
|
+
log.error 'unexpected error while completing in_tail worker batch', error: error
|
|
806
|
+
log.error_backtrace
|
|
807
|
+
end
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
return unless resume
|
|
811
|
+
|
|
812
|
+
waiting = @worker_waiting_watchers
|
|
813
|
+
@worker_waiting_watchers = []
|
|
814
|
+
waiting.each do |watcher|
|
|
815
|
+
begin
|
|
816
|
+
watcher.detached? ? watcher.read_more : watcher.on_notify
|
|
817
|
+
rescue => error
|
|
818
|
+
log.error 'unexpected error while retrying in_tail worker batch', error: error
|
|
819
|
+
log.error_backtrace
|
|
820
|
+
ensure
|
|
821
|
+
finish_worker_deferred_close(watcher)
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
def complete_worker_result(result, resume:)
|
|
827
|
+
batch = result.task.payload
|
|
828
|
+
emitted = false
|
|
829
|
+
begin
|
|
830
|
+
if result.error
|
|
831
|
+
@worker_metrics[:parse_errors].inc
|
|
832
|
+
if resume
|
|
833
|
+
log.warn 'worker parsing failed; retrying synchronously', path: batch.watcher.path, error: result.error
|
|
834
|
+
emitted = @line_feeder.feed_lines(batch.lines, batch.watcher)
|
|
835
|
+
else
|
|
836
|
+
log.warn 'worker parsing failed during shutdown; leaving position unchanged', path: batch.watcher.path, error: result.error
|
|
837
|
+
end
|
|
838
|
+
else
|
|
839
|
+
emitted = @line_feeder.emit(result.value, batch.watcher)
|
|
840
|
+
end
|
|
841
|
+
ensure
|
|
842
|
+
begin
|
|
843
|
+
@worker_pool.acknowledge(result)
|
|
844
|
+
update_worker_metrics
|
|
845
|
+
ensure
|
|
846
|
+
begin
|
|
847
|
+
batch.watcher.complete_async(emitted, resume: resume)
|
|
848
|
+
ensure
|
|
849
|
+
finish_worker_deferred_close(batch.watcher)
|
|
850
|
+
end
|
|
851
|
+
end
|
|
852
|
+
end
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
def finish_worker_deferred_close(watcher)
|
|
856
|
+
return if watcher.pending? || @worker_waiting_watchers.include?(watcher)
|
|
857
|
+
|
|
858
|
+
# Match synchronous detach on failed emit or a throttled final read:
|
|
859
|
+
# close the handle, leaving uncommitted data for a future watch.
|
|
860
|
+
watcher.close_if_ready(force: watcher.detached?)
|
|
861
|
+
@worker_detached_watchers.delete(watcher)
|
|
862
|
+
|
|
863
|
+
target_info = @worker_deferred_unwatch.delete(watcher)
|
|
864
|
+
@pf.unwatch(target_info) if target_info && @pf
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
def update_worker_metrics
|
|
868
|
+
statistics = @worker_pool.statistics
|
|
869
|
+
@worker_metrics[:pending_batches].set(statistics[:tasks])
|
|
870
|
+
@worker_metrics[:pending_bytes].set(statistics[:bytes])
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
def close_worker_watcher_handles
|
|
874
|
+
watchers = (@tails.values + @tails_rotate_wait.keys + @worker_detached_watchers + @worker_deferred_unwatch.keys).uniq
|
|
875
|
+
watchers.each(&:force_close)
|
|
876
|
+
@tails.clear
|
|
877
|
+
@worker_detached_watchers.clear
|
|
878
|
+
@worker_deferred_unwatch.each_value do |target_info|
|
|
879
|
+
@pf.unwatch(target_info) if @pf
|
|
880
|
+
end
|
|
881
|
+
@worker_deferred_unwatch.clear
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
def shutdown_worker_pool
|
|
885
|
+
return unless @worker_pool
|
|
886
|
+
|
|
887
|
+
deadline = Fluent::Clock.now + TailWatcher::IOHandler::SHUTDOWN_TIMEOUT
|
|
888
|
+
while worker_work_pending?
|
|
889
|
+
break if Fluent::Clock.now >= deadline
|
|
890
|
+
sleep 0.01
|
|
891
|
+
end
|
|
892
|
+
if worker_work_pending?
|
|
893
|
+
log.warn 'in_tail worker pool did not drain before shutdown timeout'
|
|
894
|
+
end
|
|
895
|
+
event_loop_detach(@worker_completion_watcher)
|
|
896
|
+
# Detach alone does not wait for a callback already running. Serialize
|
|
897
|
+
# the handover so every result is handled before output shutdown starts.
|
|
898
|
+
@worker_completion_mutex.synchronize do
|
|
899
|
+
@worker_completions_stopped = true
|
|
900
|
+
@worker_pool.stop
|
|
901
|
+
end
|
|
902
|
+
unless @worker_pool.join(timeout: TailWatcher::IOHandler::SHUTDOWN_TIMEOUT)
|
|
903
|
+
log.warn 'in_tail worker threads did not stop before timeout'
|
|
904
|
+
@worker_pool.kill
|
|
905
|
+
end
|
|
906
|
+
@worker_completion_mutex.synchronize do
|
|
907
|
+
drain_worker_completions(resume: false)
|
|
908
|
+
end
|
|
909
|
+
end
|
|
910
|
+
|
|
911
|
+
def worker_work_pending?
|
|
912
|
+
@worker_completion_mutex.synchronize do
|
|
913
|
+
@worker_pool.statistics[:tasks].positive? || !@worker_waiting_watchers.empty?
|
|
914
|
+
end
|
|
915
|
+
end
|
|
916
|
+
end
|
|
917
|
+
end
|