fluentd 1.12.0.rc1 → 1.12.0.rc2
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.md +1 -1
- data/.github/ISSUE_TEMPLATE/config.yml +5 -0
- data/.github/workflows/stale-actions.yml +22 -0
- data/bin/fluent-ctl +7 -0
- data/lib/fluent/command/ctl.rb +177 -0
- data/lib/fluent/command/plugin_config_formatter.rb +2 -1
- data/lib/fluent/plugin.rb +5 -0
- data/lib/fluent/plugin/in_http.rb +23 -2
- data/lib/fluent/plugin/in_tail.rb +101 -40
- data/lib/fluent/plugin/in_tail/position_file.rb +39 -14
- data/lib/fluent/plugin/output.rb +7 -1
- data/lib/fluent/plugin_helper/http_server/compat/server.rb +1 -1
- data/lib/fluent/plugin_helper/retry_state.rb +4 -0
- data/lib/fluent/supervisor.rb +140 -42
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/winsvc.rb +22 -4
- data/test/command/test_ctl.rb +57 -0
- data/test/command/test_plugin_config_formatter.rb +57 -2
- data/test/plugin/in_tail/test_position_file.rb +45 -25
- data/test/plugin/test_in_http.rb +25 -0
- data/test/plugin/test_in_tail.rb +383 -29
- data/test/test_supervisor.rb +102 -10
- metadata +9 -3
- data/.github/stale.yml +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7233372dc2b1c0ab6ae93682dc0a3ecd8ba2eb4956dfecc2456173cf30729090
|
4
|
+
data.tar.gz: e61aa30c17b3f27322a10fbc4b2e81a219eeeb0f061c4ccfd0b6c8c10777a607
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 737b0727cd390374168ed176a19a73b94d3010a7e93d5951a5580cfbca5bdd697c082afbde07ba1b266edd77be057ec7ccef6ea4383f626e7a11bcf4ad9d9c40
|
7
|
+
data.tar.gz: 94f055d9d2d88359a49caf597bc9076cee741d61e80d84aa3cd1cefe8b01e7ee78721502b5b0dd4e329cb0fceeec952b43b51efd666ee7c9742fc763d1c7cb11
|
@@ -0,0 +1,22 @@
|
|
1
|
+
name: "Mark or close stale issues and PRs"
|
2
|
+
on:
|
3
|
+
schedule:
|
4
|
+
- cron: "00 10 * * *"
|
5
|
+
|
6
|
+
jobs:
|
7
|
+
stale:
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
steps:
|
10
|
+
- uses: actions/stale@v3
|
11
|
+
with:
|
12
|
+
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
13
|
+
days-before-stale: 90
|
14
|
+
days-before-close: 30
|
15
|
+
stale-issue-message: "This issue has been automatically marked as stale because it has been open 90 days with no activity. Remove stale label or comment or this issue will be closed in 30 days"
|
16
|
+
stale-pr-message: "This PR has been automatically marked as stale because it has been open 90 days with no activity. Remove stale label or comment or this PR will be closed in 30 days"
|
17
|
+
close-issue-message: "This issue was automatically closed because of stale in 30 days"
|
18
|
+
close-pr-message: "This PR was automatically closed because of stale in 30 days"
|
19
|
+
stale-pr-label: "stale"
|
20
|
+
stale-issue-label: "stale"
|
21
|
+
exempt-issue-labels: "bug,enhancement,feature request,pending,work_in_progress,v1,v2"
|
22
|
+
exempt-pr-labels: "bug,enhancement,feature request,pending,work_in_progress,v1,v2"
|
data/bin/fluent-ctl
ADDED
@@ -0,0 +1,177 @@
|
|
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 'optparse'
|
18
|
+
require 'fluent/env'
|
19
|
+
require 'fluent/version'
|
20
|
+
if Fluent.windows?
|
21
|
+
require 'win32/event'
|
22
|
+
require 'win32/service'
|
23
|
+
end
|
24
|
+
|
25
|
+
module Fluent
|
26
|
+
class Ctl
|
27
|
+
DEFAULT_OPTIONS = {}
|
28
|
+
|
29
|
+
if Fluent.windows?
|
30
|
+
include Windows::ServiceConstants
|
31
|
+
include Windows::ServiceStructs
|
32
|
+
include Windows::ServiceFunctions
|
33
|
+
|
34
|
+
COMMAND_MAP = {
|
35
|
+
shutdown: "",
|
36
|
+
restart: "HUP",
|
37
|
+
flush: "USR1",
|
38
|
+
reload: "USR2",
|
39
|
+
}
|
40
|
+
WINSVC_CONTROL_CODE_MAP = {
|
41
|
+
shutdown: SERVICE_CONTROL_STOP,
|
42
|
+
# 128 - 255: user-defined control code
|
43
|
+
# See https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-controlservice
|
44
|
+
restart: 128,
|
45
|
+
flush: 129,
|
46
|
+
reload: SERVICE_CONTROL_PARAMCHANGE,
|
47
|
+
}
|
48
|
+
else
|
49
|
+
COMMAND_MAP = {
|
50
|
+
shutdown: :TERM,
|
51
|
+
restart: :HUP,
|
52
|
+
flush: :USR1,
|
53
|
+
reload: :USR2,
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize(argv = ARGV)
|
58
|
+
@argv = argv
|
59
|
+
@options = {}
|
60
|
+
@opt_parser = OptionParser.new
|
61
|
+
configure_option_parser
|
62
|
+
@options.merge!(DEFAULT_OPTIONS)
|
63
|
+
parse_options!
|
64
|
+
end
|
65
|
+
|
66
|
+
def help_text
|
67
|
+
text = "\n"
|
68
|
+
if Fluent.windows?
|
69
|
+
text << "Usage: #{$PROGRAM_NAME} COMMAND [PID_OR_SVCNAME]\n"
|
70
|
+
else
|
71
|
+
text << "Usage: #{$PROGRAM_NAME} COMMAND PID\n"
|
72
|
+
end
|
73
|
+
text << "\n"
|
74
|
+
text << "Commands: \n"
|
75
|
+
COMMAND_MAP.each do |key, value|
|
76
|
+
text << " #{key}\n"
|
77
|
+
end
|
78
|
+
text
|
79
|
+
end
|
80
|
+
|
81
|
+
def usage(msg = nil)
|
82
|
+
puts help_text
|
83
|
+
if msg
|
84
|
+
puts
|
85
|
+
puts "Error: #{msg}"
|
86
|
+
end
|
87
|
+
exit 1
|
88
|
+
end
|
89
|
+
|
90
|
+
def call
|
91
|
+
if Fluent.windows?
|
92
|
+
if @pid_or_svcname =~ /^[0-9]+$/
|
93
|
+
# Use as PID
|
94
|
+
return call_windows_event(@command, "fluentd_#{@pid_or_svcname}")
|
95
|
+
end
|
96
|
+
|
97
|
+
unless call_winsvc_control_code(@command, @pid_or_svcname)
|
98
|
+
puts "Cannot send control code to #{@pid_or_svcname} service, try to send an event with this name ..."
|
99
|
+
call_windows_event(@command, @pid_or_svcname)
|
100
|
+
end
|
101
|
+
else
|
102
|
+
call_signal(@command, @pid_or_svcname)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def call_signal(command, pid)
|
109
|
+
signal = COMMAND_MAP[command.to_sym]
|
110
|
+
Process.kill(signal, pid.to_i)
|
111
|
+
end
|
112
|
+
|
113
|
+
def call_winsvc_control_code(command, pid_or_svcname)
|
114
|
+
status = SERVICE_STATUS.new
|
115
|
+
|
116
|
+
begin
|
117
|
+
handle_scm = OpenSCManager(nil, nil, SC_MANAGER_CONNECT)
|
118
|
+
FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
|
119
|
+
|
120
|
+
handle_scs = OpenService(handle_scm, "fluentdwinsvc", SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_USER_DEFINED_CONTROL)
|
121
|
+
FFI.raise_windows_error('OpenService') if handle_scs == 0
|
122
|
+
|
123
|
+
control_code = WINSVC_CONTROL_CODE_MAP[command.to_sym]
|
124
|
+
|
125
|
+
unless ControlService(handle_scs, control_code, status)
|
126
|
+
FFI.raise_windows_error('ControlService')
|
127
|
+
end
|
128
|
+
rescue => e
|
129
|
+
puts e
|
130
|
+
state = status[:dwCurrentState]
|
131
|
+
return state == SERVICE_STOPPED || state == SERVICE_STOP_PENDING
|
132
|
+
ensure
|
133
|
+
CloseServiceHandle(handle_scs)
|
134
|
+
CloseServiceHandle(handle_scm)
|
135
|
+
end
|
136
|
+
|
137
|
+
return true
|
138
|
+
end
|
139
|
+
|
140
|
+
def call_windows_event(command, pid_or_svcname)
|
141
|
+
prefix = pid_or_svcname
|
142
|
+
event_name = COMMAND_MAP[command.to_sym]
|
143
|
+
suffix = event_name.empty? ? "" : "_#{event_name}"
|
144
|
+
|
145
|
+
begin
|
146
|
+
event = Win32::Event.open("#{prefix}#{suffix}")
|
147
|
+
event.set
|
148
|
+
event.close
|
149
|
+
rescue Errno::ENOENT => e
|
150
|
+
puts "Error: Cannot find the fluentd process with the event name: \"#{prefix}\""
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def configure_option_parser
|
155
|
+
@opt_parser.banner = help_text
|
156
|
+
@opt_parser.version = Fluent::VERSION
|
157
|
+
end
|
158
|
+
|
159
|
+
def parse_options!
|
160
|
+
@opt_parser.parse!(@argv)
|
161
|
+
|
162
|
+
@command = @argv[0]
|
163
|
+
@pid_or_svcname = @argv[1] || "fluentdwinsvc"
|
164
|
+
|
165
|
+
usage("Command isn't specified!") if @command.nil? || @command.empty?
|
166
|
+
usage("Unknown command: #{@command}") unless COMMAND_MAP.has_key?(@command.to_sym)
|
167
|
+
|
168
|
+
if Fluent.windows?
|
169
|
+
usage("PID or SVCNAME isn't specified!") if @pid_or_svcname.nil? || @pid_or_svcname.empty?
|
170
|
+
else
|
171
|
+
usage("PID isn't specified!") if @pid_or_svcname.nil? || @pid_or_svcname.empty?
|
172
|
+
usage("Invalid PID: #{pid}") unless @pid_or_svcname =~ /^[0-9]+$/
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
@@ -29,7 +29,8 @@ class FluentPluginConfigFormatter
|
|
29
29
|
AVAILABLE_FORMATS = [:markdown, :txt, :json]
|
30
30
|
SUPPORTED_TYPES = [
|
31
31
|
"input", "output", "filter",
|
32
|
-
"buffer", "parser", "formatter", "storage"
|
32
|
+
"buffer", "parser", "formatter", "storage",
|
33
|
+
"service_discovery"
|
33
34
|
]
|
34
35
|
|
35
36
|
DOCS_BASE_URL = "https://docs.fluentd.org/v/1.0"
|
data/lib/fluent/plugin.rb
CHANGED
@@ -121,6 +121,11 @@ module Fluent
|
|
121
121
|
new_impl('sd', SD_REGISTRY, type, parent)
|
122
122
|
end
|
123
123
|
|
124
|
+
class << self
|
125
|
+
# This should be defined for fluent-plugin-config-formatter type arguments.
|
126
|
+
alias_method :new_service_discovery, :new_sd
|
127
|
+
end
|
128
|
+
|
124
129
|
def self.new_parser(type, parent: nil)
|
125
130
|
if type[0] == '/' && type[-1] == '/'
|
126
131
|
# This usage is not recommended for new API... create RegexpParser directly
|
@@ -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
|
@@ -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)
|
@@ -105,6 +105,8 @@ module Fluent::Plugin
|
|
105
105
|
config_param :ignore_repeated_permission_error, :bool, default: false
|
106
106
|
desc 'Format path with the specified timezone'
|
107
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
|
108
110
|
|
109
111
|
config_section :parse, required: false, multi: true, init: true, param_name: :parser_configs do
|
110
112
|
config_argument :usage, :string, default: 'in_tail_parser'
|
@@ -155,6 +157,9 @@ module Fluent::Plugin
|
|
155
157
|
end
|
156
158
|
@variable_store[@pos_file] = self.plugin_id
|
157
159
|
else
|
160
|
+
if @follow_inodes
|
161
|
+
raise Fluent::ConfigError, "Can't follow inodes without pos_file configuration parameter"
|
162
|
+
end
|
158
163
|
$log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
|
159
164
|
$log.warn "this parameter is highly recommended to save the position to resume tailing."
|
160
165
|
end
|
@@ -216,7 +221,7 @@ module Fluent::Plugin
|
|
216
221
|
FileUtils.mkdir_p(pos_file_dir, mode: @dir_perm) unless Dir.exist?(pos_file_dir)
|
217
222
|
@pf_file = File.open(@pos_file, File::RDWR|File::CREAT|File::BINARY, @file_perm)
|
218
223
|
@pf_file.sync = true
|
219
|
-
@pf = PositionFile.load(@pf_file, logger: log)
|
224
|
+
@pf = PositionFile.load(@pf_file, @follow_inodes, expand_paths, logger: log)
|
220
225
|
|
221
226
|
if @pos_file_compaction_interval
|
222
227
|
timer_execute(:in_tail_refresh_compact_pos_file, @pos_file_compaction_interval) do
|
@@ -240,7 +245,7 @@ module Fluent::Plugin
|
|
240
245
|
|
241
246
|
def shutdown
|
242
247
|
# during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
|
243
|
-
stop_watchers(
|
248
|
+
stop_watchers(existence_path, immediate: true, remove_watcher: false)
|
244
249
|
@pf_file.close if @pf_file
|
245
250
|
|
246
251
|
super
|
@@ -303,7 +308,31 @@ module Fluent::Plugin
|
|
303
308
|
end
|
304
309
|
path.include?('*') ? Dir.glob(path) : path
|
305
310
|
}.flatten.uniq
|
306
|
-
|
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
|
+
target_info = TargetInfo.new(path, Fluent::FileWrapper.stat(path).ino)
|
317
|
+
if @follow_inodes
|
318
|
+
hash[target_info.ino] = target_info
|
319
|
+
else
|
320
|
+
hash[target_info.path] = target_info
|
321
|
+
end
|
322
|
+
}
|
323
|
+
hash
|
324
|
+
end
|
325
|
+
|
326
|
+
def existence_path
|
327
|
+
hash = {}
|
328
|
+
@tails.each_key {|target_info|
|
329
|
+
if @follow_inodes
|
330
|
+
hash[target_info.ino] = target_info
|
331
|
+
else
|
332
|
+
hash[target_info.path] = target_info
|
333
|
+
end
|
334
|
+
}
|
335
|
+
hash
|
307
336
|
end
|
308
337
|
|
309
338
|
# in_tail with '*' path doesn't check rotation file equality at refresh phase.
|
@@ -312,21 +341,21 @@ module Fluent::Plugin
|
|
312
341
|
# In such case, you should separate log directory and specify two paths in path parameter.
|
313
342
|
# e.g. path /path/to/dir/*,/path/to/rotated_logs/target_file
|
314
343
|
def refresh_watchers
|
315
|
-
|
316
|
-
|
344
|
+
target_paths_hash = expand_paths
|
345
|
+
existence_paths_hash = existence_path
|
317
346
|
|
318
347
|
log.debug { "tailing paths: target = #{target_paths.join(",")} | existing = #{existence_paths.join(",")}" }
|
319
348
|
|
320
|
-
|
321
|
-
|
349
|
+
unwatched_hash = existence_paths_hash.reject {|key, value| target_paths_hash.key?(key)}
|
350
|
+
added_hash = target_paths_hash.reject {|key, value| existence_paths_hash.key?(key)}
|
322
351
|
|
323
|
-
stop_watchers(
|
324
|
-
start_watchers(
|
352
|
+
stop_watchers(unwatched_hash, immediate: false, unwatched: true) unless unwatched_hash.empty?
|
353
|
+
start_watchers(added_hash) unless added_hash.empty?
|
325
354
|
end
|
326
355
|
|
327
|
-
def setup_watcher(
|
356
|
+
def setup_watcher(target_info, pe)
|
328
357
|
line_buffer_timer_flusher = @multiline_mode ? TailWatcher::LineBufferTimerFlusher.new(log, @multiline_flush_interval, &method(:flush_buffer)) : nil
|
329
|
-
tw = TailWatcher.new(
|
358
|
+
tw = TailWatcher.new(target_info, pe, log, @read_from_head, @follow_inodes, method(:update_watcher), line_buffer_timer_flusher, method(:io_handler))
|
330
359
|
|
331
360
|
if @enable_watch_timer
|
332
361
|
tt = TimerTrigger.new(1, log) { tw.on_notify }
|
@@ -357,47 +386,52 @@ module Fluent::Plugin
|
|
357
386
|
raise e
|
358
387
|
end
|
359
388
|
|
360
|
-
def start_watchers(
|
361
|
-
|
389
|
+
def start_watchers(targets_info)
|
390
|
+
targets_info.each_value { |target_info|
|
362
391
|
pe = nil
|
363
392
|
if @pf
|
364
|
-
pe = @pf[
|
393
|
+
pe = @pf[target_info]
|
365
394
|
if @read_from_head && pe.read_inode.zero?
|
366
395
|
begin
|
367
|
-
pe.update(Fluent::FileWrapper.stat(path).ino, 0)
|
396
|
+
pe.update(Fluent::FileWrapper.stat(target_info.path).ino, 0)
|
368
397
|
rescue Errno::ENOENT
|
369
|
-
$log.warn "#{path} not found. Continuing without tailing it."
|
398
|
+
$log.warn "#{target_info.path} not found. Continuing without tailing it."
|
370
399
|
end
|
371
400
|
end
|
372
401
|
end
|
373
402
|
|
374
403
|
begin
|
375
|
-
tw = setup_watcher(
|
404
|
+
tw = setup_watcher(target_info, pe)
|
376
405
|
rescue WatcherSetupError => e
|
377
|
-
log.warn "Skip #{path} because unexpected setup error happens: #{e}"
|
406
|
+
log.warn "Skip #{target_info.path} because unexpected setup error happens: #{e}"
|
378
407
|
next
|
379
408
|
end
|
380
|
-
|
409
|
+
target_info = TargetInfo.new(target_info.path, Fluent::FileWrapper.stat(target_info.path).ino)
|
410
|
+
@tails[target_info] = tw
|
381
411
|
}
|
382
412
|
end
|
383
413
|
|
384
|
-
def stop_watchers(
|
385
|
-
|
386
|
-
|
414
|
+
def stop_watchers(targets_info, immediate: false, unwatched: false, remove_watcher: true)
|
415
|
+
targets_info.each_value { |target_info|
|
416
|
+
if remove_watcher
|
417
|
+
tw = @tails.delete(target_info)
|
418
|
+
else
|
419
|
+
tw = @tails[target_info]
|
420
|
+
end
|
387
421
|
if tw
|
388
422
|
tw.unwatched = unwatched
|
389
423
|
if immediate
|
390
|
-
detach_watcher(tw, false)
|
424
|
+
detach_watcher(tw, target_info.ino, false)
|
391
425
|
else
|
392
|
-
detach_watcher_after_rotate_wait(tw)
|
426
|
+
detach_watcher_after_rotate_wait(tw, target_info.ino)
|
393
427
|
end
|
394
428
|
end
|
395
429
|
}
|
396
430
|
end
|
397
431
|
|
398
432
|
def close_watcher_handles
|
399
|
-
@tails.keys.each do |
|
400
|
-
tw = @tails.delete(
|
433
|
+
@tails.keys.each do |target_info|
|
434
|
+
tw = @tails.delete(target_info)
|
401
435
|
if tw
|
402
436
|
tw.close
|
403
437
|
end
|
@@ -405,25 +439,39 @@ module Fluent::Plugin
|
|
405
439
|
end
|
406
440
|
|
407
441
|
# refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
|
408
|
-
def update_watcher(
|
409
|
-
log.info("detected rotation of #{path}; waiting #{@rotate_wait} seconds")
|
442
|
+
def update_watcher(target_info, pe)
|
443
|
+
log.info("detected rotation of #{target_info.path}; waiting #{@rotate_wait} seconds")
|
410
444
|
|
411
445
|
if @pf
|
412
|
-
|
446
|
+
pe_inode = pe.read_inode
|
447
|
+
target_info_from_position_entry = TargetInfo.new(target_info.path, pe_inode)
|
448
|
+
unless pe_inode == @pf[target_info_from_position_entry].read_inode
|
413
449
|
log.debug "Skip update_watcher because watcher has been already updated by other inotify event"
|
414
450
|
return
|
415
451
|
end
|
416
452
|
end
|
417
|
-
|
418
|
-
|
419
|
-
|
453
|
+
|
454
|
+
rotated_target_info = TargetInfo.new(target_info.path, pe.read_inode)
|
455
|
+
rotated_tw = @tails[rotated_target_info]
|
456
|
+
new_target_info = target_info.dup
|
457
|
+
|
458
|
+
if @follow_inodes
|
459
|
+
new_position_entry = @pf[target_info]
|
460
|
+
|
461
|
+
if new_position_entry.read_inode == 0
|
462
|
+
@tails[new_target_info] = setup_watcher(new_target_info, new_position_entry)
|
463
|
+
end
|
464
|
+
else
|
465
|
+
@tails[new_target_info] = setup_watcher(new_target_info, pe)
|
466
|
+
end
|
467
|
+
detach_watcher_after_rotate_wait(rotated_tw, pe.read_inode) if rotated_tw
|
420
468
|
end
|
421
469
|
|
422
470
|
# TailWatcher#close is called by another thread at shutdown phase.
|
423
471
|
# It causes 'can't modify string; temporarily locked' error in IOHandler
|
424
472
|
# so adding close_io argument to avoid this problem.
|
425
473
|
# At shutdown, IOHandler's io will be released automatically after detached the event loop
|
426
|
-
def detach_watcher(tw, close_io = true)
|
474
|
+
def detach_watcher(tw, ino, close_io = true)
|
427
475
|
tw.watchers.each do |watcher|
|
428
476
|
event_loop_detach(watcher)
|
429
477
|
end
|
@@ -432,15 +480,16 @@ module Fluent::Plugin
|
|
432
480
|
tw.close if close_io
|
433
481
|
|
434
482
|
if tw.unwatched && @pf
|
435
|
-
|
483
|
+
target_info = TargetInfo.new(tw.path, ino)
|
484
|
+
@pf.unwatch(target_info)
|
436
485
|
end
|
437
486
|
end
|
438
487
|
|
439
|
-
def detach_watcher_after_rotate_wait(tw)
|
488
|
+
def detach_watcher_after_rotate_wait(tw, ino)
|
440
489
|
# Call event_loop_attach/event_loop_detach is high-cost for short-live object.
|
441
490
|
# If this has a problem with large number of files, use @_event_loop directly instead of timer_execute.
|
442
491
|
timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
|
443
|
-
detach_watcher(tw)
|
492
|
+
detach_watcher(tw, ino)
|
444
493
|
end
|
445
494
|
end
|
446
495
|
|
@@ -608,10 +657,12 @@ module Fluent::Plugin
|
|
608
657
|
end
|
609
658
|
|
610
659
|
class TailWatcher
|
611
|
-
def initialize(
|
612
|
-
@path = path
|
660
|
+
def initialize(target_info, pe, log, read_from_head, follow_inodes, update_watcher, line_buffer_timer_flusher, io_handler_build)
|
661
|
+
@path = target_info.path
|
662
|
+
@ino = target_info.ino
|
613
663
|
@pe = pe || MemoryPositionEntry.new
|
614
664
|
@read_from_head = read_from_head
|
665
|
+
@follow_inodes = follow_inodes
|
615
666
|
@update_watcher = update_watcher
|
616
667
|
@log = log
|
617
668
|
@rotate_handler = RotateHandler.new(log, &method(:on_rotate))
|
@@ -621,7 +672,7 @@ module Fluent::Plugin
|
|
621
672
|
@watchers = []
|
622
673
|
end
|
623
674
|
|
624
|
-
attr_reader :path
|
675
|
+
attr_reader :path, :ino
|
625
676
|
attr_reader :pe
|
626
677
|
attr_reader :line_buffer_timer_flusher
|
627
678
|
attr_accessor :unwatched # This is used for removing position entry from PositionFile
|
@@ -716,7 +767,17 @@ module Fluent::Plugin
|
|
716
767
|
end
|
717
768
|
|
718
769
|
if watcher_needs_update
|
719
|
-
|
770
|
+
# No need to update a watcher if stat is nil (file not present), because moving to inodes will create
|
771
|
+
# new watcher, and old watcher will be closed by stop_watcher in refresh_watchers method
|
772
|
+
if stat
|
773
|
+
target_info = TargetInfo.new(@path, stat.ino)
|
774
|
+
if @follow_inodes
|
775
|
+
# don't want to swap state because we need latest read offset in pos file even after rotate_wait
|
776
|
+
@update_watcher.call(target_info, @pe)
|
777
|
+
else
|
778
|
+
@update_watcher.call(target_info, swap_state(@pe))
|
779
|
+
end
|
780
|
+
end
|
720
781
|
else
|
721
782
|
@log.info "detected rotation of #{@path}"
|
722
783
|
@io_handler = io_handler
|