fluentd 1.19.3 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AGENTS.md +46 -0
- data/CHANGELOG.md +120 -0
- data/GithubWorkflow.md +1 -1
- data/MAINTAINERS.md +2 -2
- data/README.md +3 -2
- data/Rakefile +2 -1
- data/SECURITY.md +26 -1
- data/lib/fluent/command/cat.rb +3 -3
- data/lib/fluent/compat/exec_util.rb +20 -5
- data/lib/fluent/config/basic_parser.rb +0 -1
- data/lib/fluent/config/literal_parser.rb +22 -10
- data/lib/fluent/config/types.rb +10 -2
- data/lib/fluent/config/yaml_parser/loader.rb +12 -1
- data/lib/fluent/config/yaml_parser/parser.rb +14 -13
- data/lib/fluent/configurable.rb +1 -0
- data/lib/fluent/daemon.rb +2 -1
- data/lib/fluent/env.rb +1 -0
- data/lib/fluent/event.rb +37 -0
- data/lib/fluent/load.rb +0 -1
- data/lib/fluent/log.rb +2 -2
- data/lib/fluent/plugin/base.rb +7 -2
- data/lib/fluent/plugin/buf_file.rb +3 -3
- data/lib/fluent/plugin/buf_file_single.rb +6 -2
- data/lib/fluent/plugin/buffer/chunk.rb +14 -6
- data/lib/fluent/plugin/buffer/file_chunk.rb +28 -13
- data/lib/fluent/plugin/buffer/file_single_chunk.rb +28 -13
- data/lib/fluent/plugin/buffer.rb +125 -41
- data/lib/fluent/plugin/compressable.rb +2 -2
- data/lib/fluent/plugin/extractor.rb +17 -6
- data/lib/fluent/plugin/filter_record_transformer.rb +2 -1
- data/lib/fluent/plugin/formatter_out_file.rb +3 -4
- data/lib/fluent/plugin/in_exec.rb +6 -4
- data/lib/fluent/plugin/in_forward.rb +39 -29
- data/lib/fluent/plugin/in_http.rb +83 -2
- data/lib/fluent/plugin/in_monitor_agent.rb +2 -1
- data/lib/fluent/plugin/in_object_space.rb +2 -2
- data/lib/fluent/plugin/in_sample.rb +2 -1
- data/lib/fluent/plugin/in_syslog.rb +72 -6
- data/lib/fluent/plugin/in_tail/compatibility.rb +111 -0
- data/lib/fluent/plugin/in_tail/dir_watcher.rb +713 -0
- data/lib/fluent/plugin/in_tail/group_watch.rb +13 -13
- data/lib/fluent/plugin/in_tail/io_handler.rb +391 -0
- data/lib/fluent/plugin/in_tail/line_feeder.rb +230 -0
- data/lib/fluent/plugin/in_tail/stat_watcher.rb +52 -0
- data/lib/fluent/plugin/in_tail/tail_watcher.rb +276 -0
- data/lib/fluent/plugin/in_tail/worker_pool.rb +298 -0
- data/lib/fluent/plugin/in_tail.rb +324 -815
- data/lib/fluent/plugin/in_unix.rb +5 -3
- data/lib/fluent/plugin/out_exec_filter.rb +2 -2
- data/lib/fluent/plugin/out_file.rb +10 -4
- data/lib/fluent/plugin/out_forward/connection_manager.rb +18 -0
- data/lib/fluent/plugin/out_forward.rb +21 -2
- data/lib/fluent/plugin/out_http.rb +0 -10
- data/lib/fluent/plugin/output.rb +81 -31
- data/lib/fluent/plugin/parser_csv.rb +7 -2
- data/lib/fluent/plugin/parser_json.rb +25 -12
- data/lib/fluent/plugin/parser_ltsv.rb +2 -4
- data/lib/fluent/plugin/parser_msgpack.rb +7 -6
- data/lib/fluent/plugin/parser_syslog.rb +55 -8
- data/lib/fluent/plugin/parser_tsv.rb +6 -1
- data/lib/fluent/plugin/sd_file.rb +2 -1
- data/lib/fluent/plugin/storage_local.rb +2 -2
- data/lib/fluent/plugin_helper/http_server/server.rb +29 -13
- data/lib/fluent/plugin_helper/http_server.rb +2 -1
- data/lib/fluent/root_agent.rb +1 -1
- data/lib/fluent/supervisor.rb +32 -6
- data/lib/fluent/system_config.rb +5 -2
- data/lib/fluent/time.rb +8 -7
- data/lib/fluent/version.rb +1 -1
- data/templates/new_gem/.gitignore +8 -0
- metadata +41 -21
|
@@ -78,19 +78,13 @@ module Fluent::Plugin
|
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
def add_path_to_group_watcher(path)
|
|
81
|
+
def add_path_to_group_watcher(path, watcher)
|
|
82
82
|
return nil if @group.nil?
|
|
83
83
|
group_watcher = find_group_from_metadata(path)
|
|
84
|
-
group_watcher.add(path
|
|
84
|
+
group_watcher.add(path, watcher)
|
|
85
85
|
group_watcher
|
|
86
86
|
end
|
|
87
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
88
|
def construct_group_key(named_captures)
|
|
95
89
|
match_rule = []
|
|
96
90
|
@group_keys.each { |key|
|
|
@@ -135,6 +129,7 @@ module Fluent::Plugin
|
|
|
135
129
|
FileCounter = Struct.new(
|
|
136
130
|
:number_lines_read,
|
|
137
131
|
:start_reading_time,
|
|
132
|
+
:watchers,
|
|
138
133
|
)
|
|
139
134
|
|
|
140
135
|
def initialize(rate_period = 60, limit = -1)
|
|
@@ -143,8 +138,8 @@ module Fluent::Plugin
|
|
|
143
138
|
@limit = limit
|
|
144
139
|
end
|
|
145
140
|
|
|
146
|
-
def add(path)
|
|
147
|
-
@current_paths[path]
|
|
141
|
+
def add(path, watcher)
|
|
142
|
+
(@current_paths[path] ||= FileCounter.new(0, nil, Set.new)).watchers << watcher
|
|
148
143
|
end
|
|
149
144
|
|
|
150
145
|
def include?(path)
|
|
@@ -155,8 +150,11 @@ module Fluent::Plugin
|
|
|
155
150
|
@current_paths.size
|
|
156
151
|
end
|
|
157
152
|
|
|
158
|
-
def delete(path)
|
|
159
|
-
@current_paths
|
|
153
|
+
def delete(path, watcher)
|
|
154
|
+
counter = @current_paths[path]
|
|
155
|
+
return unless counter
|
|
156
|
+
counter.watchers.delete(watcher)
|
|
157
|
+
@current_paths.delete(path) if counter.watchers.empty?
|
|
160
158
|
end
|
|
161
159
|
|
|
162
160
|
def update_reading_time(path)
|
|
@@ -185,7 +183,9 @@ module Fluent::Plugin
|
|
|
185
183
|
return true if @limit == 0
|
|
186
184
|
|
|
187
185
|
return false if @limit < 0
|
|
188
|
-
|
|
186
|
+
# Give every path at least one line. With more paths than the limit, the integer division
|
|
187
|
+
# leaves no allotment, and a path that has not started reading yet has no start time to compare.
|
|
188
|
+
return false if @current_paths[path].number_lines_read < [@limit / size, 1].max
|
|
189
189
|
|
|
190
190
|
# update_reading_time(path)
|
|
191
191
|
if limit_time_period_reached?(path) # Exceeds limit
|
|
@@ -0,0 +1,391 @@
|
|
|
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
|
+
require 'fluent/clock'
|
|
19
|
+
require 'fluent/file_wrapper'
|
|
20
|
+
|
|
21
|
+
module Fluent::Plugin
|
|
22
|
+
class TailInput < Fluent::Plugin::Input
|
|
23
|
+
class TailWatcher
|
|
24
|
+
class FIFO
|
|
25
|
+
def initialize(encoding, log, max_line_size=nil, encoding_to_convert=nil)
|
|
26
|
+
@buffer = ''.force_encoding(encoding)
|
|
27
|
+
@eol = "\n".encode(encoding).freeze
|
|
28
|
+
@encoding_to_convert = encoding_to_convert
|
|
29
|
+
@max_line_size = max_line_size
|
|
30
|
+
@skip_current_line = false
|
|
31
|
+
@skipping_current_line_bytesize = 0
|
|
32
|
+
@log = log
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_reader :buffer, :max_line_size
|
|
36
|
+
|
|
37
|
+
def <<(chunk)
|
|
38
|
+
@buffer << chunk
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def convert(s)
|
|
42
|
+
if @encoding_to_convert
|
|
43
|
+
s.encode!(@encoding_to_convert)
|
|
44
|
+
else
|
|
45
|
+
s
|
|
46
|
+
end
|
|
47
|
+
rescue
|
|
48
|
+
s.encode!(@encoding_to_convert, :invalid => :replace, :undef => :replace)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def read_lines(lines)
|
|
52
|
+
idx = @buffer.index(@eol)
|
|
53
|
+
has_skipped_line = false
|
|
54
|
+
|
|
55
|
+
until idx.nil?
|
|
56
|
+
# Using freeze and slice is faster than slice!
|
|
57
|
+
# See https://github.com/fluent/fluentd/pull/2527
|
|
58
|
+
@buffer.freeze
|
|
59
|
+
slice_position = idx + 1
|
|
60
|
+
rbuf = @buffer.slice(0, slice_position)
|
|
61
|
+
@buffer = @buffer.slice(slice_position, @buffer.size - slice_position)
|
|
62
|
+
idx = @buffer.index(@eol)
|
|
63
|
+
|
|
64
|
+
is_long_line = @max_line_size && (
|
|
65
|
+
@skip_current_line || rbuf.bytesize > @max_line_size
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if is_long_line
|
|
69
|
+
@log.warn "received line length is longer than #{@max_line_size}"
|
|
70
|
+
if @skip_current_line
|
|
71
|
+
@log.debug("The continuing line is finished. Finally discarded data: ") { convert(rbuf).chomp }
|
|
72
|
+
else
|
|
73
|
+
@log.debug("skipped line: ") { convert(rbuf).chomp }
|
|
74
|
+
end
|
|
75
|
+
has_skipped_line = true
|
|
76
|
+
@skip_current_line = false
|
|
77
|
+
@skipping_current_line_bytesize = 0
|
|
78
|
+
next
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
lines << convert(rbuf)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
is_long_current_line = @max_line_size && (
|
|
85
|
+
@skip_current_line || @buffer.bytesize > @max_line_size
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if is_long_current_line
|
|
89
|
+
@log.debug(
|
|
90
|
+
"The continuing current line length is longer than #{@max_line_size}." +
|
|
91
|
+
" The received data will be discarded until this line is finished." +
|
|
92
|
+
" Discarded data: "
|
|
93
|
+
) { convert(@buffer).chomp }
|
|
94
|
+
@skip_current_line = true
|
|
95
|
+
@skipping_current_line_bytesize += @buffer.bytesize
|
|
96
|
+
@buffer.clear
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
return has_skipped_line
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def reading_bytesize
|
|
103
|
+
return @skipping_current_line_bytesize if @skip_current_line
|
|
104
|
+
@buffer.bytesize
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class IOHandler
|
|
109
|
+
BYTES_TO_READ = 64 * 1024
|
|
110
|
+
SHUTDOWN_TIMEOUT = 5
|
|
111
|
+
ASYNC_PENDING = :async_pending
|
|
112
|
+
|
|
113
|
+
attr_accessor :shutdown_timeout
|
|
114
|
+
|
|
115
|
+
def initialize(watcher, path:, read_lines_limit:, read_bytes_limit_per_second:, max_line_size: nil, log:, open_on_every_update:, encoding: Encoding::ASCII_8BIT, encoding_to_convert: nil, metrics:, &receive_lines)
|
|
116
|
+
@watcher = watcher
|
|
117
|
+
@path = path
|
|
118
|
+
@read_lines_limit = read_lines_limit
|
|
119
|
+
@read_bytes_limit_per_second = read_bytes_limit_per_second
|
|
120
|
+
@receive_lines = receive_lines
|
|
121
|
+
@open_on_every_update = open_on_every_update
|
|
122
|
+
@encoding = encoding
|
|
123
|
+
@fifo = FIFO.new(encoding, log, max_line_size, encoding_to_convert)
|
|
124
|
+
@lines = []
|
|
125
|
+
@io = nil
|
|
126
|
+
@notify_mutex = Mutex.new
|
|
127
|
+
@log = log
|
|
128
|
+
@start_reading_time = nil
|
|
129
|
+
@number_bytes_read = 0
|
|
130
|
+
@shutdown_start_time = nil
|
|
131
|
+
@shutdown_timeout = SHUTDOWN_TIMEOUT
|
|
132
|
+
@shutdown_mutex = Mutex.new
|
|
133
|
+
@eof = false
|
|
134
|
+
@metrics = metrics
|
|
135
|
+
@async_pending = false
|
|
136
|
+
@notify_pending = false
|
|
137
|
+
@pending_position = nil
|
|
138
|
+
@pending_read_more = false
|
|
139
|
+
|
|
140
|
+
@log.info "following tail of #{@path}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def group_watcher
|
|
144
|
+
@watcher.group_watcher
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def on_notify
|
|
148
|
+
@notify_mutex.synchronize { handle_notify }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def ready_to_shutdown(shutdown_start_time = nil)
|
|
152
|
+
@shutdown_mutex.synchronize {
|
|
153
|
+
@shutdown_start_time =
|
|
154
|
+
shutdown_start_time || Fluent::Clock.now
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def close
|
|
159
|
+
if @io && !@io.closed?
|
|
160
|
+
@io.close
|
|
161
|
+
@io = nil
|
|
162
|
+
@metrics.closed.inc
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def opened?
|
|
167
|
+
!!@io
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def eof?
|
|
171
|
+
@eof
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def drained?
|
|
175
|
+
@eof && @lines.empty? && !@async_pending
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def pending?
|
|
179
|
+
@async_pending
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Completes a receive callback that returned ASYNC_PENDING. The caller
|
|
183
|
+
# must invoke TailWatcher#on_notify when this returns true.
|
|
184
|
+
def complete_async(success)
|
|
185
|
+
@notify_mutex.synchronize do
|
|
186
|
+
raise 'no asynchronous receive is pending' unless @async_pending
|
|
187
|
+
|
|
188
|
+
begin
|
|
189
|
+
if success
|
|
190
|
+
@watcher.pe.update_pos(@pending_position)
|
|
191
|
+
@lines.clear
|
|
192
|
+
end
|
|
193
|
+
should_notify = (success && @pending_read_more) || @notify_pending
|
|
194
|
+
@pending_read_more = false if success
|
|
195
|
+
should_notify
|
|
196
|
+
rescue => error
|
|
197
|
+
@log.error error.to_s
|
|
198
|
+
@log.error_backtrace
|
|
199
|
+
@notify_pending
|
|
200
|
+
ensure
|
|
201
|
+
@async_pending = false
|
|
202
|
+
@notify_pending = false
|
|
203
|
+
@pending_position = nil
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
private
|
|
209
|
+
|
|
210
|
+
def limit_bytes_per_second_reached?
|
|
211
|
+
return false if @read_bytes_limit_per_second < 0 # not enabled by conf
|
|
212
|
+
return false if @number_bytes_read < @read_bytes_limit_per_second
|
|
213
|
+
|
|
214
|
+
@start_reading_time ||= Fluent::Clock.now
|
|
215
|
+
time_spent_reading = Fluent::Clock.now - @start_reading_time
|
|
216
|
+
@log.debug("time_spent_reading: #{time_spent_reading} #{ @watcher.path}")
|
|
217
|
+
|
|
218
|
+
if time_spent_reading < 1
|
|
219
|
+
true
|
|
220
|
+
else
|
|
221
|
+
@start_reading_time = nil
|
|
222
|
+
@number_bytes_read = 0
|
|
223
|
+
false
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def should_shutdown_now?
|
|
228
|
+
# Ensure to read all remaining lines, but abort immediately if it
|
|
229
|
+
# seems to take too long time.
|
|
230
|
+
@shutdown_mutex.synchronize {
|
|
231
|
+
return false if @shutdown_start_time.nil?
|
|
232
|
+
return Fluent::Clock.now - @shutdown_start_time > @shutdown_timeout
|
|
233
|
+
}
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def handle_notify
|
|
237
|
+
if @async_pending
|
|
238
|
+
@notify_pending = true
|
|
239
|
+
return
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# A notification may arrive while a rejected batch is waiting for capacity.
|
|
243
|
+
@pending_read_more = true unless @lines.empty?
|
|
244
|
+
|
|
245
|
+
if limit_bytes_per_second_reached? || group_watcher&.limit_lines_reached?(@path)
|
|
246
|
+
@metrics.throttled.inc
|
|
247
|
+
return
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
with_io do |io|
|
|
251
|
+
iobuf = ''.force_encoding(@encoding)
|
|
252
|
+
begin
|
|
253
|
+
read_more = false
|
|
254
|
+
has_skipped_line = false
|
|
255
|
+
|
|
256
|
+
if !io.nil? && @lines.empty?
|
|
257
|
+
begin
|
|
258
|
+
while true
|
|
259
|
+
@start_reading_time ||= Fluent::Clock.now
|
|
260
|
+
group_watcher&.update_reading_time(@path)
|
|
261
|
+
|
|
262
|
+
data = io.readpartial(BYTES_TO_READ, iobuf)
|
|
263
|
+
@eof = false
|
|
264
|
+
@number_bytes_read += data.bytesize
|
|
265
|
+
@fifo << data
|
|
266
|
+
|
|
267
|
+
n_lines_before_read = @lines.size
|
|
268
|
+
has_skipped_line = @fifo.read_lines(@lines) || has_skipped_line
|
|
269
|
+
group_watcher&.update_lines_read(@path, @lines.size - n_lines_before_read)
|
|
270
|
+
|
|
271
|
+
group_watcher_limit = group_watcher&.limit_lines_reached?(@path)
|
|
272
|
+
@log.debug "Reading Limit exceeded #{@path} #{group_watcher.number_lines_read}" if group_watcher_limit
|
|
273
|
+
|
|
274
|
+
if group_watcher_limit || limit_bytes_per_second_reached? || should_shutdown_now?
|
|
275
|
+
# Just get out from tailing loop.
|
|
276
|
+
@metrics.throttled.inc if group_watcher_limit || limit_bytes_per_second_reached?
|
|
277
|
+
read_more = false
|
|
278
|
+
break
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
if @lines.size >= @read_lines_limit
|
|
282
|
+
# not to use too much memory in case the file is very large
|
|
283
|
+
read_more = true
|
|
284
|
+
break
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
rescue EOFError
|
|
288
|
+
@eof = true
|
|
289
|
+
ensure
|
|
290
|
+
iobuf.clear
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
if @lines.empty?
|
|
295
|
+
@watcher.pe.update_pos(io.pos - @fifo.reading_bytesize) if has_skipped_line
|
|
296
|
+
else
|
|
297
|
+
received = @receive_lines.call(@lines, @watcher)
|
|
298
|
+
if received == ASYNC_PENDING
|
|
299
|
+
@pending_position = io.pos - @fifo.reading_bytesize
|
|
300
|
+
@pending_read_more ||= read_more
|
|
301
|
+
@async_pending = true
|
|
302
|
+
read_more = false
|
|
303
|
+
elsif received
|
|
304
|
+
@watcher.pe.update_pos(io.pos - @fifo.reading_bytesize)
|
|
305
|
+
@lines.clear
|
|
306
|
+
@pending_read_more = false
|
|
307
|
+
else
|
|
308
|
+
# A rejected worker submission must retain the continuation for retry.
|
|
309
|
+
@pending_read_more ||= read_more
|
|
310
|
+
read_more = false
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end while read_more
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def open
|
|
318
|
+
io = Fluent::FileWrapper.open(@path)
|
|
319
|
+
io.seek(@watcher.pe.read_pos + @fifo.reading_bytesize)
|
|
320
|
+
@metrics.opened.inc
|
|
321
|
+
io
|
|
322
|
+
rescue RangeError
|
|
323
|
+
io.close if io
|
|
324
|
+
raise WatcherSetupError, "seek error with #{@path}: file position = #{@watcher.pe.read_pos.to_s(16)}, reading bytesize = #{@fifo.reading_bytesize.to_s(16)}"
|
|
325
|
+
rescue Errno::EACCES => e
|
|
326
|
+
@log.warn "#{e}"
|
|
327
|
+
nil
|
|
328
|
+
rescue Errno::ENOENT
|
|
329
|
+
nil
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def with_io
|
|
333
|
+
if @open_on_every_update
|
|
334
|
+
io = open
|
|
335
|
+
begin
|
|
336
|
+
yield io
|
|
337
|
+
ensure
|
|
338
|
+
io.close unless io.nil?
|
|
339
|
+
end
|
|
340
|
+
else
|
|
341
|
+
@io ||= open
|
|
342
|
+
yield @io
|
|
343
|
+
@eof = true if @io.nil?
|
|
344
|
+
end
|
|
345
|
+
rescue WatcherSetupError => e
|
|
346
|
+
close
|
|
347
|
+
@eof = true
|
|
348
|
+
raise e
|
|
349
|
+
rescue
|
|
350
|
+
@log.error $!.to_s
|
|
351
|
+
@log.error_backtrace
|
|
352
|
+
close
|
|
353
|
+
@eof = true
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
class NullIOHandler
|
|
358
|
+
def initialize
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def ready_to_shutdown(shutdown_start_time = nil)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def io
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def on_notify
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def close
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def opened?
|
|
374
|
+
false
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def eof?
|
|
378
|
+
true
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def drained?
|
|
382
|
+
true
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def pending?
|
|
386
|
+
false
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
@@ -0,0 +1,230 @@
|
|
|
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
|
+
require 'fluent/event'
|
|
19
|
+
require 'fluent/plugin/buffer'
|
|
20
|
+
require 'fluent/clock'
|
|
21
|
+
|
|
22
|
+
module Fluent::Plugin
|
|
23
|
+
class TailInput < Fluent::Plugin::Input
|
|
24
|
+
# LineFeeder provides the built-in line parsing and emitting for TailInput.
|
|
25
|
+
# To preserve compatibility with TailInput subclasses, TailInput passes its
|
|
26
|
+
# line-processing methods as handlers. This keeps subclass overrides in the
|
|
27
|
+
# call path, and super reaches this built-in implementation.
|
|
28
|
+
class LineFeeder
|
|
29
|
+
def initialize(parser:, router_provider:, log:, tag:, tag_prefix:, tag_suffix:, path_key:, emit_unmatched_lines:, multiline_mode:, parse_handler: nil, convert_handler: nil, flush_handler: nil)
|
|
30
|
+
@parser = parser
|
|
31
|
+
@router_provider = router_provider
|
|
32
|
+
@log = log
|
|
33
|
+
@tag = tag
|
|
34
|
+
@tag_prefix = tag_prefix
|
|
35
|
+
@tag_suffix = tag_suffix
|
|
36
|
+
@path_key = path_key
|
|
37
|
+
@emit_unmatched_lines = emit_unmatched_lines
|
|
38
|
+
@multiline_mode = multiline_mode
|
|
39
|
+
@parse_handler = parse_handler || if multiline_mode
|
|
40
|
+
method(:parse_multilines)
|
|
41
|
+
else
|
|
42
|
+
method(:parse_singleline)
|
|
43
|
+
end
|
|
44
|
+
@convert_handler = convert_handler || method(:convert_line_to_event)
|
|
45
|
+
@flush_handler = flush_handler || method(:flush_buffer)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Builds a per-file feed. The flush interval applies only to multiline
|
|
49
|
+
# parsers with a firstline.
|
|
50
|
+
def new_file_feed(flush_interval: nil)
|
|
51
|
+
timer_flush_interval = (@multiline_mode && @parser.has_firstline?) ? flush_interval : nil
|
|
52
|
+
FileFeed.new(self, flush_interval: timer_flush_interval, flush_handler: @flush_handler)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Parses and emits a batch for FileFeed and legacy watchers.
|
|
56
|
+
# The flush deadline is started by #parse_multilines for compatibility.
|
|
57
|
+
# @return true if no error or unrecoverable error happens in emit action. false if got BufferOverflowError
|
|
58
|
+
def feed_lines(lines, tail_watcher)
|
|
59
|
+
emit(parse(lines, tail_watcher), tail_watcher)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def parse(lines, tail_watcher)
|
|
63
|
+
@parse_handler.call(lines, tail_watcher)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Emits the events returned by #parse, with the tag of the file of the
|
|
67
|
+
# watcher which the lines were read from.
|
|
68
|
+
# @return true if no error or unrecoverable error happens in emit action. false if got BufferOverflowError
|
|
69
|
+
def emit(es, tail_watcher)
|
|
70
|
+
return true if es.empty?
|
|
71
|
+
|
|
72
|
+
begin
|
|
73
|
+
@router_provider.call.emit_stream(tag_for(tail_watcher), es)
|
|
74
|
+
rescue Fluent::Plugin::Buffer::BufferOverflowError
|
|
75
|
+
return false
|
|
76
|
+
rescue
|
|
77
|
+
# ignore non BufferQueueLimitError errors because in_tail can't recover. Engine shows logs and backtraces.
|
|
78
|
+
return true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
return true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def flush_buffer(tw, buf)
|
|
85
|
+
buf.chomp!
|
|
86
|
+
@parser.parse(buf) { |time, record|
|
|
87
|
+
if time && record
|
|
88
|
+
record[@path_key] ||= tw.path unless @path_key.nil?
|
|
89
|
+
@router_provider.call.emit(tag_for(tw), time, record)
|
|
90
|
+
else
|
|
91
|
+
if @emit_unmatched_lines
|
|
92
|
+
record = { 'unmatched_line' => buf }
|
|
93
|
+
record[@path_key] ||= tw.path unless @path_key.nil?
|
|
94
|
+
@router_provider.call.emit(tag_for(tw), Fluent::EventTime.now, record)
|
|
95
|
+
end
|
|
96
|
+
@log.warn "got incomplete line at shutdown from #{tw.path}: #{buf.inspect}"
|
|
97
|
+
end
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Per-file state replacing TailWatcher::LineBufferTimerFlusher.
|
|
102
|
+
class FileFeed
|
|
103
|
+
attr_accessor :line_buffer
|
|
104
|
+
|
|
105
|
+
def initialize(line_feeder, flush_interval:, flush_handler:)
|
|
106
|
+
@line_feeder = line_feeder
|
|
107
|
+
@flush_interval = flush_interval
|
|
108
|
+
@flush_handler = flush_handler
|
|
109
|
+
@line_buffer = nil
|
|
110
|
+
@start = nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @return true if no error or unrecoverable error happens in emit action. false if got BufferOverflowError
|
|
114
|
+
def feed_lines(lines, tail_watcher)
|
|
115
|
+
@line_feeder.feed_lines(lines, tail_watcher)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def on_notify(tail_watcher)
|
|
119
|
+
return unless @start
|
|
120
|
+
|
|
121
|
+
if Fluent::Clock.now - @start >= @flush_interval
|
|
122
|
+
flush_buffer(@line_buffer, tail_watcher) if @line_buffer
|
|
123
|
+
@line_buffer = nil
|
|
124
|
+
@start = nil
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def close(tail_watcher)
|
|
129
|
+
return unless @line_buffer
|
|
130
|
+
|
|
131
|
+
flush_buffer(@line_buffer, tail_watcher)
|
|
132
|
+
@line_buffer = nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def flush_buffer(buf, tail_watcher)
|
|
136
|
+
@flush_handler.call(tail_watcher, buf)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Starts the flush deadline. Overrides that do not call super may call
|
|
140
|
+
# this method to preserve the old timer behavior.
|
|
141
|
+
def reset_timer
|
|
142
|
+
return unless @flush_interval
|
|
143
|
+
|
|
144
|
+
@start = Fluent::Clock.now
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def convert_line_to_event(line, es, tail_watcher)
|
|
149
|
+
begin
|
|
150
|
+
line.chomp! # remove \n
|
|
151
|
+
@parser.parse(line) { |time, record|
|
|
152
|
+
if time && record
|
|
153
|
+
record[@path_key] ||= tail_watcher.path unless @path_key.nil?
|
|
154
|
+
es.add(time, record)
|
|
155
|
+
else
|
|
156
|
+
if @emit_unmatched_lines
|
|
157
|
+
record = {'unmatched_line' => line}
|
|
158
|
+
record[@path_key] ||= tail_watcher.path unless @path_key.nil?
|
|
159
|
+
es.add(Fluent::EventTime.now, record)
|
|
160
|
+
end
|
|
161
|
+
@log.warn { "pattern not matched: #{line.inspect}" }
|
|
162
|
+
end
|
|
163
|
+
}
|
|
164
|
+
rescue => e
|
|
165
|
+
@log.warn 'invalid line found', file: tail_watcher.path, line: line, error: e.to_s
|
|
166
|
+
@log.debug_backtrace(e.backtrace)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def parse_singleline(lines, tail_watcher)
|
|
171
|
+
es = Fluent::MultiEventStream.new
|
|
172
|
+
lines.each { |line|
|
|
173
|
+
@convert_handler.call(line, es, tail_watcher)
|
|
174
|
+
}
|
|
175
|
+
es
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Parses multiline input and starts the flush deadline before parsing, as
|
|
179
|
+
# TailInput#parse_multilines did before LineFeeder was extracted.
|
|
180
|
+
def parse_multilines(lines, tail_watcher)
|
|
181
|
+
file_feed = tail_watcher.file_feed
|
|
182
|
+
lb = file_feed.line_buffer
|
|
183
|
+
es = Fluent::MultiEventStream.new
|
|
184
|
+
if @parser.has_firstline?
|
|
185
|
+
file_feed.reset_timer
|
|
186
|
+
lines.each { |line|
|
|
187
|
+
if @parser.firstline?(line)
|
|
188
|
+
if lb
|
|
189
|
+
@convert_handler.call(lb, es, tail_watcher)
|
|
190
|
+
end
|
|
191
|
+
lb = line
|
|
192
|
+
else
|
|
193
|
+
if lb.nil?
|
|
194
|
+
if @emit_unmatched_lines
|
|
195
|
+
@convert_handler.call(line, es, tail_watcher)
|
|
196
|
+
end
|
|
197
|
+
@log.warn "got incomplete line before first line from #{tail_watcher.path}: #{line.inspect}"
|
|
198
|
+
else
|
|
199
|
+
lb << line
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
}
|
|
203
|
+
else
|
|
204
|
+
lb ||= ''
|
|
205
|
+
lines.each do |line|
|
|
206
|
+
lb << line
|
|
207
|
+
@parser.parse(lb) { |time, record|
|
|
208
|
+
if time && record
|
|
209
|
+
@convert_handler.call(lb, es, tail_watcher)
|
|
210
|
+
lb = ''
|
|
211
|
+
end
|
|
212
|
+
}
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
file_feed.line_buffer = lb
|
|
216
|
+
es
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
def tag_for(tw)
|
|
222
|
+
if @tag_prefix || @tag_suffix
|
|
223
|
+
@tag_prefix + tw.tag + @tag_suffix
|
|
224
|
+
else
|
|
225
|
+
@tag
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|