fluentd 1.11.4-x86-mingw32 → 1.12.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/.deepsource.toml +13 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- data/.github/ISSUE_TEMPLATE/config.yml +5 -0
- data/.github/workflows/linux-test.yaml +36 -0
- data/.github/workflows/macos-test.yaml +30 -0
- data/.github/workflows/stale-actions.yml +22 -0
- data/.github/workflows/windows-test.yaml +35 -0
- data/.gitlab-ci.yml +41 -19
- data/CHANGELOG.md +166 -0
- data/MAINTAINERS.md +5 -2
- data/README.md +7 -4
- data/bin/fluent-cap-ctl +7 -0
- data/bin/fluent-ctl +7 -0
- data/fluentd.gemspec +7 -5
- data/lib/fluent/capability.rb +87 -0
- data/lib/fluent/command/bundler_injection.rb +1 -1
- data/lib/fluent/command/ca_generate.rb +6 -3
- data/lib/fluent/command/cap_ctl.rb +174 -0
- data/lib/fluent/command/cat.rb +0 -1
- data/lib/fluent/command/ctl.rb +177 -0
- data/lib/fluent/command/fluentd.rb +4 -0
- data/lib/fluent/command/plugin_config_formatter.rb +18 -2
- data/lib/fluent/command/plugin_generator.rb +31 -1
- data/lib/fluent/compat/parser.rb +2 -2
- data/lib/fluent/config/section.rb +2 -2
- data/lib/fluent/config/types.rb +2 -2
- data/lib/fluent/env.rb +4 -0
- data/lib/fluent/event.rb +3 -13
- data/lib/fluent/load.rb +0 -1
- data/lib/fluent/plugin.rb +5 -0
- data/lib/fluent/plugin/buffer.rb +2 -21
- data/lib/fluent/plugin/file_wrapper.rb +39 -3
- data/lib/fluent/plugin/formatter.rb +24 -0
- data/lib/fluent/plugin/formatter_csv.rb +1 -1
- data/lib/fluent/plugin/formatter_hash.rb +3 -1
- data/lib/fluent/plugin/formatter_json.rb +3 -1
- data/lib/fluent/plugin/formatter_ltsv.rb +7 -5
- data/lib/fluent/plugin/formatter_out_file.rb +6 -4
- data/lib/fluent/plugin/formatter_single_value.rb +4 -2
- data/lib/fluent/plugin/formatter_tsv.rb +4 -2
- data/lib/fluent/plugin/in_http.rb +24 -3
- data/lib/fluent/plugin/in_monitor_agent.rb +1 -1
- data/lib/fluent/plugin/in_tail.rb +129 -41
- data/lib/fluent/plugin/in_tail/position_file.rb +39 -14
- data/lib/fluent/plugin/in_tcp.rb +1 -0
- data/lib/fluent/plugin/out_copy.rb +18 -5
- data/lib/fluent/plugin/out_exec_filter.rb +3 -3
- data/lib/fluent/plugin/out_forward.rb +61 -28
- data/lib/fluent/plugin/out_http.rb +28 -3
- data/lib/fluent/plugin/output.rb +18 -10
- data/lib/fluent/plugin/parser_csv.rb +2 -2
- data/lib/fluent/plugin/parser_syslog.rb +2 -2
- data/lib/fluent/plugin/storage_local.rb +4 -4
- data/lib/fluent/plugin_helper/http_server/compat/server.rb +1 -1
- data/lib/fluent/plugin_helper/inject.rb +4 -2
- data/lib/fluent/plugin_helper/retry_state.rb +4 -0
- data/lib/fluent/plugin_helper/server.rb +4 -2
- data/lib/fluent/plugin_helper/socket_option.rb +2 -2
- data/lib/fluent/supervisor.rb +153 -48
- data/lib/fluent/system_config.rb +2 -1
- data/lib/fluent/time.rb +58 -1
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/winsvc.rb +22 -4
- data/templates/new_gem/fluent-plugin.gemspec.erb +3 -3
- data/templates/plugin_config_formatter/param.md-table.erb +10 -0
- data/test/command/test_binlog_reader.rb +22 -6
- data/test/command/test_cap_ctl.rb +100 -0
- data/test/command/test_ctl.rb +57 -0
- data/test/command/test_fluentd.rb +38 -0
- data/test/command/test_plugin_config_formatter.rb +124 -2
- data/test/config/test_configurable.rb +1 -1
- data/test/plugin/in_tail/test_position_file.rb +46 -26
- data/test/plugin/test_file_wrapper.rb +105 -0
- data/test/plugin/test_filter_stdout.rb +6 -1
- data/test/plugin/test_formatter_hash.rb +6 -3
- data/test/plugin/test_formatter_json.rb +14 -4
- data/test/plugin/test_formatter_ltsv.rb +13 -5
- data/test/plugin/test_formatter_out_file.rb +35 -14
- data/test/plugin/test_formatter_single_value.rb +12 -6
- data/test/plugin/test_formatter_tsv.rb +12 -4
- data/test/plugin/test_in_exec.rb +1 -1
- data/test/plugin/test_in_http.rb +25 -0
- data/test/plugin/test_in_tail.rb +503 -42
- data/test/plugin/test_out_copy.rb +87 -0
- data/test/plugin/test_out_file.rb +23 -18
- data/test/plugin/test_out_forward.rb +94 -6
- data/test/plugin/test_out_http.rb +20 -1
- data/test/plugin/test_output.rb +15 -3
- data/test/plugin/test_output_as_buffered_backup.rb +2 -0
- data/test/plugin/test_parser_csv.rb +14 -0
- data/test/plugin/test_parser_syslog.rb +16 -2
- data/test/plugin/test_sd_file.rb +1 -1
- data/test/plugin_helper/service_discovery/test_manager.rb +1 -1
- data/test/plugin_helper/test_child_process.rb +5 -2
- data/test/plugin_helper/test_compat_parameters.rb +7 -2
- data/test/plugin_helper/test_http_server_helper.rb +4 -2
- data/test/plugin_helper/test_inject.rb +29 -0
- data/test/plugin_helper/test_server.rb +26 -7
- data/test/test_capability.rb +74 -0
- data/test/test_event.rb +16 -0
- data/test/test_formatter.rb +64 -10
- data/test/test_output.rb +8 -3
- data/test/test_supervisor.rb +150 -1
- data/test/test_time_parser.rb +109 -0
- metadata +87 -33
- data/.travis.yml +0 -57
- data/appveyor.yml +0 -28
@@ -19,14 +19,16 @@ require 'fluent/plugin/formatter'
|
|
19
19
|
module Fluent
|
20
20
|
module Plugin
|
21
21
|
class SingleValueFormatter < Formatter
|
22
|
+
include Fluent::Plugin::Newline::Mixin
|
23
|
+
|
22
24
|
Plugin.register_formatter('single_value', self)
|
23
25
|
|
24
|
-
config_param :message_key, :string, default: 'message'
|
26
|
+
config_param :message_key, :string, default: 'message'.freeze
|
25
27
|
config_param :add_newline, :bool, default: true
|
26
28
|
|
27
29
|
def format(tag, time, record)
|
28
30
|
text = record[@message_key].to_s.dup
|
29
|
-
text <<
|
31
|
+
text << @newline if @add_newline
|
30
32
|
text
|
31
33
|
end
|
32
34
|
end
|
@@ -19,18 +19,20 @@ require 'fluent/plugin/formatter'
|
|
19
19
|
module Fluent
|
20
20
|
module Plugin
|
21
21
|
class TSVFormatter < Formatter
|
22
|
+
include Fluent::Plugin::Newline::Mixin
|
23
|
+
|
22
24
|
Plugin.register_formatter('tsv', self)
|
23
25
|
|
24
26
|
desc 'Field names included in each lines'
|
25
27
|
config_param :keys, :array, value_type: :string
|
26
28
|
desc 'The delimiter character (or string) of TSV values'
|
27
|
-
config_param :delimiter, :string, default: "\t"
|
29
|
+
config_param :delimiter, :string, default: "\t".freeze
|
28
30
|
desc 'The parameter to enable writing to new lines'
|
29
31
|
config_param :add_newline, :bool, default: true
|
30
32
|
|
31
33
|
def format(tag, time, record)
|
32
34
|
formatted = @keys.map{|k| record[k].to_s }.join(@delimiter)
|
33
|
-
formatted <<
|
35
|
+
formatted << @newline if @add_newline
|
34
36
|
formatted
|
35
37
|
end
|
36
38
|
end
|
@@ -80,6 +80,8 @@ module Fluent::Plugin
|
|
80
80
|
config_param :use_204_response, :bool, default: false
|
81
81
|
desc 'Dump error log or not'
|
82
82
|
config_param :dump_error_log, :bool, default: true
|
83
|
+
desc 'Add QUERY_ prefix query params to record'
|
84
|
+
config_param :add_query_params, :bool, default: false
|
83
85
|
|
84
86
|
config_section :parse do
|
85
87
|
config_set_default :@type, 'in_http'
|
@@ -277,7 +279,7 @@ module Fluent::Plugin
|
|
277
279
|
private
|
278
280
|
|
279
281
|
def on_server_connect(conn)
|
280
|
-
handler = Handler.new(conn, @km, method(:on_request), @body_size_limit, @format_name, log, @cors_allow_origins)
|
282
|
+
handler = Handler.new(conn, @km, method(:on_request), @body_size_limit, @format_name, log, @cors_allow_origins, @add_query_params)
|
281
283
|
|
282
284
|
conn.on(:data) do |data|
|
283
285
|
handler.on_read(data)
|
@@ -326,6 +328,14 @@ module Fluent::Plugin
|
|
326
328
|
}
|
327
329
|
end
|
328
330
|
|
331
|
+
if @add_query_params
|
332
|
+
params.each_pair { |k, v|
|
333
|
+
if k.start_with?("QUERY_".freeze)
|
334
|
+
record[k] = v
|
335
|
+
end
|
336
|
+
}
|
337
|
+
end
|
338
|
+
|
329
339
|
if @add_remote_addr
|
330
340
|
record['REMOTE_ADDR'] = params['REMOTE_ADDR']
|
331
341
|
end
|
@@ -346,7 +356,7 @@ module Fluent::Plugin
|
|
346
356
|
class Handler
|
347
357
|
attr_reader :content_type
|
348
358
|
|
349
|
-
def initialize(io, km, callback, body_size_limit, format_name, log, cors_allow_origins)
|
359
|
+
def initialize(io, km, callback, body_size_limit, format_name, log, cors_allow_origins, add_query_params)
|
350
360
|
@io = io
|
351
361
|
@km = km
|
352
362
|
@callback = callback
|
@@ -356,6 +366,7 @@ module Fluent::Plugin
|
|
356
366
|
@log = log
|
357
367
|
@cors_allow_origins = cors_allow_origins
|
358
368
|
@idle = 0
|
369
|
+
@add_query_params = add_query_params
|
359
370
|
@km.add(self)
|
360
371
|
|
361
372
|
@remote_port, @remote_addr = io.remote_port, io.remote_addr
|
@@ -492,7 +503,7 @@ module Fluent::Plugin
|
|
492
503
|
# For every incoming request, we check if we have some CORS
|
493
504
|
# restrictions and allow listed origins through @cors_allow_origins.
|
494
505
|
unless @cors_allow_origins.nil?
|
495
|
-
unless @cors_allow_origins.include?('*')
|
506
|
+
unless @cors_allow_origins.include?('*') || include_cors_allow_origin
|
496
507
|
send_response_and_close(RES_403_STATUS, {'Connection' => 'close'}, "")
|
497
508
|
return
|
498
509
|
end
|
@@ -533,7 +544,17 @@ module Fluent::Plugin
|
|
533
544
|
end
|
534
545
|
path_info = uri.path
|
535
546
|
|
547
|
+
if (@add_query_params)
|
548
|
+
|
549
|
+
query_params = WEBrick::HTTPUtils.parse_query(uri.query)
|
550
|
+
|
551
|
+
query_params.each_pair {|k,v|
|
552
|
+
params["QUERY_#{k.gsub('-','_').upcase}"] = v
|
553
|
+
}
|
554
|
+
end
|
555
|
+
|
536
556
|
params.merge!(@env)
|
557
|
+
|
537
558
|
@env.clear
|
538
559
|
|
539
560
|
code, header, body = @callback.call(path_info, params)
|
@@ -338,7 +338,7 @@ module Fluent::Plugin
|
|
338
338
|
obj.merge!(pe.statistics['output'] || {})
|
339
339
|
end
|
340
340
|
|
341
|
-
obj['retry'] = get_retry_info(pe.retry) if opts[:with_retry]
|
341
|
+
obj['retry'] = get_retry_info(pe.retry) if opts[:with_retry] && pe.instance_variable_defined?(:@retry)
|
342
342
|
|
343
343
|
# include all instance variables if :with_debug_info is set
|
344
344
|
if opts[:with_debug_info]
|
@@ -22,6 +22,7 @@ require 'fluent/event'
|
|
22
22
|
require 'fluent/plugin/buffer'
|
23
23
|
require 'fluent/plugin/parser_multiline'
|
24
24
|
require 'fluent/variable_store'
|
25
|
+
require 'fluent/capability'
|
25
26
|
require 'fluent/plugin/in_tail/position_file'
|
26
27
|
|
27
28
|
if Fluent.windows?
|
@@ -104,6 +105,8 @@ module Fluent::Plugin
|
|
104
105
|
config_param :ignore_repeated_permission_error, :bool, default: false
|
105
106
|
desc 'Format path with the specified timezone'
|
106
107
|
config_param :path_timezone, :string, default: nil
|
108
|
+
desc 'Follow inodes instead of following file names. Guarantees more stable delivery and allows to use * in path pattern with rotating files'
|
109
|
+
config_param :follow_inodes, :bool, default: false
|
107
110
|
|
108
111
|
config_section :parse, required: false, multi: true, init: true, param_name: :parser_configs do
|
109
112
|
config_argument :usage, :string, default: 'in_tail_parser'
|
@@ -154,6 +157,9 @@ module Fluent::Plugin
|
|
154
157
|
end
|
155
158
|
@variable_store[@pos_file] = self.plugin_id
|
156
159
|
else
|
160
|
+
if @follow_inodes
|
161
|
+
raise Fluent::ConfigError, "Can't follow inodes without pos_file configuration parameter"
|
162
|
+
end
|
157
163
|
$log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
|
158
164
|
$log.warn "this parameter is highly recommended to save the position to resume tailing."
|
159
165
|
end
|
@@ -171,6 +177,7 @@ module Fluent::Plugin
|
|
171
177
|
@dir_perm = system_config.dir_permission || Fluent::DEFAULT_DIR_PERMISSION
|
172
178
|
# parser is already created by parser helper
|
173
179
|
@parser = parser_create(usage: parser_config['usage'] || @parser_configs.first.usage)
|
180
|
+
@capability = Fluent::Capability.new(:current_process)
|
174
181
|
end
|
175
182
|
|
176
183
|
def configure_tag
|
@@ -214,7 +221,7 @@ module Fluent::Plugin
|
|
214
221
|
FileUtils.mkdir_p(pos_file_dir, mode: @dir_perm) unless Dir.exist?(pos_file_dir)
|
215
222
|
@pf_file = File.open(@pos_file, File::RDWR|File::CREAT|File::BINARY, @file_perm)
|
216
223
|
@pf_file.sync = true
|
217
|
-
@pf = PositionFile.load(@pf_file, logger: log)
|
224
|
+
@pf = PositionFile.load(@pf_file, @follow_inodes, expand_paths, logger: log)
|
218
225
|
|
219
226
|
if @pos_file_compaction_interval
|
220
227
|
timer_execute(:in_tail_refresh_compact_pos_file, @pos_file_compaction_interval) do
|
@@ -238,7 +245,7 @@ module Fluent::Plugin
|
|
238
245
|
|
239
246
|
def shutdown
|
240
247
|
# during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
|
241
|
-
stop_watchers(
|
248
|
+
stop_watchers(existence_path, immediate: true, remove_watcher: false)
|
242
249
|
@pf_file.close if @pf_file
|
243
250
|
|
244
251
|
super
|
@@ -250,6 +257,11 @@ module Fluent::Plugin
|
|
250
257
|
close_watcher_handles
|
251
258
|
end
|
252
259
|
|
260
|
+
def have_read_capability?
|
261
|
+
@capability.have_capability?(:effective, :dac_read_search) ||
|
262
|
+
@capability.have_capability?(:effective, :dac_override)
|
263
|
+
end
|
264
|
+
|
253
265
|
def expand_paths
|
254
266
|
date = Fluent::EventTime.now
|
255
267
|
paths = []
|
@@ -263,7 +275,7 @@ module Fluent::Plugin
|
|
263
275
|
paths += Dir.glob(path).select { |p|
|
264
276
|
begin
|
265
277
|
is_file = !File.directory?(p)
|
266
|
-
if File.readable?(p) && is_file
|
278
|
+
if (File.readable?(p) || have_read_capability?) && is_file
|
267
279
|
if @limit_recently_modified && File.mtime(p) < (date.to_time - @limit_recently_modified)
|
268
280
|
false
|
269
281
|
else
|
@@ -296,7 +308,37 @@ module Fluent::Plugin
|
|
296
308
|
end
|
297
309
|
path.include?('*') ? Dir.glob(path) : path
|
298
310
|
}.flatten.uniq
|
299
|
-
|
311
|
+
# filter out non existing files, so in case pattern is without '*' we don't do unnecessary work
|
312
|
+
hash = {}
|
313
|
+
(paths - excluded).select { |path|
|
314
|
+
FileTest.exist?(path)
|
315
|
+
}.each { |path|
|
316
|
+
# Even we just checked for existence, there is a race condition here as
|
317
|
+
# of which stat() might fail with ENOENT. See #3224.
|
318
|
+
begin
|
319
|
+
target_info = TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
|
320
|
+
if @follow_inodes
|
321
|
+
hash[target_info.ino] = target_info
|
322
|
+
else
|
323
|
+
hash[target_info.path] = target_info
|
324
|
+
end
|
325
|
+
rescue Errno::ENOENT
|
326
|
+
$log.warn "expand_paths: stat() for #{path} failed with ENOENT. Skip file."
|
327
|
+
end
|
328
|
+
}
|
329
|
+
hash
|
330
|
+
end
|
331
|
+
|
332
|
+
def existence_path
|
333
|
+
hash = {}
|
334
|
+
@tails.each_key {|target_info|
|
335
|
+
if @follow_inodes
|
336
|
+
hash[target_info.ino] = target_info
|
337
|
+
else
|
338
|
+
hash[target_info.path] = target_info
|
339
|
+
end
|
340
|
+
}
|
341
|
+
hash
|
300
342
|
end
|
301
343
|
|
302
344
|
# in_tail with '*' path doesn't check rotation file equality at refresh phase.
|
@@ -305,21 +347,21 @@ module Fluent::Plugin
|
|
305
347
|
# In such case, you should separate log directory and specify two paths in path parameter.
|
306
348
|
# e.g. path /path/to/dir/*,/path/to/rotated_logs/target_file
|
307
349
|
def refresh_watchers
|
308
|
-
|
309
|
-
|
350
|
+
target_paths_hash = expand_paths
|
351
|
+
existence_paths_hash = existence_path
|
310
352
|
|
311
353
|
log.debug { "tailing paths: target = #{target_paths.join(",")} | existing = #{existence_paths.join(",")}" }
|
312
354
|
|
313
|
-
|
314
|
-
|
355
|
+
unwatched_hash = existence_paths_hash.reject {|key, value| target_paths_hash.key?(key)}
|
356
|
+
added_hash = target_paths_hash.reject {|key, value| existence_paths_hash.key?(key)}
|
315
357
|
|
316
|
-
stop_watchers(
|
317
|
-
start_watchers(
|
358
|
+
stop_watchers(unwatched_hash, immediate: false, unwatched: true) unless unwatched_hash.empty?
|
359
|
+
start_watchers(added_hash) unless added_hash.empty?
|
318
360
|
end
|
319
361
|
|
320
|
-
def setup_watcher(
|
362
|
+
def setup_watcher(target_info, pe)
|
321
363
|
line_buffer_timer_flusher = @multiline_mode ? TailWatcher::LineBufferTimerFlusher.new(log, @multiline_flush_interval, &method(:flush_buffer)) : nil
|
322
|
-
tw = TailWatcher.new(
|
364
|
+
tw = TailWatcher.new(target_info, pe, log, @read_from_head, @follow_inodes, method(:update_watcher), line_buffer_timer_flusher, method(:io_handler))
|
323
365
|
|
324
366
|
if @enable_watch_timer
|
325
367
|
tt = TimerTrigger.new(1, log) { tw.on_notify }
|
@@ -350,47 +392,60 @@ module Fluent::Plugin
|
|
350
392
|
raise e
|
351
393
|
end
|
352
394
|
|
353
|
-
def start_watchers(
|
354
|
-
|
395
|
+
def start_watchers(targets_info)
|
396
|
+
targets_info.each_value { |target_info|
|
355
397
|
pe = nil
|
356
398
|
if @pf
|
357
|
-
pe = @pf[
|
399
|
+
pe = @pf[target_info]
|
358
400
|
if @read_from_head && pe.read_inode.zero?
|
359
401
|
begin
|
360
|
-
pe.update(Fluent::FileWrapper.stat(path).ino, 0)
|
402
|
+
pe.update(Fluent::FileWrapper.stat(target_info.path).ino, 0)
|
361
403
|
rescue Errno::ENOENT
|
362
|
-
$log.warn "#{path} not found. Continuing without tailing it."
|
404
|
+
$log.warn "#{target_info.path} not found. Continuing without tailing it."
|
363
405
|
end
|
364
406
|
end
|
365
407
|
end
|
366
408
|
|
367
409
|
begin
|
368
|
-
tw = setup_watcher(
|
410
|
+
tw = setup_watcher(target_info, pe)
|
369
411
|
rescue WatcherSetupError => e
|
370
|
-
log.warn "Skip #{path} because unexpected setup error happens: #{e}"
|
412
|
+
log.warn "Skip #{target_info.path} because unexpected setup error happens: #{e}"
|
371
413
|
next
|
372
414
|
end
|
373
|
-
|
415
|
+
|
416
|
+
begin
|
417
|
+
target_info = TargetInfo.new(target_info.path, Fluent::FileWrapper.stat(target_info.path).ino)
|
418
|
+
@tails[target_info] = tw
|
419
|
+
rescue Errno::ENOENT
|
420
|
+
$log.warn "stat() for #{target_info.path} failed with ENOENT. Drop tail watcher for now."
|
421
|
+
# explicitly detach and unwatch watcher `tw`.
|
422
|
+
tw.unwatched = true
|
423
|
+
detach_watcher(tw, target_info.ino, false)
|
424
|
+
end
|
374
425
|
}
|
375
426
|
end
|
376
427
|
|
377
|
-
def stop_watchers(
|
378
|
-
|
379
|
-
|
428
|
+
def stop_watchers(targets_info, immediate: false, unwatched: false, remove_watcher: true)
|
429
|
+
targets_info.each_value { |target_info|
|
430
|
+
if remove_watcher
|
431
|
+
tw = @tails.delete(target_info)
|
432
|
+
else
|
433
|
+
tw = @tails[target_info]
|
434
|
+
end
|
380
435
|
if tw
|
381
436
|
tw.unwatched = unwatched
|
382
437
|
if immediate
|
383
|
-
detach_watcher(tw, false)
|
438
|
+
detach_watcher(tw, target_info.ino, false)
|
384
439
|
else
|
385
|
-
detach_watcher_after_rotate_wait(tw)
|
440
|
+
detach_watcher_after_rotate_wait(tw, target_info.ino)
|
386
441
|
end
|
387
442
|
end
|
388
443
|
}
|
389
444
|
end
|
390
445
|
|
391
446
|
def close_watcher_handles
|
392
|
-
@tails.keys.each do |
|
393
|
-
tw = @tails.delete(
|
447
|
+
@tails.keys.each do |target_info|
|
448
|
+
tw = @tails.delete(target_info)
|
394
449
|
if tw
|
395
450
|
tw.close
|
396
451
|
end
|
@@ -398,25 +453,39 @@ module Fluent::Plugin
|
|
398
453
|
end
|
399
454
|
|
400
455
|
# refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
|
401
|
-
def update_watcher(
|
402
|
-
log.info("detected rotation of #{path}; waiting #{@rotate_wait} seconds")
|
456
|
+
def update_watcher(target_info, pe)
|
457
|
+
log.info("detected rotation of #{target_info.path}; waiting #{@rotate_wait} seconds")
|
403
458
|
|
404
459
|
if @pf
|
405
|
-
|
460
|
+
pe_inode = pe.read_inode
|
461
|
+
target_info_from_position_entry = TargetInfo.new(target_info.path, pe_inode)
|
462
|
+
unless pe_inode == @pf[target_info_from_position_entry].read_inode
|
406
463
|
log.debug "Skip update_watcher because watcher has been already updated by other inotify event"
|
407
464
|
return
|
408
465
|
end
|
409
466
|
end
|
410
|
-
|
411
|
-
|
412
|
-
|
467
|
+
|
468
|
+
rotated_target_info = TargetInfo.new(target_info.path, pe.read_inode)
|
469
|
+
rotated_tw = @tails[rotated_target_info]
|
470
|
+
new_target_info = target_info.dup
|
471
|
+
|
472
|
+
if @follow_inodes
|
473
|
+
new_position_entry = @pf[target_info]
|
474
|
+
|
475
|
+
if new_position_entry.read_inode == 0
|
476
|
+
@tails[new_target_info] = setup_watcher(new_target_info, new_position_entry)
|
477
|
+
end
|
478
|
+
else
|
479
|
+
@tails[new_target_info] = setup_watcher(new_target_info, pe)
|
480
|
+
end
|
481
|
+
detach_watcher_after_rotate_wait(rotated_tw, pe.read_inode) if rotated_tw
|
413
482
|
end
|
414
483
|
|
415
484
|
# TailWatcher#close is called by another thread at shutdown phase.
|
416
485
|
# It causes 'can't modify string; temporarily locked' error in IOHandler
|
417
486
|
# so adding close_io argument to avoid this problem.
|
418
487
|
# At shutdown, IOHandler's io will be released automatically after detached the event loop
|
419
|
-
def detach_watcher(tw, close_io = true)
|
488
|
+
def detach_watcher(tw, ino, close_io = true)
|
420
489
|
tw.watchers.each do |watcher|
|
421
490
|
event_loop_detach(watcher)
|
422
491
|
end
|
@@ -425,15 +494,16 @@ module Fluent::Plugin
|
|
425
494
|
tw.close if close_io
|
426
495
|
|
427
496
|
if tw.unwatched && @pf
|
428
|
-
|
497
|
+
target_info = TargetInfo.new(tw.path, ino)
|
498
|
+
@pf.unwatch(target_info)
|
429
499
|
end
|
430
500
|
end
|
431
501
|
|
432
|
-
def detach_watcher_after_rotate_wait(tw)
|
502
|
+
def detach_watcher_after_rotate_wait(tw, ino)
|
433
503
|
# Call event_loop_attach/event_loop_detach is high-cost for short-live object.
|
434
504
|
# If this has a problem with large number of files, use @_event_loop directly instead of timer_execute.
|
435
505
|
timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
|
436
|
-
detach_watcher(tw)
|
506
|
+
detach_watcher(tw, ino)
|
437
507
|
end
|
438
508
|
end
|
439
509
|
|
@@ -601,10 +671,12 @@ module Fluent::Plugin
|
|
601
671
|
end
|
602
672
|
|
603
673
|
class TailWatcher
|
604
|
-
def initialize(
|
605
|
-
@path = path
|
674
|
+
def initialize(target_info, pe, log, read_from_head, follow_inodes, update_watcher, line_buffer_timer_flusher, io_handler_build)
|
675
|
+
@path = target_info.path
|
676
|
+
@ino = target_info.ino
|
606
677
|
@pe = pe || MemoryPositionEntry.new
|
607
678
|
@read_from_head = read_from_head
|
679
|
+
@follow_inodes = follow_inodes
|
608
680
|
@update_watcher = update_watcher
|
609
681
|
@log = log
|
610
682
|
@rotate_handler = RotateHandler.new(log, &method(:on_rotate))
|
@@ -614,7 +686,7 @@ module Fluent::Plugin
|
|
614
686
|
@watchers = []
|
615
687
|
end
|
616
688
|
|
617
|
-
attr_reader :path
|
689
|
+
attr_reader :path, :ino
|
618
690
|
attr_reader :pe
|
619
691
|
attr_reader :line_buffer_timer_flusher
|
620
692
|
attr_accessor :unwatched # This is used for removing position entry from PositionFile
|
@@ -709,7 +781,23 @@ module Fluent::Plugin
|
|
709
781
|
end
|
710
782
|
|
711
783
|
if watcher_needs_update
|
712
|
-
|
784
|
+
if @follow_inodes
|
785
|
+
# No need to update a watcher if stat is nil (file not present), because moving to inodes will create
|
786
|
+
# new watcher, and old watcher will be closed by stop_watcher in refresh_watchers method
|
787
|
+
# don't want to swap state because we need latest read offset in pos file even after rotate_wait
|
788
|
+
if stat
|
789
|
+
target_info = TargetInfo.new(@path, stat)
|
790
|
+
@update_watcher.call(target_info, @pe)
|
791
|
+
end
|
792
|
+
else
|
793
|
+
# Permit to handle if stat is nil (file not present).
|
794
|
+
# If a file is mv-ed and a new file is created during
|
795
|
+
# calling `#refresh_watchers`s, and `#refresh_watchers` won't run `#start_watchers`
|
796
|
+
# and `#stop_watchers()` for the path because `target_paths_hash`
|
797
|
+
# always contains the path.
|
798
|
+
target_info = TargetInfo.new(@path, stat ? stat.ino : nil)
|
799
|
+
@update_watcher.call(target_info, swap_state(@pe))
|
800
|
+
end
|
713
801
|
else
|
714
802
|
@log.info "detected rotation of #{@path}"
|
715
803
|
@io_handler = io_handler
|