fluentd 1.14.6-x64-mingw-ucrt → 1.15.2-x64-mingw-ucrt
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 +85 -1
- data/fluentd.gemspec +1 -3
- data/lib/fluent/command/ctl.rb +4 -1
- data/lib/fluent/command/fluentd.rb +11 -6
- 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/error.rb +3 -0
- data/lib/fluent/plugin/base.rb +19 -0
- data/lib/fluent/plugin/file_wrapper.rb +57 -113
- 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 +68 -48
- data/lib/fluent/plugin/out_file.rb +11 -1
- 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/child_process.rb +3 -0
- data/lib/fluent/plugin_helper/server.rb +3 -1
- data/lib/fluent/plugin_helper/service_discovery.rb +2 -2
- data/lib/fluent/supervisor.rb +125 -31
- data/lib/fluent/system_config.rb +4 -2
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/win32api.rb +38 -0
- data/lib/fluent/winsvc.rb +5 -8
- data/test/command/test_ctl.rb +0 -1
- data/test/command/test_fluentd.rb +33 -0
- data/test/config/test_system_config.rb +5 -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_base.rb +34 -0
- data/test/plugin/test_file_wrapper.rb +0 -73
- 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 +12 -39
@@ -0,0 +1,56 @@
|
|
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/config/yaml_parser/loader'
|
18
|
+
require 'fluent/config/yaml_parser/parser'
|
19
|
+
require 'pathname'
|
20
|
+
|
21
|
+
module Fluent
|
22
|
+
module Config
|
23
|
+
module YamlParser
|
24
|
+
def self.parse(path)
|
25
|
+
context = Kernel.binding
|
26
|
+
|
27
|
+
unless context.respond_to?(:use_nil)
|
28
|
+
context.define_singleton_method(:use_nil) do
|
29
|
+
raise Fluent::SetNil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
unless context.respond_to?(:use_default)
|
34
|
+
context.define_singleton_method(:use_default) do
|
35
|
+
raise Fluent::SetDefault
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
unless context.respond_to?(:hostname)
|
40
|
+
context.define_singleton_method(:hostname) do
|
41
|
+
Socket.gethostname
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
unless context.respond_to?(:worker_id)
|
46
|
+
context.define_singleton_method(:worker_id) do
|
47
|
+
ENV['SERVERENGINE_WORKER_ID'] || ''
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
s = Fluent::Config::YamlParser::Loader.new(context).load(Pathname.new(path))
|
52
|
+
Fluent::Config::YamlParser::Parser.new(s).build.to_element
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/fluent/config.rb
CHANGED
@@ -17,6 +17,7 @@
|
|
17
17
|
require 'fluent/config/error'
|
18
18
|
require 'fluent/config/element'
|
19
19
|
require 'fluent/configurable'
|
20
|
+
require 'fluent/config/yaml_parser'
|
20
21
|
|
21
22
|
module Fluent
|
22
23
|
module Config
|
@@ -25,7 +26,18 @@ module Fluent
|
|
25
26
|
# @param additional_config [String] config which is added to last of config body
|
26
27
|
# @param use_v1_config [Bool] config is formatted with v1 or not
|
27
28
|
# @return [Fluent::Config]
|
28
|
-
def self.build(config_path:, encoding: 'utf-8', additional_config: nil, use_v1_config: true)
|
29
|
+
def self.build(config_path:, encoding: 'utf-8', additional_config: nil, use_v1_config: true, type: nil)
|
30
|
+
if type == :guess
|
31
|
+
config_file_ext = File.extname(config_path)
|
32
|
+
if config_file_ext == '.yaml' || config_file_ext == '.yml'
|
33
|
+
type = :yaml
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if type == :yaml || type == :yml
|
38
|
+
return Fluent::Config::YamlParser.parse(config_path)
|
39
|
+
end
|
40
|
+
|
29
41
|
config_fname = File.basename(config_path)
|
30
42
|
config_basedir = File.dirname(config_path)
|
31
43
|
config_data = File.open(config_path, "r:#{encoding}:utf-8") do |f|
|
@@ -36,6 +48,7 @@ module Fluent
|
|
36
48
|
end
|
37
49
|
s
|
38
50
|
end
|
51
|
+
|
39
52
|
Fluent::Config.parse(config_data, config_fname, config_basedir, use_v1_config)
|
40
53
|
end
|
41
54
|
|
data/lib/fluent/error.rb
CHANGED
data/lib/fluent/plugin/base.rb
CHANGED
@@ -31,6 +31,7 @@ module Fluent
|
|
31
31
|
def initialize
|
32
32
|
@log = nil
|
33
33
|
super
|
34
|
+
@fluentd_lock_dir = ENV['FLUENTD_LOCK_DIR']
|
34
35
|
@_state = State.new(false, false, false, false, false, false, false, false, false)
|
35
36
|
@_context_router = nil
|
36
37
|
@_fluentd_worker_id = nil
|
@@ -70,6 +71,24 @@ module Fluent
|
|
70
71
|
true
|
71
72
|
end
|
72
73
|
|
74
|
+
def get_lock_path(name)
|
75
|
+
name = name.gsub(/[^a-zA-Z0-9]/, "_")
|
76
|
+
File.join(@fluentd_lock_dir, "fluentd-#{name}.lock")
|
77
|
+
end
|
78
|
+
|
79
|
+
def acquire_worker_lock(name)
|
80
|
+
if @fluentd_lock_dir.nil?
|
81
|
+
raise InvalidLockDirectory, "can't acquire lock because FLUENTD_LOCK_DIR isn't set"
|
82
|
+
end
|
83
|
+
lock_path = get_lock_path(name)
|
84
|
+
File.open(lock_path, "w") do |f|
|
85
|
+
f.flock(File::LOCK_EX)
|
86
|
+
yield
|
87
|
+
end
|
88
|
+
# Update access time to prevent tmpwatch from deleting a lock file.
|
89
|
+
FileUtils.touch(lock_path);
|
90
|
+
end
|
91
|
+
|
73
92
|
def string_safe_encoding(str)
|
74
93
|
unless str.valid_encoding?
|
75
94
|
str = str.scrub('?')
|
@@ -14,10 +14,12 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
|
17
|
+
require 'fluent/win32api'
|
18
|
+
|
17
19
|
module Fluent
|
18
20
|
module FileWrapper
|
19
|
-
def self.open(
|
20
|
-
io = WindowsFile.new(
|
21
|
+
def self.open(path, mode='r')
|
22
|
+
io = WindowsFile.new(path, mode).io
|
21
23
|
if block_given?
|
22
24
|
v = yield io
|
23
25
|
io.close
|
@@ -35,126 +37,78 @@ module Fluent
|
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
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
40
|
class WindowsFile
|
93
|
-
|
94
|
-
require 'windows/error'
|
95
|
-
require 'windows/handle'
|
96
|
-
require 'windows/nio'
|
41
|
+
include File::Constants
|
97
42
|
|
98
|
-
|
99
|
-
include Windows::File
|
100
|
-
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
|
43
|
+
attr_reader :io
|
107
44
|
|
45
|
+
INVALID_HANDLE_VALUE = -1
|
108
46
|
|
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
|
47
|
+
def initialize(path, mode='r')
|
48
|
+
@path = path
|
49
|
+
@io = File.open(path, mode2flags(mode))
|
50
|
+
@file_handle = Win32API._get_osfhandle(@io.to_i)
|
51
|
+
@io.instance_variable_set(:@file_index, self.ino)
|
52
|
+
def @io.ino
|
53
|
+
@file_index
|
130
54
|
end
|
131
55
|
end
|
132
56
|
|
133
57
|
def close
|
134
|
-
|
58
|
+
@io.close
|
135
59
|
@file_handle = INVALID_HANDLE_VALUE
|
136
60
|
end
|
137
61
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
io.instance_variable_set :@path, @path
|
144
|
-
io.extend WindowsFileExtension
|
145
|
-
io
|
146
|
-
end
|
147
|
-
|
62
|
+
# To keep backward compatibility, we continue to use GetFileInformationByHandle()
|
63
|
+
# to get file id.
|
64
|
+
# Note that Ruby's File.stat uses GetFileInformationByHandleEx() with FileIdInfo
|
65
|
+
# and returned value is different with above one, former one is 64 bit while
|
66
|
+
# later one is 128bit.
|
148
67
|
def ino
|
149
68
|
by_handle_file_information = '\0'*(4+8+8+8+4+4+4+4+4+4) #72bytes
|
150
69
|
|
151
|
-
unless GetFileInformationByHandle
|
70
|
+
unless Win32API.GetFileInformationByHandle(@file_handle, by_handle_file_information)
|
152
71
|
return 0
|
153
72
|
end
|
154
73
|
|
155
74
|
by_handle_file_information.unpack("I11Q1")[11] # fileindex
|
156
75
|
end
|
157
76
|
|
77
|
+
def stat
|
78
|
+
raise Errno::ENOENT if delete_pending
|
79
|
+
s = File.stat(@path)
|
80
|
+
s.instance_variable_set :@ino, self.ino
|
81
|
+
def s.ino; @ino; end
|
82
|
+
s
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def mode2flags(mode)
|
88
|
+
# Always inject File::Constants::SHARE_DELETE
|
89
|
+
# https://github.com/fluent/fluentd/pull/3585#issuecomment-1101502617
|
90
|
+
# To enable SHARE_DELETE, BINARY is also required.
|
91
|
+
# https://bugs.ruby-lang.org/issues/11218
|
92
|
+
# https://github.com/ruby/ruby/blob/d6684f063bc53e3cab025bd39526eca3b480b5e7/win32/win32.c#L6332-L6345
|
93
|
+
flags = BINARY | SHARE_DELETE
|
94
|
+
case mode.delete("b")
|
95
|
+
when "r"
|
96
|
+
flags |= RDONLY
|
97
|
+
when "r+"
|
98
|
+
flags |= RDWR
|
99
|
+
when "w"
|
100
|
+
flags |= WRONLY | CREAT | TRUNC
|
101
|
+
when "w+"
|
102
|
+
flags |= RDWR | CREAT | TRUNC
|
103
|
+
when "a"
|
104
|
+
flags |= WRONLY | CREAT | APPEND
|
105
|
+
when "a+"
|
106
|
+
flags |= RDWR | CREAT | APPEND
|
107
|
+
else
|
108
|
+
raise Errno::EINVAL.new("Unsupported mode by Fluent::FileWrapper: #{mode}")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
158
112
|
# DeletePending is a Windows-specific file state that roughly means
|
159
113
|
# "this file is queued for deletion, so close any open handlers"
|
160
114
|
#
|
@@ -167,21 +121,11 @@ module Fluent
|
|
167
121
|
bufsize = 1024
|
168
122
|
buf = '\0' * bufsize
|
169
123
|
|
170
|
-
unless GetFileInformationByHandleEx
|
124
|
+
unless Win32API.GetFileInformationByHandleEx(@file_handle, file_standard_info, buf, bufsize)
|
171
125
|
return false
|
172
126
|
end
|
173
127
|
|
174
128
|
return buf.unpack("QQICC")[3] != 0
|
175
129
|
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
130
|
end
|
187
131
|
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
|