fluentd 1.19.4 → 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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +46 -0
  3. data/CHANGELOG.md +75 -0
  4. data/GithubWorkflow.md +1 -1
  5. data/MAINTAINERS.md +2 -2
  6. data/README.md +3 -2
  7. data/Rakefile +2 -1
  8. data/SECURITY.md +26 -1
  9. data/lib/fluent/command/cat.rb +2 -2
  10. data/lib/fluent/compat/exec_util.rb +20 -5
  11. data/lib/fluent/config/literal_parser.rb +4 -3
  12. data/lib/fluent/config/yaml_parser/loader.rb +12 -1
  13. data/lib/fluent/config/yaml_parser/parser.rb +14 -13
  14. data/lib/fluent/event.rb +37 -0
  15. data/lib/fluent/load.rb +0 -1
  16. data/lib/fluent/log.rb +2 -2
  17. data/lib/fluent/plugin/buf_file.rb +2 -2
  18. data/lib/fluent/plugin/buf_file_single.rb +6 -2
  19. data/lib/fluent/plugin/buffer/file_chunk.rb +23 -10
  20. data/lib/fluent/plugin/buffer/file_single_chunk.rb +23 -10
  21. data/lib/fluent/plugin/buffer.rb +102 -36
  22. data/lib/fluent/plugin/formatter_out_file.rb +3 -4
  23. data/lib/fluent/plugin/in_exec.rb +6 -4
  24. data/lib/fluent/plugin/in_forward.rb +39 -29
  25. data/lib/fluent/plugin/in_object_space.rb +2 -2
  26. data/lib/fluent/plugin/in_syslog.rb +2 -2
  27. data/lib/fluent/plugin/in_tail/compatibility.rb +111 -0
  28. data/lib/fluent/plugin/in_tail/dir_watcher.rb +713 -0
  29. data/lib/fluent/plugin/in_tail/group_watch.rb +13 -13
  30. data/lib/fluent/plugin/in_tail/io_handler.rb +391 -0
  31. data/lib/fluent/plugin/in_tail/line_feeder.rb +230 -0
  32. data/lib/fluent/plugin/in_tail/stat_watcher.rb +52 -0
  33. data/lib/fluent/plugin/in_tail/tail_watcher.rb +276 -0
  34. data/lib/fluent/plugin/in_tail/worker_pool.rb +298 -0
  35. data/lib/fluent/plugin/in_tail.rb +324 -815
  36. data/lib/fluent/plugin/in_unix.rb +5 -3
  37. data/lib/fluent/plugin/out_exec_filter.rb +2 -2
  38. data/lib/fluent/plugin/out_file.rb +10 -4
  39. data/lib/fluent/plugin/out_forward.rb +8 -0
  40. data/lib/fluent/plugin/out_http.rb +0 -10
  41. data/lib/fluent/plugin/output.rb +49 -19
  42. data/lib/fluent/plugin/parser_csv.rb +7 -2
  43. data/lib/fluent/plugin/parser_json.rb +20 -12
  44. data/lib/fluent/plugin/parser_ltsv.rb +2 -4
  45. data/lib/fluent/plugin/parser_msgpack.rb +7 -6
  46. data/lib/fluent/plugin/parser_syslog.rb +52 -5
  47. data/lib/fluent/plugin/parser_tsv.rb +6 -1
  48. data/lib/fluent/plugin_helper/http_server/server.rb +29 -13
  49. data/lib/fluent/plugin_helper/http_server.rb +2 -1
  50. data/lib/fluent/root_agent.rb +1 -1
  51. data/lib/fluent/supervisor.rb +24 -5
  52. data/lib/fluent/system_config.rb +5 -2
  53. data/lib/fluent/time.rb +8 -7
  54. data/lib/fluent/version.rb +1 -1
  55. data/templates/new_gem/.gitignore +8 -0
  56. metadata +39 -16
@@ -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) unless group_watcher.include?(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] = FileCounter.new(0, nil)
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.delete(path)
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
- return false if @current_paths[path].number_lines_read < @limit / size
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