fluentd 1.14.6 → 1.15.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of fluentd might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/workflows/linux-test.yaml +1 -1
- data/.github/workflows/windows-test.yaml +4 -1
- data/CHANGELOG.md +53 -1
- data/fluentd.gemspec +2 -1
- data/lib/fluent/command/ctl.rb +4 -1
- data/lib/fluent/command/fluentd.rb +10 -0
- data/lib/fluent/config/literal_parser.rb +2 -2
- data/lib/fluent/config/yaml_parser/fluent_value.rb +47 -0
- data/lib/fluent/config/yaml_parser/loader.rb +91 -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/plugin/file_wrapper.rb +52 -107
- data/lib/fluent/plugin/in_tail/group_watch.rb +204 -0
- data/lib/fluent/plugin/in_tail/position_file.rb +1 -15
- data/lib/fluent/plugin/in_tail.rb +66 -47
- data/lib/fluent/plugin/out_forward/socket_cache.rb +2 -0
- data/lib/fluent/plugin/output.rb +2 -1
- data/lib/fluent/plugin/parser_syslog.rb +1 -1
- data/lib/fluent/plugin_helper/server.rb +3 -1
- data/lib/fluent/plugin_helper/service_discovery.rb +2 -2
- data/lib/fluent/supervisor.rb +109 -25
- data/lib/fluent/system_config.rb +2 -1
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/winsvc.rb +2 -0
- data/test/command/test_ctl.rb +0 -1
- data/test/command/test_fluentd.rb +33 -0
- data/test/config/test_system_config.rb +3 -1
- data/test/config/test_types.rb +1 -1
- data/test/plugin/in_tail/test_io_handler.rb +14 -4
- data/test/plugin/in_tail/test_position_file.rb +0 -63
- data/test/plugin/out_forward/test_socket_cache.rb +26 -1
- data/test/plugin/test_file_wrapper.rb +0 -68
- data/test/plugin/test_in_object_space.rb +9 -3
- data/test/plugin/test_in_syslog.rb +1 -1
- data/test/plugin/test_in_tail.rb +629 -353
- data/test/plugin/test_out_forward.rb +30 -20
- 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 +16 -4
- data/test/test_config.rb +135 -4
- data/test/test_supervisor.rb +155 -0
- metadata +11 -5
@@ -16,8 +16,8 @@
|
|
16
16
|
|
17
17
|
module Fluent
|
18
18
|
module FileWrapper
|
19
|
-
def self.open(
|
20
|
-
io = WindowsFile.new(
|
19
|
+
def self.open(path, mode='r')
|
20
|
+
io = WindowsFile.new(path, mode).io
|
21
21
|
if block_given?
|
22
22
|
v = yield io
|
23
23
|
io.close
|
@@ -35,116 +35,36 @@ module Fluent
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
module WindowsFileExtension
|
39
|
-
attr_reader :path
|
40
|
-
|
41
|
-
def stat
|
42
|
-
s = super
|
43
|
-
s.instance_variable_set :@ino, @ino
|
44
|
-
def s.ino; @ino; end
|
45
|
-
s
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
class Win32Error < StandardError
|
50
|
-
require 'windows/error'
|
51
|
-
include Windows::Error
|
52
|
-
|
53
|
-
attr_reader :errcode, :msg
|
54
|
-
|
55
|
-
WSABASEERR = 10000
|
56
|
-
|
57
|
-
def initialize(errcode, msg = nil)
|
58
|
-
@errcode = errcode
|
59
|
-
@msg = msg
|
60
|
-
end
|
61
|
-
|
62
|
-
def format_english_message(errcode)
|
63
|
-
buf = 0.chr * 260
|
64
|
-
flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY
|
65
|
-
english_lang_id = 1033 # The result of MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)
|
66
|
-
FormatMessageA.call(flags, 0, errcode, english_lang_id, buf, buf.size, 0)
|
67
|
-
buf.force_encoding(Encoding.default_external).strip
|
68
|
-
end
|
69
|
-
|
70
|
-
def to_s
|
71
|
-
msg = super
|
72
|
-
msg << ": code: #{@errcode}, #{format_english_message(@errcode)}"
|
73
|
-
msg << " - #{@msg}" if @msg
|
74
|
-
msg
|
75
|
-
end
|
76
|
-
|
77
|
-
def inspect
|
78
|
-
"#<#{to_s}>"
|
79
|
-
end
|
80
|
-
|
81
|
-
def ==(other)
|
82
|
-
return false if other.class != Win32Error
|
83
|
-
@errcode == other.errcode && @msg == other.msg
|
84
|
-
end
|
85
|
-
|
86
|
-
def wsaerr?
|
87
|
-
@errcode >= WSABASEERR
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
# To open and get stat with setting FILE_SHARE_DELETE
|
92
38
|
class WindowsFile
|
93
39
|
require 'windows/file'
|
94
|
-
require 'windows/error'
|
95
40
|
require 'windows/handle'
|
96
|
-
require 'windows/nio'
|
97
41
|
|
98
|
-
include
|
42
|
+
include File::Constants
|
99
43
|
include Windows::File
|
100
44
|
include Windows::Handle
|
101
|
-
include Windows::NIO
|
102
|
-
|
103
|
-
def initialize(path, mode='r', sharemode=FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE)
|
104
|
-
@path = path
|
105
|
-
@file_handle = INVALID_HANDLE_VALUE
|
106
|
-
@mode = mode
|
107
45
|
|
46
|
+
attr_reader :io
|
108
47
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
else raise "unknown mode '#{mode}'"
|
117
|
-
end
|
118
|
-
|
119
|
-
@file_handle = CreateFile.call(@path, access, sharemode,
|
120
|
-
0, creationdisposition, FILE_ATTRIBUTE_NORMAL, 0)
|
121
|
-
if @file_handle == INVALID_HANDLE_VALUE
|
122
|
-
win32err = Win32Error.new(Win32::API.last_error, path)
|
123
|
-
errno = ServerEngine::RbWinSock.rb_w32_map_errno(win32err.errcode)
|
124
|
-
if errno == Errno::EINVAL::Errno || win32err.wsaerr?
|
125
|
-
# maybe failed to map
|
126
|
-
raise win32err
|
127
|
-
else
|
128
|
-
raise SystemCallError.new(win32err.message, errno)
|
129
|
-
end
|
48
|
+
def initialize(path, mode='r')
|
49
|
+
@path = path
|
50
|
+
@io = File.open(path, mode2flags(mode))
|
51
|
+
@file_handle = _get_osfhandle(@io.to_i)
|
52
|
+
@io.instance_variable_set(:@file_index, self.ino)
|
53
|
+
def @io.ino
|
54
|
+
@file_index
|
130
55
|
end
|
131
56
|
end
|
132
57
|
|
133
58
|
def close
|
134
|
-
|
59
|
+
@io.close
|
135
60
|
@file_handle = INVALID_HANDLE_VALUE
|
136
61
|
end
|
137
62
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
io.instance_variable_set :@path, @path
|
144
|
-
io.extend WindowsFileExtension
|
145
|
-
io
|
146
|
-
end
|
147
|
-
|
63
|
+
# To keep backward compatibility, we continue to use GetFileInformationByHandle()
|
64
|
+
# to get file id.
|
65
|
+
# Note that Ruby's File.stat uses GetFileInformationByHandleEx() with FileIdInfo
|
66
|
+
# and returned value is different with above one, former one is 64 bit while
|
67
|
+
# later one is 128bit.
|
148
68
|
def ino
|
149
69
|
by_handle_file_information = '\0'*(4+8+8+8+4+4+4+4+4+4) #72bytes
|
150
70
|
|
@@ -155,6 +75,41 @@ module Fluent
|
|
155
75
|
by_handle_file_information.unpack("I11Q1")[11] # fileindex
|
156
76
|
end
|
157
77
|
|
78
|
+
def stat
|
79
|
+
raise Errno::ENOENT if delete_pending
|
80
|
+
s = File.stat(@path)
|
81
|
+
s.instance_variable_set :@ino, self.ino
|
82
|
+
def s.ino; @ino; end
|
83
|
+
s
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def mode2flags(mode)
|
89
|
+
# Always inject File::Constants::SHARE_DELETE
|
90
|
+
# https://github.com/fluent/fluentd/pull/3585#issuecomment-1101502617
|
91
|
+
# To enable SHARE_DELETE, BINARY is also required.
|
92
|
+
# https://bugs.ruby-lang.org/issues/11218
|
93
|
+
# https://github.com/ruby/ruby/blob/d6684f063bc53e3cab025bd39526eca3b480b5e7/win32/win32.c#L6332-L6345
|
94
|
+
flags = BINARY | SHARE_DELETE
|
95
|
+
case mode.delete("b")
|
96
|
+
when "r"
|
97
|
+
flags |= RDONLY
|
98
|
+
when "r+"
|
99
|
+
flags |= RDWR
|
100
|
+
when "w"
|
101
|
+
flags |= WRONLY | CREAT | TRUNC
|
102
|
+
when "w+"
|
103
|
+
flags |= RDWR | CREAT | TRUNC
|
104
|
+
when "a"
|
105
|
+
flags |= WRONLY | CREAT | APPEND
|
106
|
+
when "a+"
|
107
|
+
flags |= RDWR | CREAT | APPEND
|
108
|
+
else
|
109
|
+
raise Errno::EINVAL.new("Unsupported mode by Fluent::FileWrapper: #{mode}")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
158
113
|
# DeletePending is a Windows-specific file state that roughly means
|
159
114
|
# "this file is queued for deletion, so close any open handlers"
|
160
115
|
#
|
@@ -173,15 +128,5 @@ module Fluent
|
|
173
128
|
|
174
129
|
return buf.unpack("QQICC")[3] != 0
|
175
130
|
end
|
176
|
-
|
177
|
-
private :delete_pending
|
178
|
-
|
179
|
-
def stat
|
180
|
-
raise Errno::ENOENT if delete_pending
|
181
|
-
s = File.stat(@path)
|
182
|
-
s.instance_variable_set :@ino, self.ino
|
183
|
-
def s.ino; @ino; end
|
184
|
-
s
|
185
|
-
end
|
186
131
|
end
|
187
132
|
end if Fluent.windows?
|
@@ -0,0 +1,204 @@
|
|
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 'fluent/plugin/input'
|
18
|
+
|
19
|
+
module Fluent::Plugin
|
20
|
+
class TailInput < Fluent::Plugin::Input
|
21
|
+
module GroupWatchParams
|
22
|
+
include Fluent::Configurable
|
23
|
+
|
24
|
+
DEFAULT_KEY = /.*/
|
25
|
+
DEFAULT_LIMIT = -1
|
26
|
+
REGEXP_JOIN = "_"
|
27
|
+
|
28
|
+
config_section :group, param_name: :group, required: false, multi: false do
|
29
|
+
desc 'Regex for extracting group\'s metadata'
|
30
|
+
config_param :pattern,
|
31
|
+
:regexp,
|
32
|
+
default: /^\/var\/log\/containers\/(?<podname>[a-z0-9]([-a-z0-9]*[a-z0-9])?(\/[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)_(?<namespace>[^_]+)_(?<container>.+)-(?<docker_id>[a-z0-9]{64})\.log$/
|
33
|
+
|
34
|
+
desc 'Period of time in which the group_line_limit is applied'
|
35
|
+
config_param :rate_period, :time, default: 5
|
36
|
+
|
37
|
+
config_section :rule, param_name: :rule, required: true, multi: true do
|
38
|
+
desc 'Key-value pairs for grouping'
|
39
|
+
config_param :match, :hash, value_type: :regexp, default: { namespace: [DEFAULT_KEY], podname: [DEFAULT_KEY] }
|
40
|
+
desc 'Maximum number of log lines allowed per group over a period of rate_period'
|
41
|
+
config_param :limit, :integer, default: DEFAULT_LIMIT
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module GroupWatch
|
47
|
+
def self.included(mod)
|
48
|
+
mod.include GroupWatchParams
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :group_watchers, :default_group_key
|
52
|
+
|
53
|
+
def initialize
|
54
|
+
super
|
55
|
+
@group_watchers = {}
|
56
|
+
@group_keys = nil
|
57
|
+
@default_group_key = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def configure(conf)
|
61
|
+
super
|
62
|
+
|
63
|
+
unless @group.nil?
|
64
|
+
## Ensuring correct time period syntax
|
65
|
+
@group.rule.each { |rule|
|
66
|
+
raise "Metadata Group Limit >= DEFAULT_LIMIT" unless rule.limit >= GroupWatchParams::DEFAULT_LIMIT
|
67
|
+
}
|
68
|
+
|
69
|
+
@group_keys = Regexp.compile(@group.pattern).named_captures.keys
|
70
|
+
@default_group_key = ([GroupWatchParams::DEFAULT_KEY] * @group_keys.length).join(GroupWatchParams::REGEXP_JOIN)
|
71
|
+
|
72
|
+
## Ensures that "specific" rules (with larger number of `rule.match` keys)
|
73
|
+
## have a higher priority against "generic" rules (with less number of `rule.match` keys).
|
74
|
+
## This will be helpful when a file satisfies more than one rule.
|
75
|
+
@group.rule.sort_by! { |rule| -rule.match.length() }
|
76
|
+
construct_groupwatchers
|
77
|
+
@group_watchers[@default_group_key] ||= GroupWatcher.new(@group.rate_period, GroupWatchParams::DEFAULT_LIMIT)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_path_to_group_watcher(path)
|
82
|
+
return nil if @group.nil?
|
83
|
+
group_watcher = find_group_from_metadata(path)
|
84
|
+
group_watcher.add(path) unless group_watcher.include?(path)
|
85
|
+
group_watcher
|
86
|
+
end
|
87
|
+
|
88
|
+
def remove_path_from_group_watcher(path)
|
89
|
+
return if @group.nil?
|
90
|
+
group_watcher = find_group_from_metadata(path)
|
91
|
+
group_watcher.delete(path)
|
92
|
+
end
|
93
|
+
|
94
|
+
def construct_group_key(named_captures)
|
95
|
+
match_rule = []
|
96
|
+
@group_keys.each { |key|
|
97
|
+
match_rule.append(named_captures.fetch(key, GroupWatchParams::DEFAULT_KEY))
|
98
|
+
}
|
99
|
+
match_rule = match_rule.join(GroupWatchParams::REGEXP_JOIN)
|
100
|
+
|
101
|
+
match_rule
|
102
|
+
end
|
103
|
+
|
104
|
+
def construct_groupwatchers
|
105
|
+
@group.rule.each { |rule|
|
106
|
+
match_rule = construct_group_key(rule.match)
|
107
|
+
@group_watchers[match_rule] ||= GroupWatcher.new(@group.rate_period, rule.limit)
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
def find_group(metadata)
|
112
|
+
metadata_key = construct_group_key(metadata)
|
113
|
+
gw_key = @group_watchers.keys.find { |regexp| metadata_key.match?(regexp) && regexp != @default_group_key }
|
114
|
+
gw_key ||= @default_group_key
|
115
|
+
|
116
|
+
@group_watchers[gw_key]
|
117
|
+
end
|
118
|
+
|
119
|
+
def find_group_from_metadata(path)
|
120
|
+
begin
|
121
|
+
metadata = @group.pattern.match(path).named_captures
|
122
|
+
group_watcher = find_group(metadata)
|
123
|
+
rescue
|
124
|
+
log.warn "Cannot find group from metadata, Adding file in the default group"
|
125
|
+
group_watcher = @group_watchers[@default_group_key]
|
126
|
+
end
|
127
|
+
|
128
|
+
group_watcher
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class GroupWatcher
|
133
|
+
attr_accessor :current_paths, :limit, :number_lines_read, :start_reading_time, :rate_period
|
134
|
+
|
135
|
+
FileCounter = Struct.new(
|
136
|
+
:number_lines_read,
|
137
|
+
:start_reading_time,
|
138
|
+
)
|
139
|
+
|
140
|
+
def initialize(rate_period = 60, limit = -1)
|
141
|
+
@current_paths = {}
|
142
|
+
@rate_period = rate_period
|
143
|
+
@limit = limit
|
144
|
+
end
|
145
|
+
|
146
|
+
def add(path)
|
147
|
+
@current_paths[path] = FileCounter.new(0, nil)
|
148
|
+
end
|
149
|
+
|
150
|
+
def include?(path)
|
151
|
+
@current_paths.key?(path)
|
152
|
+
end
|
153
|
+
|
154
|
+
def size
|
155
|
+
@current_paths.size
|
156
|
+
end
|
157
|
+
|
158
|
+
def delete(path)
|
159
|
+
@current_paths.delete(path)
|
160
|
+
end
|
161
|
+
|
162
|
+
def update_reading_time(path)
|
163
|
+
@current_paths[path].start_reading_time ||= Fluent::Clock.now
|
164
|
+
end
|
165
|
+
|
166
|
+
def update_lines_read(path, value)
|
167
|
+
@current_paths[path].number_lines_read += value
|
168
|
+
end
|
169
|
+
|
170
|
+
def reset_counter(path)
|
171
|
+
@current_paths[path].start_reading_time = nil
|
172
|
+
@current_paths[path].number_lines_read = 0
|
173
|
+
end
|
174
|
+
|
175
|
+
def time_spent_reading(path)
|
176
|
+
Fluent::Clock.now - @current_paths[path].start_reading_time
|
177
|
+
end
|
178
|
+
|
179
|
+
def limit_time_period_reached?(path)
|
180
|
+
time_spent_reading(path) < @rate_period
|
181
|
+
end
|
182
|
+
|
183
|
+
def limit_lines_reached?(path)
|
184
|
+
return true unless include?(path)
|
185
|
+
return true if @limit == 0
|
186
|
+
|
187
|
+
return false if @limit < 0
|
188
|
+
return false if @current_paths[path].number_lines_read < @limit / size
|
189
|
+
|
190
|
+
# update_reading_time(path)
|
191
|
+
if limit_time_period_reached?(path) # Exceeds limit
|
192
|
+
true
|
193
|
+
else # Does not exceed limit
|
194
|
+
reset_counter(path)
|
195
|
+
false
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def to_s
|
200
|
+
super + " current_paths: #{@current_paths} rate_period: #{@rate_period} limit: #{@limit}"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -250,20 +250,6 @@ module Fluent::Plugin
|
|
250
250
|
end
|
251
251
|
end
|
252
252
|
|
253
|
-
TargetInfo = Struct.new(:path, :ino)
|
254
|
-
def ==(other)
|
255
|
-
return false unless other.is_a?(TargetInfo)
|
256
|
-
self.path == other.path
|
257
|
-
end
|
258
|
-
|
259
|
-
def hash
|
260
|
-
self.path.hash
|
261
|
-
end
|
262
|
-
|
263
|
-
def eql?(other)
|
264
|
-
return false unless other.is_a?(TargetInfo)
|
265
|
-
self.path == other.path
|
266
|
-
end
|
267
|
-
end
|
253
|
+
TargetInfo = Struct.new(:path, :ino)
|
268
254
|
end
|
269
255
|
end
|
@@ -24,6 +24,7 @@ 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
|
+
require 'fluent/plugin/in_tail/group_watch'
|
27
28
|
|
28
29
|
if Fluent.windows?
|
29
30
|
require_relative 'file_wrapper'
|
@@ -33,6 +34,8 @@ end
|
|
33
34
|
|
34
35
|
module Fluent::Plugin
|
35
36
|
class TailInput < Fluent::Plugin::Input
|
37
|
+
include GroupWatch
|
38
|
+
|
36
39
|
Fluent::Plugin.register_input('tail', self)
|
37
40
|
|
38
41
|
helpers :timer, :event_loop, :parser, :compat_parameters
|
@@ -354,11 +357,11 @@ module Fluent::Plugin
|
|
354
357
|
|
355
358
|
def existence_path
|
356
359
|
hash = {}
|
357
|
-
@tails.
|
360
|
+
@tails.each {|path, tw|
|
358
361
|
if @follow_inodes
|
359
|
-
hash[
|
362
|
+
hash[tw.ino] = TargetInfo.new(tw.path, tw.ino)
|
360
363
|
else
|
361
|
-
hash[
|
364
|
+
hash[tw.path] = TargetInfo.new(tw.path, tw.ino)
|
362
365
|
end
|
363
366
|
}
|
364
367
|
hash
|
@@ -406,6 +409,8 @@ module Fluent::Plugin
|
|
406
409
|
event_loop_attach(watcher)
|
407
410
|
end
|
408
411
|
|
412
|
+
tw.group_watcher = add_path_to_group_watcher(target_info.path)
|
413
|
+
|
409
414
|
tw
|
410
415
|
rescue => e
|
411
416
|
if tw
|
@@ -420,36 +425,31 @@ module Fluent::Plugin
|
|
420
425
|
end
|
421
426
|
|
422
427
|
def construct_watcher(target_info)
|
428
|
+
path = target_info.path
|
429
|
+
|
430
|
+
# The file might be rotated or removed after collecting paths, so check inode again here.
|
431
|
+
begin
|
432
|
+
target_info.ino = Fluent::FileWrapper.stat(path).ino
|
433
|
+
rescue Errno::ENOENT, Errno::EACCES
|
434
|
+
$log.warn "stat() for #{path} failed. Continuing without tailing it."
|
435
|
+
return
|
436
|
+
end
|
437
|
+
|
423
438
|
pe = nil
|
424
439
|
if @pf
|
425
440
|
pe = @pf[target_info]
|
426
|
-
if @read_from_head && pe.read_inode.zero?
|
427
|
-
begin
|
428
|
-
pe.update(Fluent::FileWrapper.stat(target_info.path).ino, 0)
|
429
|
-
rescue Errno::ENOENT, Errno::EACCES
|
430
|
-
$log.warn "stat() for #{target_info.path} failed. Continuing without tailing it."
|
431
|
-
end
|
432
|
-
end
|
441
|
+
pe.update(target_info.ino, 0) if @read_from_head && pe.read_inode.zero?
|
433
442
|
end
|
434
443
|
|
435
444
|
begin
|
436
445
|
tw = setup_watcher(target_info, pe)
|
437
446
|
rescue WatcherSetupError => e
|
438
|
-
log.warn "Skip #{
|
447
|
+
log.warn "Skip #{path} because unexpected setup error happens: #{e}"
|
439
448
|
return
|
440
449
|
end
|
441
450
|
|
442
|
-
|
443
|
-
|
444
|
-
@tails.delete(target_info)
|
445
|
-
@tails[target_info] = tw
|
446
|
-
tw.on_notify
|
447
|
-
rescue Errno::ENOENT, Errno::EACCES => e
|
448
|
-
$log.warn "stat() for #{target_info.path} failed with #{e.class.name}. Drop tail watcher for now."
|
449
|
-
# explicitly detach and unwatch watcher `tw`.
|
450
|
-
tw.unwatched = true
|
451
|
-
detach_watcher(tw, target_info.ino, false)
|
452
|
-
end
|
451
|
+
@tails[path] = tw
|
452
|
+
tw.on_notify
|
453
453
|
end
|
454
454
|
|
455
455
|
def start_watchers(targets_info)
|
@@ -461,10 +461,12 @@ module Fluent::Plugin
|
|
461
461
|
|
462
462
|
def stop_watchers(targets_info, immediate: false, unwatched: false, remove_watcher: true)
|
463
463
|
targets_info.each_value { |target_info|
|
464
|
+
remove_path_from_group_watcher(target_info.path)
|
465
|
+
|
464
466
|
if remove_watcher
|
465
|
-
tw = @tails.delete(target_info)
|
467
|
+
tw = @tails.delete(target_info.path)
|
466
468
|
else
|
467
|
-
tw = @tails[target_info]
|
469
|
+
tw = @tails[target_info.path]
|
468
470
|
end
|
469
471
|
if tw
|
470
472
|
tw.unwatched = unwatched
|
@@ -478,8 +480,8 @@ module Fluent::Plugin
|
|
478
480
|
end
|
479
481
|
|
480
482
|
def close_watcher_handles
|
481
|
-
@tails.keys.each do |
|
482
|
-
tw = @tails.delete(
|
483
|
+
@tails.keys.each do |path|
|
484
|
+
tw = @tails.delete(path)
|
483
485
|
if tw
|
484
486
|
tw.close
|
485
487
|
end
|
@@ -488,20 +490,20 @@ module Fluent::Plugin
|
|
488
490
|
|
489
491
|
# refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
|
490
492
|
def update_watcher(target_info, pe)
|
491
|
-
|
493
|
+
path = target_info.path
|
494
|
+
|
495
|
+
log.info("detected rotation of #{path}; waiting #{@rotate_wait} seconds")
|
492
496
|
|
493
497
|
if @pf
|
494
498
|
pe_inode = pe.read_inode
|
495
|
-
target_info_from_position_entry = TargetInfo.new(
|
499
|
+
target_info_from_position_entry = TargetInfo.new(path, pe_inode)
|
496
500
|
unless pe_inode == @pf[target_info_from_position_entry].read_inode
|
497
501
|
log.debug "Skip update_watcher because watcher has been already updated by other inotify event"
|
498
502
|
return
|
499
503
|
end
|
500
504
|
end
|
501
505
|
|
502
|
-
|
503
|
-
rotated_tw = @tails[rotated_target_info]
|
504
|
-
new_target_info = target_info.dup
|
506
|
+
rotated_tw = @tails[path]
|
505
507
|
|
506
508
|
if @follow_inodes
|
507
509
|
new_position_entry = @pf[target_info]
|
@@ -509,17 +511,13 @@ module Fluent::Plugin
|
|
509
511
|
if new_position_entry.read_inode == 0
|
510
512
|
# When follow_inodes is true, it's not cleaned up by refresh_watcher.
|
511
513
|
# So it should be unwatched here explicitly.
|
512
|
-
rotated_tw.unwatched = true
|
513
|
-
|
514
|
-
@tails.
|
515
|
-
@tails[new_target_info] = setup_watcher(new_target_info, new_position_entry)
|
516
|
-
@tails[new_target_info].on_notify
|
514
|
+
rotated_tw.unwatched = true if rotated_tw
|
515
|
+
@tails[path] = setup_watcher(target_info, new_position_entry)
|
516
|
+
@tails[path].on_notify
|
517
517
|
end
|
518
518
|
else
|
519
|
-
|
520
|
-
@tails.
|
521
|
-
@tails[new_target_info] = setup_watcher(new_target_info, pe)
|
522
|
-
@tails[new_target_info].on_notify
|
519
|
+
@tails[path] = setup_watcher(target_info, pe)
|
520
|
+
@tails[path].on_notify
|
523
521
|
end
|
524
522
|
detach_watcher_after_rotate_wait(rotated_tw, pe.read_inode) if rotated_tw
|
525
523
|
end
|
@@ -542,18 +540,19 @@ module Fluent::Plugin
|
|
542
540
|
end
|
543
541
|
end
|
544
542
|
|
543
|
+
def throttling_is_enabled?(tw)
|
544
|
+
return true if @read_bytes_limit_per_second > 0
|
545
|
+
return true if tw.group_watcher && tw.group_watcher.limit >= 0
|
546
|
+
false
|
547
|
+
end
|
548
|
+
|
545
549
|
def detach_watcher_after_rotate_wait(tw, ino)
|
546
550
|
# Call event_loop_attach/event_loop_detach is high-cost for short-live object.
|
547
551
|
# If this has a problem with large number of files, use @_event_loop directly instead of timer_execute.
|
548
552
|
if @open_on_every_update
|
549
553
|
# Detach now because it's already closed, waiting it doesn't make sense.
|
550
554
|
detach_watcher(tw, ino)
|
551
|
-
elsif
|
552
|
-
# throttling isn't enabled, just wait @rotate_wait
|
553
|
-
timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
|
554
|
-
detach_watcher(tw, ino)
|
555
|
-
end
|
556
|
-
else
|
555
|
+
elsif throttling_is_enabled?(tw)
|
557
556
|
# When the throttling feature is enabled, it might not reach EOF yet.
|
558
557
|
# Should ensure to read all contents before closing it, with keeping throttling.
|
559
558
|
start_time_to_wait = Fluent::Clock.now
|
@@ -564,6 +563,11 @@ module Fluent::Plugin
|
|
564
563
|
detach_watcher(tw, ino)
|
565
564
|
end
|
566
565
|
end
|
566
|
+
else
|
567
|
+
# when the throttling feature isn't enabled, just wait @rotate_wait
|
568
|
+
timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
|
569
|
+
detach_watcher(tw, ino)
|
570
|
+
end
|
567
571
|
end
|
568
572
|
end
|
569
573
|
|
@@ -775,6 +779,7 @@ module Fluent::Plugin
|
|
775
779
|
attr_reader :line_buffer_timer_flusher
|
776
780
|
attr_accessor :unwatched # This is used for removing position entry from PositionFile
|
777
781
|
attr_reader :watchers
|
782
|
+
attr_accessor :group_watcher
|
778
783
|
|
779
784
|
def tag
|
780
785
|
@parsed_tag ||= @path.tr('/', '.').gsub(/\.+/, '.').gsub(/^\./, '')
|
@@ -997,6 +1002,10 @@ module Fluent::Plugin
|
|
997
1002
|
@log.info "following tail of #{@path}"
|
998
1003
|
end
|
999
1004
|
|
1005
|
+
def group_watcher
|
1006
|
+
@watcher.group_watcher
|
1007
|
+
end
|
1008
|
+
|
1000
1009
|
def on_notify
|
1001
1010
|
@notify_mutex.synchronize { handle_notify }
|
1002
1011
|
end
|
@@ -1054,6 +1063,7 @@ module Fluent::Plugin
|
|
1054
1063
|
|
1055
1064
|
def handle_notify
|
1056
1065
|
return if limit_bytes_per_second_reached?
|
1066
|
+
return if group_watcher&.limit_lines_reached?(@path)
|
1057
1067
|
|
1058
1068
|
with_io do |io|
|
1059
1069
|
begin
|
@@ -1063,17 +1073,26 @@ module Fluent::Plugin
|
|
1063
1073
|
begin
|
1064
1074
|
while true
|
1065
1075
|
@start_reading_time ||= Fluent::Clock.now
|
1076
|
+
group_watcher&.update_reading_time(@path)
|
1077
|
+
|
1066
1078
|
data = io.readpartial(BYTES_TO_READ, @iobuf)
|
1067
1079
|
@eof = false
|
1068
1080
|
@number_bytes_read += data.bytesize
|
1069
1081
|
@fifo << data
|
1082
|
+
|
1083
|
+
n_lines_before_read = @lines.size
|
1070
1084
|
@fifo.read_lines(@lines)
|
1085
|
+
group_watcher&.update_lines_read(@path, @lines.size - n_lines_before_read)
|
1086
|
+
|
1087
|
+
group_watcher_limit = group_watcher&.limit_lines_reached?(@path)
|
1088
|
+
@log.debug "Reading Limit exceeded #{@path} #{group_watcher.number_lines_read}" if group_watcher_limit
|
1071
1089
|
|
1072
|
-
if limit_bytes_per_second_reached? || should_shutdown_now?
|
1090
|
+
if group_watcher_limit || limit_bytes_per_second_reached? || should_shutdown_now?
|
1073
1091
|
# Just get out from tailing loop.
|
1074
1092
|
read_more = false
|
1075
1093
|
break
|
1076
1094
|
end
|
1095
|
+
|
1077
1096
|
if @lines.size >= @read_lines_limit
|
1078
1097
|
# not to use too much memory in case the file is very large
|
1079
1098
|
read_more = true
|
@@ -50,6 +50,7 @@ module Fluent::Plugin
|
|
50
50
|
def checkin(sock)
|
51
51
|
@mutex.synchronize do
|
52
52
|
if (s = @inflight_sockets.delete(sock))
|
53
|
+
s.timeout = timeout
|
53
54
|
@available_sockets[s.key] << s
|
54
55
|
else
|
55
56
|
@log.debug("there is no socket #{sock}")
|
@@ -122,6 +123,7 @@ module Fluent::Plugin
|
|
122
123
|
t = Time.now
|
123
124
|
if (s = @available_sockets[key].find { |sock| !expired_socket?(sock, time: t) })
|
124
125
|
@inflight_sockets[s.sock] = @available_sockets[key].delete(s)
|
126
|
+
s.timeout = timeout
|
125
127
|
s
|
126
128
|
else
|
127
129
|
nil
|