fluentd 1.12.4-x86-mingw32 → 1.13.3-x86-mingw32
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/ISSUE_TEMPLATE/bug_report.yaml +69 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yaml +38 -0
- data/.github/workflows/linux-test.yaml +1 -1
- data/.github/workflows/windows-test.yaml +14 -3
- data/.gitlab-ci.yml +0 -22
- data/CHANGELOG.md +129 -0
- data/CONTRIBUTING.md +2 -2
- data/MAINTAINERS.md +1 -1
- data/README.md +3 -3
- data/bin/fluentd +8 -1
- data/example/counter.conf +1 -1
- data/example/v0_12_filter.conf +2 -2
- data/fluentd.gemspec +1 -1
- data/lib/fluent/command/cat.rb +19 -3
- data/lib/fluent/command/fluentd.rb +1 -2
- data/lib/fluent/command/plugin_generator.rb +15 -5
- data/lib/fluent/config.rb +1 -1
- data/lib/fluent/config/section.rb +5 -0
- data/lib/fluent/config/types.rb +15 -0
- data/lib/fluent/config/v1_parser.rb +3 -2
- data/lib/fluent/env.rb +2 -1
- data/lib/fluent/log.rb +1 -0
- data/lib/fluent/oj_options.rb +62 -0
- data/lib/fluent/plugin/file_wrapper.rb +35 -4
- data/lib/fluent/plugin/formatter.rb +1 -0
- data/lib/fluent/plugin/formatter_json.rb +9 -7
- data/lib/fluent/plugin/in_http.rb +10 -0
- data/lib/fluent/plugin/in_tail.rb +159 -41
- data/lib/fluent/plugin/in_tail/position_file.rb +20 -18
- data/lib/fluent/plugin/out_forward.rb +14 -33
- data/lib/fluent/plugin/parser_json.rb +2 -3
- data/lib/fluent/plugin/service_discovery.rb +0 -15
- data/lib/fluent/plugin_helper/http_server/router.rb +1 -1
- data/lib/fluent/plugin_helper/service_discovery.rb +39 -1
- data/lib/fluent/plugin_helper/service_discovery/manager.rb +11 -5
- data/lib/fluent/supervisor.rb +15 -0
- data/lib/fluent/system_config.rb +14 -0
- data/lib/fluent/test/driver/storage.rb +30 -0
- data/lib/fluent/version.rb +1 -1
- data/templates/new_gem/lib/fluent/plugin/storage.rb.erb +40 -0
- data/templates/new_gem/test/plugin/test_storage.rb.erb +18 -0
- data/test/command/test_cat.rb +99 -0
- data/test/command/test_plugin_generator.rb +2 -1
- data/test/config/test_section.rb +9 -0
- data/test/config/test_system_config.rb +46 -0
- data/test/config/test_types.rb +7 -0
- data/test/plugin/in_tail/test_io_handler.rb +4 -4
- data/test/plugin/in_tail/test_position_file.rb +23 -5
- data/test/plugin/test_file_wrapper.rb +22 -1
- data/test/plugin/test_in_forward.rb +59 -83
- data/test/plugin/test_in_http.rb +58 -40
- data/test/plugin/test_in_syslog.rb +66 -56
- data/test/plugin/test_in_tail.rb +341 -1
- data/test/plugin/test_in_tcp.rb +45 -32
- data/test/plugin/test_in_udp.rb +47 -33
- data/test/plugin/test_out_forward.rb +114 -95
- data/test/plugin/test_out_stream.rb +18 -8
- data/test/plugin_helper/http_server/test_route.rb +1 -1
- data/test/plugin_helper/test_child_process.rb +1 -1
- data/test/plugin_helper/test_http_server_helper.rb +33 -26
- data/test/plugin_helper/test_server.rb +137 -138
- data/test/plugin_helper/test_service_discovery.rb +74 -14
- data/test/plugin_helper/test_socket.rb +16 -9
- data/test/test_config.rb +2 -1
- data/test/test_event_time.rb +2 -2
- data/test/test_oj_options.rb +55 -0
- data/test/test_supervisor.rb +35 -0
- metadata +15 -7
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
@@ -85,9 +85,8 @@ op.on('-o', '--log PATH', "log file path") {|s|
|
|
85
85
|
opts[:log_path] = s
|
86
86
|
}
|
87
87
|
|
88
|
-
ROTATE_AGE = %w(daily weekly monthly)
|
89
88
|
op.on('--log-rotate-age AGE', 'generations to keep rotated log files') {|age|
|
90
|
-
if
|
89
|
+
if Fluent::Log::LOG_ROTATE_AGE.include?(age)
|
91
90
|
opts[:log_rotate_age] = age
|
92
91
|
else
|
93
92
|
begin
|
@@ -27,7 +27,7 @@ class FluentPluginGenerator
|
|
27
27
|
attr_reader :type, :name
|
28
28
|
attr_reader :license_name
|
29
29
|
|
30
|
-
SUPPORTED_TYPES = ["input", "output", "filter", "parser", "formatter"]
|
30
|
+
SUPPORTED_TYPES = ["input", "output", "filter", "parser", "formatter", "storage"]
|
31
31
|
|
32
32
|
def initialize(argv = ARGV)
|
33
33
|
@argv = argv
|
@@ -164,8 +164,13 @@ BANNER
|
|
164
164
|
end
|
165
165
|
|
166
166
|
def locked_gem_version(gem_name)
|
167
|
-
|
168
|
-
|
167
|
+
if File.exist?(lock_file_path)
|
168
|
+
d = Bundler::Definition.build(gem_file_path, lock_file_path, false)
|
169
|
+
d.locked_gems.dependencies[gem_name].requirement.requirements.first.last.version
|
170
|
+
else
|
171
|
+
# fallback even though Fluentd is installed without bundler
|
172
|
+
Gem::Specification.find_by_name(gem_name).version.version
|
173
|
+
end
|
169
174
|
end
|
170
175
|
|
171
176
|
def rake_version
|
@@ -177,8 +182,13 @@ BANNER
|
|
177
182
|
end
|
178
183
|
|
179
184
|
def bundler_version
|
180
|
-
|
181
|
-
|
185
|
+
if File.exist?(lock_file_path)
|
186
|
+
d = Bundler::Definition.build(gem_file_path, lock_file_path, false)
|
187
|
+
d.locked_gems.bundler_version.version
|
188
|
+
else
|
189
|
+
# fallback even though Fluentd is installed without bundler
|
190
|
+
Gem::Specification.find_by_name("bundler").version.version
|
191
|
+
end
|
182
192
|
end
|
183
193
|
|
184
194
|
def class_name
|
data/lib/fluent/config.rb
CHANGED
@@ -62,7 +62,7 @@ module Fluent
|
|
62
62
|
Parser.parse(str, fname, basepath)
|
63
63
|
when :ruby
|
64
64
|
require 'fluent/config/dsl'
|
65
|
-
$log.warn("Ruby DSL configuration format is deprecated. Please use original configuration format. https://docs.fluentd.org/configuration/config-file")
|
65
|
+
$log.warn("Ruby DSL configuration format is deprecated. Please use original configuration format. https://docs.fluentd.org/configuration/config-file") if $log
|
66
66
|
Config::DSL::Parser.parse(str, File.join(basepath, fname))
|
67
67
|
else
|
68
68
|
raise "[BUG] unknown configuration parser specification:'#{parser}'"
|
data/lib/fluent/config/types.rb
CHANGED
@@ -20,6 +20,10 @@ require 'fluent/config/error'
|
|
20
20
|
|
21
21
|
module Fluent
|
22
22
|
module Config
|
23
|
+
def self.reformatted_value(type, val, opts = {}, name = nil)
|
24
|
+
REFORMAT_VALUE.call(type, val, opts, name)
|
25
|
+
end
|
26
|
+
|
23
27
|
def self.size_value(str, opts = {}, name = nil)
|
24
28
|
return nil if str.nil?
|
25
29
|
|
@@ -104,6 +108,16 @@ module Fluent
|
|
104
108
|
Config.string_value(val, opts, name)
|
105
109
|
}
|
106
110
|
|
111
|
+
def self.symbol_value(val, opts = {}, name = nil)
|
112
|
+
return nil if val.nil? || val.empty?
|
113
|
+
|
114
|
+
val.delete_prefix(":").to_sym
|
115
|
+
end
|
116
|
+
|
117
|
+
SYMBOL_TYPE = Proc.new { |val, opts = {}, name = nil|
|
118
|
+
Config.symbol_value(val, opts, name)
|
119
|
+
}
|
120
|
+
|
107
121
|
def self.enum_value(val, opts = {}, name = nil)
|
108
122
|
return nil if val.nil?
|
109
123
|
|
@@ -176,6 +190,7 @@ module Fluent
|
|
176
190
|
when :bool then Config.bool_value(value, opts, name)
|
177
191
|
when :time then Config.time_value(value, opts, name)
|
178
192
|
when :regexp then Config.regexp_value(value, opts, name)
|
193
|
+
when :symbol then Config.symbol_value(value, opts, name)
|
179
194
|
else
|
180
195
|
raise "unknown type in REFORMAT: #{type}"
|
181
196
|
end
|
@@ -37,6 +37,7 @@ module Fluent
|
|
37
37
|
super(strscan, eval_context)
|
38
38
|
@include_basepath = include_basepath
|
39
39
|
@fname = fname
|
40
|
+
@logger = defined?($log) ? $log : nil
|
40
41
|
end
|
41
42
|
|
42
43
|
def parse!
|
@@ -99,7 +100,7 @@ module Fluent
|
|
99
100
|
|
100
101
|
elsif root_element && skip(/(\@include|include)#{SPACING}/)
|
101
102
|
if !prev_match.start_with?('@')
|
102
|
-
|
103
|
+
@logger.warn "'include' is deprecated. Use '@include' instead" if @logger
|
103
104
|
end
|
104
105
|
parse_include(attrs, elems)
|
105
106
|
|
@@ -123,7 +124,7 @@ module Fluent
|
|
123
124
|
parse_error! "'@' is the system reserved prefix. Don't use '@' prefix parameter in the configuration: #{k}"
|
124
125
|
else
|
125
126
|
# TODO: This is for backward compatibility. It will throw an error in the future.
|
126
|
-
|
127
|
+
@logger.warn "'@' is the system reserved prefix. It works in the nested configuration for now but it will be rejected: #{k}" if @logger
|
127
128
|
end
|
128
129
|
end
|
129
130
|
|
data/lib/fluent/env.rb
CHANGED
@@ -15,13 +15,14 @@
|
|
15
15
|
#
|
16
16
|
|
17
17
|
require 'serverengine/utils'
|
18
|
+
require 'fluent/oj_options'
|
18
19
|
|
19
20
|
module Fluent
|
20
21
|
DEFAULT_CONFIG_PATH = ENV['FLUENT_CONF'] || '/etc/fluent/fluent.conf'
|
21
22
|
DEFAULT_PLUGIN_DIR = ENV['FLUENT_PLUGIN'] || '/etc/fluent/plugin'
|
22
23
|
DEFAULT_SOCKET_PATH = ENV['FLUENT_SOCKET'] || '/var/run/fluent/fluent.sock'
|
23
24
|
DEFAULT_BACKUP_DIR = ENV['FLUENT_BACKUP_DIR'] || '/tmp/fluent'
|
24
|
-
DEFAULT_OJ_OPTIONS =
|
25
|
+
DEFAULT_OJ_OPTIONS = Fluent::OjOptions.load_env
|
25
26
|
DEFAULT_DIR_PERMISSION = 0755
|
26
27
|
DEFAULT_FILE_PERMISSION = 0644
|
27
28
|
|
data/lib/fluent/log.rb
CHANGED
@@ -49,6 +49,7 @@ module Fluent
|
|
49
49
|
LOG_TYPE_DEFAULT = :default # show logs in all supervisor/workers, with worker id in workers (default)
|
50
50
|
|
51
51
|
LOG_TYPES = [LOG_TYPE_SUPERVISOR, LOG_TYPE_WORKER0, LOG_TYPE_DEFAULT].freeze
|
52
|
+
LOG_ROTATE_AGE = %w(daily weekly monthly)
|
52
53
|
|
53
54
|
def self.str_to_level(log_level_str)
|
54
55
|
case log_level_str.downcase
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'fluent/config/types'
|
2
|
+
|
3
|
+
module Fluent
|
4
|
+
class OjOptions
|
5
|
+
OPTIONS = {
|
6
|
+
'bigdecimal_load': :symbol,
|
7
|
+
'max_nesting': :integer,
|
8
|
+
'mode': :symbol,
|
9
|
+
'use_to_json': :bool
|
10
|
+
}
|
11
|
+
|
12
|
+
ALLOWED_VALUES = {
|
13
|
+
'bigdecimal_load': %i[bigdecimal float auto],
|
14
|
+
'mode': %i[strict null compat json rails object custom]
|
15
|
+
}
|
16
|
+
|
17
|
+
DEFAULTS = {
|
18
|
+
'bigdecimal_load': :float,
|
19
|
+
'mode': :compat,
|
20
|
+
'use_to_json': true
|
21
|
+
}
|
22
|
+
|
23
|
+
@@available = false
|
24
|
+
|
25
|
+
def self.available?
|
26
|
+
@@available
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.load_env
|
30
|
+
options = self.get_options
|
31
|
+
begin
|
32
|
+
require 'oj'
|
33
|
+
Oj.default_options = options
|
34
|
+
@@available = true
|
35
|
+
rescue LoadError
|
36
|
+
@@available = false
|
37
|
+
end
|
38
|
+
options
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def self.get_options
|
44
|
+
options = {}
|
45
|
+
DEFAULTS.each { |key, value| options[key] = value }
|
46
|
+
|
47
|
+
OPTIONS.each do |key, type|
|
48
|
+
env_value = ENV["FLUENT_OJ_OPTION_#{key.upcase}"]
|
49
|
+
next if env_value.nil?
|
50
|
+
|
51
|
+
cast_value = Fluent::Config.reformatted_value(OPTIONS[key], env_value, { strict: true })
|
52
|
+
next if cast_value.nil?
|
53
|
+
|
54
|
+
next if ALLOWED_VALUES[key] && !ALLOWED_VALUES[key].include?(cast_value)
|
55
|
+
|
56
|
+
options[key.to_sym] = cast_value
|
57
|
+
end
|
58
|
+
|
59
|
+
options
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -52,6 +52,8 @@ module Fluent
|
|
52
52
|
|
53
53
|
attr_reader :errcode, :msg
|
54
54
|
|
55
|
+
WSABASEERR = 10000
|
56
|
+
|
55
57
|
def initialize(errcode, msg = nil)
|
56
58
|
@errcode = errcode
|
57
59
|
@msg = msg
|
@@ -80,6 +82,10 @@ module Fluent
|
|
80
82
|
return false if other.class != Win32Error
|
81
83
|
@errcode == other.errcode && @msg == other.msg
|
82
84
|
end
|
85
|
+
|
86
|
+
def wsaerr?
|
87
|
+
@errcode >= WSABASEERR
|
88
|
+
end
|
83
89
|
end
|
84
90
|
|
85
91
|
# To open and get stat with setting FILE_SHARE_DELETE
|
@@ -113,11 +119,14 @@ module Fluent
|
|
113
119
|
@file_handle = CreateFile.call(@path, access, sharemode,
|
114
120
|
0, creationdisposition, FILE_ATTRIBUTE_NORMAL, 0)
|
115
121
|
if @file_handle == INVALID_HANDLE_VALUE
|
116
|
-
|
117
|
-
|
118
|
-
|
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)
|
119
129
|
end
|
120
|
-
raise Win32Error.new(err, path)
|
121
130
|
end
|
122
131
|
end
|
123
132
|
|
@@ -146,7 +155,29 @@ module Fluent
|
|
146
155
|
by_handle_file_information.unpack("I11Q1")[11] # fileindex
|
147
156
|
end
|
148
157
|
|
158
|
+
# DeletePending is a Windows-specific file state that roughly means
|
159
|
+
# "this file is queued for deletion, so close any open handlers"
|
160
|
+
#
|
161
|
+
# This flag can be retrieved via GetFileInformationByHandleEx().
|
162
|
+
#
|
163
|
+
# https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getfileinformationbyhandleex
|
164
|
+
#
|
165
|
+
def delete_pending
|
166
|
+
file_standard_info = 0x01
|
167
|
+
bufsize = 1024
|
168
|
+
buf = '\0' * bufsize
|
169
|
+
|
170
|
+
unless GetFileInformationByHandleEx.call(@file_handle, file_standard_info, buf, bufsize)
|
171
|
+
return false
|
172
|
+
end
|
173
|
+
|
174
|
+
return buf.unpack("QQICC")[3] != 0
|
175
|
+
end
|
176
|
+
|
177
|
+
private :delete_pending
|
178
|
+
|
149
179
|
def stat
|
180
|
+
raise Errno::ENOENT if delete_pending
|
150
181
|
s = File.stat(@path)
|
151
182
|
s.instance_variable_set :@ino, self.ino
|
152
183
|
def s.ino; @ino; end
|
@@ -15,7 +15,7 @@
|
|
15
15
|
#
|
16
16
|
|
17
17
|
require 'fluent/plugin/formatter'
|
18
|
-
require 'fluent/
|
18
|
+
require 'fluent/oj_options'
|
19
19
|
|
20
20
|
module Fluent
|
21
21
|
module Plugin
|
@@ -30,12 +30,14 @@ module Fluent
|
|
30
30
|
def configure(conf)
|
31
31
|
super
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
33
|
+
if @json_parser == 'oj'
|
34
|
+
if Fluent::OjOptions.available?
|
35
|
+
@dump_proc = Oj.method(:dump)
|
36
|
+
else
|
37
|
+
log.info "Oj isn't installed, fallback to Yajl as json parser"
|
38
|
+
@dump_proc = Yajl.method(:dump)
|
39
|
+
end
|
40
|
+
else
|
39
41
|
@dump_proc = Yajl.method(:dump)
|
40
42
|
end
|
41
43
|
|
@@ -461,6 +461,12 @@ module Fluent::Plugin
|
|
461
461
|
RES_200_STATUS = "200 OK".freeze
|
462
462
|
RES_403_STATUS = "403 Forbidden".freeze
|
463
463
|
|
464
|
+
# Azure App Service sends GET requests for health checking purpose.
|
465
|
+
# Respond with `200 OK` to accommodate it.
|
466
|
+
def handle_get_request
|
467
|
+
return send_response_and_close(RES_200_STATUS, {}, "")
|
468
|
+
end
|
469
|
+
|
464
470
|
# Web browsers can send an OPTIONS request before performing POST
|
465
471
|
# to check if cross-origin requests are supported.
|
466
472
|
def handle_options_request
|
@@ -494,6 +500,10 @@ module Fluent::Plugin
|
|
494
500
|
def on_message_complete
|
495
501
|
return if closing?
|
496
502
|
|
503
|
+
if @parser.http_method == 'GET'.freeze
|
504
|
+
return handle_get_request()
|
505
|
+
end
|
506
|
+
|
497
507
|
if @parser.http_method == 'OPTIONS'.freeze
|
498
508
|
return handle_options_request()
|
499
509
|
end
|
@@ -56,6 +56,7 @@ module Fluent::Plugin
|
|
56
56
|
@pf_file = nil
|
57
57
|
@pf = nil
|
58
58
|
@ignore_list = []
|
59
|
+
@shutdown_start_time = nil
|
59
60
|
end
|
60
61
|
|
61
62
|
desc 'The paths to read. Multiple paths can be specified, separated by comma.'
|
@@ -81,6 +82,8 @@ module Fluent::Plugin
|
|
81
82
|
config_param :refresh_interval, :time, default: 60
|
82
83
|
desc 'The number of reading lines at each IO.'
|
83
84
|
config_param :read_lines_limit, :integer, default: 1000
|
85
|
+
desc 'The number of reading bytes per second'
|
86
|
+
config_param :read_bytes_limit_per_second, :size, default: -1
|
84
87
|
desc 'The interval of flushing the buffer for multiline format'
|
85
88
|
config_param :multiline_flush_interval, :time, default: nil
|
86
89
|
desc 'Enable the option to emit unmatched lines.'
|
@@ -178,6 +181,16 @@ module Fluent::Plugin
|
|
178
181
|
# parser is already created by parser helper
|
179
182
|
@parser = parser_create(usage: parser_config['usage'] || @parser_configs.first.usage)
|
180
183
|
@capability = Fluent::Capability.new(:current_process)
|
184
|
+
if @read_bytes_limit_per_second > 0
|
185
|
+
if !@enable_watch_timer
|
186
|
+
raise Fluent::ConfigError, "Need to enable watch timer when using log throttling feature"
|
187
|
+
end
|
188
|
+
min_bytes = TailWatcher::IOHandler::BYTES_TO_READ
|
189
|
+
if @read_bytes_limit_per_second < min_bytes
|
190
|
+
log.warn "Should specify greater equal than #{min_bytes}. Use #{min_bytes} for read_bytes_limit_per_second"
|
191
|
+
@read_bytes_limit_per_second = min_bytes
|
192
|
+
end
|
193
|
+
end
|
181
194
|
end
|
182
195
|
|
183
196
|
def configure_tag
|
@@ -244,6 +257,7 @@ module Fluent::Plugin
|
|
244
257
|
end
|
245
258
|
|
246
259
|
def shutdown
|
260
|
+
@shutdown_start_time = Fluent::Clock.now
|
247
261
|
# during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
|
248
262
|
stop_watchers(existence_path, immediate: true, remove_watcher: false)
|
249
263
|
@pf_file.close if @pf_file
|
@@ -290,7 +304,7 @@ module Fluent::Plugin
|
|
290
304
|
end
|
291
305
|
false
|
292
306
|
end
|
293
|
-
rescue Errno::ENOENT
|
307
|
+
rescue Errno::ENOENT, Errno::EACCES
|
294
308
|
log.debug("#{p} is missing after refresh file list")
|
295
309
|
false
|
296
310
|
end
|
@@ -322,8 +336,8 @@ module Fluent::Plugin
|
|
322
336
|
else
|
323
337
|
hash[target_info.path] = target_info
|
324
338
|
end
|
325
|
-
rescue Errno::ENOENT
|
326
|
-
$log.warn "expand_paths: stat() for #{path} failed with
|
339
|
+
rescue Errno::ENOENT, Errno::EACCES => e
|
340
|
+
$log.warn "expand_paths: stat() for #{path} failed with #{e.class.name}. Skip file."
|
327
341
|
end
|
328
342
|
}
|
329
343
|
hash
|
@@ -373,8 +387,6 @@ module Fluent::Plugin
|
|
373
387
|
tw.register_watcher(tt)
|
374
388
|
end
|
375
389
|
|
376
|
-
tw.on_notify
|
377
|
-
|
378
390
|
tw.watchers.each do |watcher|
|
379
391
|
event_loop_attach(watcher)
|
380
392
|
end
|
@@ -386,42 +398,49 @@ module Fluent::Plugin
|
|
386
398
|
event_loop_detach(watcher)
|
387
399
|
end
|
388
400
|
|
389
|
-
tw.detach
|
401
|
+
tw.detach(@shutdown_start_time)
|
390
402
|
tw.close
|
391
403
|
end
|
392
404
|
raise e
|
393
405
|
end
|
394
406
|
|
395
|
-
def
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
$log.warn "#{target_info.path} not found. Continuing without tailing it."
|
405
|
-
end
|
407
|
+
def construct_watcher(target_info)
|
408
|
+
pe = nil
|
409
|
+
if @pf
|
410
|
+
pe = @pf[target_info]
|
411
|
+
if @read_from_head && pe.read_inode.zero?
|
412
|
+
begin
|
413
|
+
pe.update(Fluent::FileWrapper.stat(target_info.path).ino, 0)
|
414
|
+
rescue Errno::ENOENT, Errno::EACCES
|
415
|
+
$log.warn "stat() for #{target_info.path} failed. Continuing without tailing it."
|
406
416
|
end
|
407
417
|
end
|
418
|
+
end
|
408
419
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
420
|
+
begin
|
421
|
+
tw = setup_watcher(target_info, pe)
|
422
|
+
rescue WatcherSetupError => e
|
423
|
+
log.warn "Skip #{target_info.path} because unexpected setup error happens: #{e}"
|
424
|
+
return
|
425
|
+
end
|
415
426
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
427
|
+
begin
|
428
|
+
target_info = TargetInfo.new(target_info.path, Fluent::FileWrapper.stat(target_info.path).ino)
|
429
|
+
@tails.delete(target_info)
|
430
|
+
@tails[target_info] = tw
|
431
|
+
tw.on_notify
|
432
|
+
rescue Errno::ENOENT, Errno::EACCES => e
|
433
|
+
$log.warn "stat() for #{target_info.path} failed with #{e.class.name}. Drop tail watcher for now."
|
434
|
+
# explicitly detach and unwatch watcher `tw`.
|
435
|
+
tw.unwatched = true
|
436
|
+
detach_watcher(tw, target_info.ino, false)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def start_watchers(targets_info)
|
441
|
+
targets_info.each_value {|target_info|
|
442
|
+
construct_watcher(target_info)
|
443
|
+
break if before_shutdown?
|
425
444
|
}
|
426
445
|
end
|
427
446
|
|
@@ -473,10 +492,19 @@ module Fluent::Plugin
|
|
473
492
|
new_position_entry = @pf[target_info]
|
474
493
|
|
475
494
|
if new_position_entry.read_inode == 0
|
495
|
+
# When follow_inodes is true, it's not cleaned up by refresh_watcher.
|
496
|
+
# So it should be unwatched here explicitly.
|
497
|
+
rotated_tw.unwatched = true
|
498
|
+
# Make sure to delete old key, it has a different ino while the hash key is same.
|
499
|
+
@tails.delete(rotated_target_info)
|
476
500
|
@tails[new_target_info] = setup_watcher(new_target_info, new_position_entry)
|
501
|
+
@tails[new_target_info].on_notify
|
477
502
|
end
|
478
503
|
else
|
504
|
+
# Make sure to delete old key, it has a different ino while the hash key is same.
|
505
|
+
@tails.delete(rotated_target_info)
|
479
506
|
@tails[new_target_info] = setup_watcher(new_target_info, pe)
|
507
|
+
@tails[new_target_info].on_notify
|
480
508
|
end
|
481
509
|
detach_watcher_after_rotate_wait(rotated_tw, pe.read_inode) if rotated_tw
|
482
510
|
end
|
@@ -489,7 +517,7 @@ module Fluent::Plugin
|
|
489
517
|
tw.watchers.each do |watcher|
|
490
518
|
event_loop_detach(watcher)
|
491
519
|
end
|
492
|
-
tw.detach
|
520
|
+
tw.detach(@shutdown_start_time)
|
493
521
|
|
494
522
|
tw.close if close_io
|
495
523
|
|
@@ -502,8 +530,25 @@ module Fluent::Plugin
|
|
502
530
|
def detach_watcher_after_rotate_wait(tw, ino)
|
503
531
|
# Call event_loop_attach/event_loop_detach is high-cost for short-live object.
|
504
532
|
# If this has a problem with large number of files, use @_event_loop directly instead of timer_execute.
|
505
|
-
|
533
|
+
if @open_on_every_update
|
534
|
+
# Detach now because it's already closed, waiting it doesn't make sense.
|
506
535
|
detach_watcher(tw, ino)
|
536
|
+
elsif @read_bytes_limit_per_second < 0
|
537
|
+
# throttling isn't enabled, just wait @rotate_wait
|
538
|
+
timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
|
539
|
+
detach_watcher(tw, ino)
|
540
|
+
end
|
541
|
+
else
|
542
|
+
# When the throttling feature is enabled, it might not reach EOF yet.
|
543
|
+
# Should ensure to read all contents before closing it, with keeping throttling.
|
544
|
+
start_time_to_wait = Fluent::Clock.now
|
545
|
+
timer = timer_execute(:in_tail_close_watcher, 1, repeat: true) do
|
546
|
+
elapsed = Fluent::Clock.now - start_time_to_wait
|
547
|
+
if tw.eof? && elapsed >= @rotate_wait
|
548
|
+
timer.detach
|
549
|
+
detach_watcher(tw, ino)
|
550
|
+
end
|
551
|
+
end
|
507
552
|
end
|
508
553
|
end
|
509
554
|
|
@@ -633,6 +678,7 @@ module Fluent::Plugin
|
|
633
678
|
path: path,
|
634
679
|
log: log,
|
635
680
|
read_lines_limit: @read_lines_limit,
|
681
|
+
read_bytes_limit_per_second: @read_bytes_limit_per_second,
|
636
682
|
open_on_every_update: @open_on_every_update,
|
637
683
|
from_encoding: @from_encoding,
|
638
684
|
encoding: @encoding,
|
@@ -700,8 +746,11 @@ module Fluent::Plugin
|
|
700
746
|
@watchers << watcher
|
701
747
|
end
|
702
748
|
|
703
|
-
def detach
|
704
|
-
|
749
|
+
def detach(shutdown_start_time = nil)
|
750
|
+
if @io_handler
|
751
|
+
@io_handler.ready_to_shutdown(shutdown_start_time)
|
752
|
+
@io_handler.on_notify
|
753
|
+
end
|
705
754
|
@line_buffer_timer_flusher&.close(self)
|
706
755
|
end
|
707
756
|
|
@@ -712,10 +761,14 @@ module Fluent::Plugin
|
|
712
761
|
end
|
713
762
|
end
|
714
763
|
|
764
|
+
def eof?
|
765
|
+
@io_handler.eof?
|
766
|
+
end
|
767
|
+
|
715
768
|
def on_notify
|
716
769
|
begin
|
717
770
|
stat = Fluent::FileWrapper.stat(@path)
|
718
|
-
rescue Errno::ENOENT
|
771
|
+
rescue Errno::ENOENT, Errno::EACCES
|
719
772
|
# moved or deleted
|
720
773
|
stat = nil
|
721
774
|
end
|
@@ -786,7 +839,7 @@ module Fluent::Plugin
|
|
786
839
|
# new watcher, and old watcher will be closed by stop_watcher in refresh_watchers method
|
787
840
|
# don't want to swap state because we need latest read offset in pos file even after rotate_wait
|
788
841
|
if stat
|
789
|
-
target_info = TargetInfo.new(@path, stat)
|
842
|
+
target_info = TargetInfo.new(@path, stat.ino)
|
790
843
|
@update_watcher.call(target_info, @pe)
|
791
844
|
end
|
792
845
|
else
|
@@ -876,10 +929,16 @@ module Fluent::Plugin
|
|
876
929
|
end
|
877
930
|
|
878
931
|
class IOHandler
|
879
|
-
|
932
|
+
BYTES_TO_READ = 8192
|
933
|
+
SHUTDOWN_TIMEOUT = 5
|
934
|
+
|
935
|
+
attr_accessor :shutdown_timeout
|
936
|
+
|
937
|
+
def initialize(watcher, path:, read_lines_limit:, read_bytes_limit_per_second:, log:, open_on_every_update:, from_encoding: nil, encoding: nil, &receive_lines)
|
880
938
|
@watcher = watcher
|
881
939
|
@path = path
|
882
940
|
@read_lines_limit = read_lines_limit
|
941
|
+
@read_bytes_limit_per_second = read_bytes_limit_per_second
|
883
942
|
@receive_lines = receive_lines
|
884
943
|
@open_on_every_update = open_on_every_update
|
885
944
|
@fifo = FIFO.new(from_encoding || Encoding::ASCII_8BIT, encoding || Encoding::ASCII_8BIT)
|
@@ -888,6 +947,12 @@ module Fluent::Plugin
|
|
888
947
|
@io = nil
|
889
948
|
@notify_mutex = Mutex.new
|
890
949
|
@log = log
|
950
|
+
@start_reading_time = nil
|
951
|
+
@number_bytes_read = 0
|
952
|
+
@shutdown_start_time = nil
|
953
|
+
@shutdown_timeout = SHUTDOWN_TIMEOUT
|
954
|
+
@shutdown_mutex = Mutex.new
|
955
|
+
@eof = false
|
891
956
|
|
892
957
|
@log.info "following tail of #{@path}"
|
893
958
|
end
|
@@ -896,6 +961,13 @@ module Fluent::Plugin
|
|
896
961
|
@notify_mutex.synchronize { handle_notify }
|
897
962
|
end
|
898
963
|
|
964
|
+
def ready_to_shutdown(shutdown_start_time = nil)
|
965
|
+
@shutdown_mutex.synchronize {
|
966
|
+
@shutdown_start_time =
|
967
|
+
shutdown_start_time || Fluent::Clock.now
|
968
|
+
}
|
969
|
+
end
|
970
|
+
|
899
971
|
def close
|
900
972
|
if @io && !@io.closed?
|
901
973
|
@io.close
|
@@ -907,9 +979,41 @@ module Fluent::Plugin
|
|
907
979
|
!!@io
|
908
980
|
end
|
909
981
|
|
982
|
+
def eof?
|
983
|
+
@eof
|
984
|
+
end
|
985
|
+
|
910
986
|
private
|
911
987
|
|
988
|
+
def limit_bytes_per_second_reached?
|
989
|
+
return false if @read_bytes_limit_per_second < 0 # not enabled by conf
|
990
|
+
return false if @number_bytes_read < @read_bytes_limit_per_second
|
991
|
+
|
992
|
+
@start_reading_time ||= Fluent::Clock.now
|
993
|
+
time_spent_reading = Fluent::Clock.now - @start_reading_time
|
994
|
+
@log.debug("time_spent_reading: #{time_spent_reading} #{ @watcher.path}")
|
995
|
+
|
996
|
+
if time_spent_reading < 1
|
997
|
+
true
|
998
|
+
else
|
999
|
+
@start_reading_time = nil
|
1000
|
+
@number_bytes_read = 0
|
1001
|
+
false
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
def should_shutdown_now?
|
1006
|
+
# Ensure to read all remaining lines, but abort immediately if it
|
1007
|
+
# seems to take too long time.
|
1008
|
+
@shutdown_mutex.synchronize {
|
1009
|
+
return false if @shutdown_start_time.nil?
|
1010
|
+
return Fluent::Clock.now - @shutdown_start_time > @shutdown_timeout
|
1011
|
+
}
|
1012
|
+
end
|
1013
|
+
|
912
1014
|
def handle_notify
|
1015
|
+
return if limit_bytes_per_second_reached?
|
1016
|
+
|
913
1017
|
with_io do |io|
|
914
1018
|
begin
|
915
1019
|
read_more = false
|
@@ -917,8 +1021,18 @@ module Fluent::Plugin
|
|
917
1021
|
if !io.nil? && @lines.empty?
|
918
1022
|
begin
|
919
1023
|
while true
|
920
|
-
@
|
1024
|
+
@start_reading_time ||= Fluent::Clock.now
|
1025
|
+
data = io.readpartial(BYTES_TO_READ, @iobuf)
|
1026
|
+
@eof = false
|
1027
|
+
@number_bytes_read += data.bytesize
|
1028
|
+
@fifo << data
|
921
1029
|
@fifo.read_lines(@lines)
|
1030
|
+
|
1031
|
+
if limit_bytes_per_second_reached? || should_shutdown_now?
|
1032
|
+
# Just get out from tailing loop.
|
1033
|
+
read_more = false
|
1034
|
+
break
|
1035
|
+
end
|
922
1036
|
if @lines.size >= @read_lines_limit
|
923
1037
|
# not to use too much memory in case the file is very large
|
924
1038
|
read_more = true
|
@@ -926,6 +1040,7 @@ module Fluent::Plugin
|
|
926
1040
|
end
|
927
1041
|
end
|
928
1042
|
rescue EOFError
|
1043
|
+
@eof = true
|
929
1044
|
end
|
930
1045
|
end
|
931
1046
|
|
@@ -948,7 +1063,7 @@ module Fluent::Plugin
|
|
948
1063
|
rescue RangeError
|
949
1064
|
io.close if io
|
950
1065
|
raise WatcherSetupError, "seek error with #{@path}: file position = #{@watcher.pe.read_pos.to_s(16)}, reading bytesize = #{@fifo.bytesize.to_s(16)}"
|
951
|
-
rescue Errno::ENOENT
|
1066
|
+
rescue Errno::ENOENT, Errno::EACCES
|
952
1067
|
nil
|
953
1068
|
end
|
954
1069
|
|
@@ -963,14 +1078,17 @@ module Fluent::Plugin
|
|
963
1078
|
else
|
964
1079
|
@io ||= open
|
965
1080
|
yield @io
|
1081
|
+
@eof = true if @io.nil?
|
966
1082
|
end
|
967
1083
|
rescue WatcherSetupError => e
|
968
1084
|
close
|
1085
|
+
@eof = true
|
969
1086
|
raise e
|
970
1087
|
rescue
|
971
1088
|
@log.error $!.to_s
|
972
1089
|
@log.error_backtrace
|
973
1090
|
close
|
1091
|
+
@eof = true
|
974
1092
|
end
|
975
1093
|
end
|
976
1094
|
|