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
@@ -72,6 +72,9 @@ module Fluent
72
72
  desc 'If true, chunks are thrown away when unrecoverable error happens'
73
73
  config_param :disable_chunk_backup, :bool, default: false
74
74
 
75
+ desc 'The approximate size limit of the evacuated chunk files in the evacuation directory. When the retry limit is reached, queued chunks that fit within the remaining capacity are evacuated, and the others are purged without being evacuated. The total size may exceed this limit due to chunk metadata or concurrent evacuation by multiple workers. Set to 0 to disable evacuation. nil (the default) means no limit.'
76
+ config_param :evacuate_limit_size, :size, default: nil
77
+
75
78
  Metadata = Struct.new(:timekey, :tag, :variables, :seq) do
76
79
  def initialize(timekey, tag, variables)
77
80
  super(timekey, tag, variables, 0)
@@ -227,6 +230,20 @@ module Fluent
227
230
  unless @queue_limit_length.nil?
228
231
  @total_limit_size = @chunk_limit_size * @queue_limit_length
229
232
  end
233
+ if @evacuate_limit_size
234
+ raise Fluent::ConfigError, "evacuate_limit_size must be 0 or larger" if @evacuate_limit_size < 0
235
+ if !persistent?
236
+ log.warn "evacuate_limit_size is ignored because this buffer does not evacuate chunks"
237
+ @evacuate_limit_size = nil
238
+ elsif @evacuate_limit_size > 0
239
+ unless owner.plugin_id_configured?
240
+ log.warn "evacuate_limit_size should be used with @id, because the evacuation directory without @id can change when the configuration is changed or reloaded"
241
+ end
242
+ if @evacuate_limit_size < @chunk_limit_size
243
+ log.warn "evacuate_limit_size is smaller than chunk_limit_size, so full chunks may not be evacuated", evacuate_limit_size: @evacuate_limit_size, chunk_limit_size: @chunk_limit_size
244
+ end
245
+ end
246
+ end
230
247
  @stage_length_metrics = metrics_create(namespace: "fluentd", subsystem: "buffer", name: "stage_length",
231
248
  help_text: 'Length of stage buffers', prefer_gauge: true)
232
249
  @stage_length_metrics.set(0)
@@ -383,6 +400,12 @@ module Fluent
383
400
  if enqueue || first_chunk.unstaged? || chunk_size_full?(first_chunk)
384
401
  chunks_to_enqueue << first_chunk
385
402
  end
403
+ if (bytesize = staged_bytesizes_by_chunk[first_chunk])
404
+ # Account while the chunk lock is still held so enqueue_chunk
405
+ # cannot remove the chunk and subtract its bytes first.
406
+ @stage_size_metrics.add(bytesize)
407
+ log.on_trace { log.trace { "chunk #{first_chunk.path} size_added: #{bytesize} new_size: #{first_chunk.bytesize}" } }
408
+ end
386
409
  first_chunk.mon_exit
387
410
  rescue
388
411
  operated_chunks.unshift(first_chunk)
@@ -397,6 +420,10 @@ module Fluent
397
420
  if enqueue || chunk.unstaged? || chunk_size_full?(chunk)
398
421
  chunks_to_enqueue << chunk
399
422
  end
423
+ if (bytesize = staged_bytesizes_by_chunk[chunk])
424
+ @stage_size_metrics.add(bytesize)
425
+ log.on_trace { log.trace { "chunk #{chunk.path} size_added: #{bytesize} new_size: #{chunk.bytesize}" } }
426
+ end
400
427
  chunk.mon_exit
401
428
  rescue => e
402
429
  chunk.rollback
@@ -405,44 +432,46 @@ module Fluent
405
432
  end
406
433
  end
407
434
 
408
- # All locks about chunks are released.
409
-
410
- #
411
- # Now update the stage, stage_size with proper locking
412
- # FIX FOR stage_size miscomputation - https://github.com/fluent/fluentd/issues/2712
413
- #
414
- staged_bytesizes_by_chunk.each do |chunk, bytesize|
415
- chunk.synchronize do
416
- synchronize { @stage_size_metrics.add(bytesize) }
417
- log.on_trace { log.trace { "chunk #{chunk.path} size_added: #{bytesize} new_size: #{chunk.bytesize}" } }
418
- end
419
- end
435
+ # All locks about chunks are released. Stage-size accounting for the
436
+ # staged chunks happened inside each chunk monitor above, so a
437
+ # concurrent enqueue_chunk cannot observe a transiently negative
438
+ # stage_size (issue #2712 race).
420
439
 
440
+ enqueue_errors = []
421
441
  chunks_to_enqueue.each do |c|
422
- if c.staged? && (enqueue || chunk_size_full?(c))
423
- m = c.metadata
424
- enqueue_chunk(m)
425
- if unstaged_chunks[m] && !unstaged_chunks[m].empty?
426
- u = unstaged_chunks[m].pop
427
- u.synchronize do
428
- if u.unstaged? && !chunk_size_full?(u)
429
- # `u.metadata.seq` and `m.seq` can be different but Buffer#enqueue_chunk expect them to be the same value
430
- u.metadata.seq = 0
431
- synchronize {
432
- @stage[m] = u.staged!
433
- @stage_size_metrics.add(u.bytesize)
434
- }
442
+ begin
443
+ if c.staged? && (enqueue || chunk_size_full?(c))
444
+ m = c.metadata
445
+ enqueue_chunk(m)
446
+ if unstaged_chunks[m] && !unstaged_chunks[m].empty?
447
+ u = unstaged_chunks[m].pop
448
+ u.synchronize do
449
+ if u.unstaged? && !chunk_size_full?(u)
450
+ # `u.metadata.seq` and `m.seq` can be different but Buffer#enqueue_chunk expect them to be the same value
451
+ u.metadata.seq = 0
452
+ synchronize {
453
+ @stage[m] = u.staged!
454
+ @stage_size_metrics.add(u.bytesize)
455
+ }
456
+ end
435
457
  end
436
458
  end
459
+ elsif c.unstaged?
460
+ enqueue_unstaged_chunk(c)
461
+ else
462
+ # previously staged chunk is already enqueued, closed or purged.
463
+ # no problem.
437
464
  end
438
- elsif c.unstaged?
439
- enqueue_unstaged_chunk(c)
440
- else
441
- # previously staged chunk is already enqueued, closed or purged.
442
- # no problem.
465
+ rescue => e
466
+ enqueue_errors << e
443
467
  end
444
468
  end
445
469
 
470
+ enqueue_errors.each do |e|
471
+ log.warn "error occurs in enqueueing a chunk", error: e
472
+ log.warn_backtrace e.backtrace
473
+ end
474
+
446
475
  operated_chunks.clear if errors.empty?
447
476
 
448
477
  if errors.size > 0
@@ -492,6 +521,9 @@ module Fluent
492
521
 
493
522
  chunk.synchronize do
494
523
  synchronize do
524
+ bytesize = chunk.bytesize
525
+ @stage_size_metrics.sub(bytesize)
526
+ @queue_size_metrics.add(bytesize)
495
527
  if chunk.empty?
496
528
  chunk.close
497
529
  else
@@ -500,9 +532,6 @@ module Fluent
500
532
  @queued_num[metadata] = @queued_num.fetch(metadata, 0) + 1
501
533
  chunk.enqueued!
502
534
  end
503
- bytesize = chunk.bytesize
504
- @stage_size_metrics.sub(bytesize)
505
- @queue_size_metrics.add(bytesize)
506
535
  end
507
536
  end
508
537
  nil
@@ -517,9 +546,9 @@ module Fluent
517
546
  metadata.seq = 0 # metadata.seq should be 0 for counting @queued_num
518
547
  @queue << chunk
519
548
  @queued_num[metadata] = @queued_num.fetch(metadata, 0) + 1
549
+ @queue_size_metrics.add(chunk.bytesize)
520
550
  chunk.enqueued!
521
551
  end
522
- @queue_size_metrics.add(chunk.bytesize)
523
552
  end
524
553
  end
525
554
 
@@ -630,12 +659,27 @@ module Fluent
630
659
  def clear_queue!
631
660
  log.on_trace { log.trace "clearing queue", instance: self.object_id }
632
661
 
662
+ evacuate_budget = nil
663
+ skipped_num = 0
664
+ skipped_bytesize = 0
665
+
633
666
  synchronize do
634
667
  until @queue.empty?
635
668
  begin
636
669
  q = @queue.shift
637
- evacuate_chunk(q)
638
- log.trace("purging a chunk in queue"){ {id: dump_unique_id_hex(chunk.unique_id), bytesize: chunk.bytesize, size: chunk.size} }
670
+ if @evacuate_limit_size.nil?
671
+ evacuate_chunk(q)
672
+ elsif @evacuate_limit_size > 0
673
+ evacuate_budget ||= remaining_evacuate_size
674
+ if q.bytesize <= evacuate_budget
675
+ evacuate_chunk(q)
676
+ evacuate_budget -= q.bytesize
677
+ else
678
+ skipped_num += 1
679
+ skipped_bytesize += q.bytesize
680
+ end
681
+ end
682
+ log.trace("purging a chunk in queue"){ {id: dump_unique_id_hex(q.unique_id), bytesize: q.bytesize, size: q.size} }
639
683
  q.purge
640
684
  rescue => e
641
685
  log.error "unexpected error while clearing buffer queue", error_class: e.class, error: e
@@ -644,6 +688,10 @@ module Fluent
644
688
  end
645
689
  @queue_size_metrics.set(0)
646
690
  end
691
+
692
+ if skipped_num > 0
693
+ log.warn "some chunks are purged without being evacuated to stay within evacuate_limit_size.", evacuate_limit_size: @evacuate_limit_size, chunks: skipped_num, bytesize: skipped_bytesize
694
+ end
647
695
  end
648
696
 
649
697
  def evacuate_chunk(chunk)
@@ -992,6 +1040,24 @@ module Fluent
992
1040
  def backup_base_dir
993
1041
  system_config.root_dir || DEFAULT_BACKUP_DIR
994
1042
  end
1043
+
1044
+ def evacuate_dir
1045
+ File.join(backup_base_dir, 'buffer', safe_owner_id)
1046
+ end
1047
+
1048
+ def evacuated_bytesize
1049
+ dir = evacuate_dir
1050
+ Dir.children(dir, encoding: dir.encoding).sum { |name| File.size?(File.join(dir, name)).to_i }
1051
+ rescue Errno::ENOENT
1052
+ 0
1053
+ end
1054
+
1055
+ def remaining_evacuate_size
1056
+ @evacuate_limit_size - evacuated_bytesize
1057
+ rescue => e
1058
+ log.warn "failed to get the size of the evacuated chunk files. queued chunks are purged without being evacuated.", error: e
1059
+ 0
1060
+ end
995
1061
  end
996
1062
  end
997
1063
  end
@@ -44,10 +44,9 @@ module Fluent
44
44
 
45
45
  def format(tag, time, record)
46
46
  json_str = JSON.generate(record)
47
- header = ''
48
- header << "#{@timef.format(time)}#{@delimiter}" if @output_time
49
- header << "#{tag}#{@delimiter}" if @output_tag
50
- "#{header}#{json_str}#{@newline}"
47
+ time_str = @output_time ? "#{@timef.format(time)}#{@delimiter}" : ''
48
+ tag_str = @output_tag ? "#{tag}#{@delimiter}" : ''
49
+ "#{time_str}#{tag_str}#{json_str}#{@newline}"
51
50
  ensure
52
51
  json_str&.clear
53
52
  end
@@ -15,7 +15,7 @@
15
15
  #
16
16
 
17
17
  require 'fluent/plugin/input'
18
- require 'yajl'
18
+ require 'json'
19
19
 
20
20
  module Fluent::Plugin
21
21
  class ExecInput < Fluent::Plugin::Input
@@ -43,6 +43,8 @@ module Fluent::Plugin
43
43
  config_param :tag, :string, default: nil
44
44
  desc 'The interval time between periodic program runs.'
45
45
  config_param :run_interval, :time, default: nil
46
+ desc 'Command (program) execution timeout.'
47
+ config_param :command_timeout, :time, default: nil
46
48
  desc 'The default block size to read if parser requires partial read.'
47
49
  config_param :read_block_size, :size, default: 10240 # 10k
48
50
  desc 'The encoding to receive the result of the command, especially for non-ascii characters.'
@@ -86,9 +88,9 @@ module Fluent::Plugin
86
88
  options[:external_encoding] = @encoding if @encoding
87
89
 
88
90
  if @run_interval
89
- child_process_execute(:exec_input, @command, interval: @run_interval, **options, &method(:run))
91
+ child_process_execute(:exec_input, @command, interval: @run_interval, wait_timeout: @command_timeout, **options, &method(:run))
90
92
  else
91
- child_process_execute(:exec_input, @command, immediate: true, **options, &method(:run))
93
+ child_process_execute(:exec_input, @command, immediate: true, wait_timeout: @command_timeout, **options, &method(:run))
92
94
  end
93
95
  end
94
96
 
@@ -115,7 +117,7 @@ module Fluent::Plugin
115
117
  time ||= extract_time_from_record(record) || Fluent::EventTime.now
116
118
  router.emit(tag, time, record)
117
119
  rescue => e
118
- log.error "exec failed to emit", tag: tag, record: Yajl.dump(record), error: e
120
+ log.error "exec failed to emit", tag: tag, record: JSON.generate(record), error: e
119
121
  router.emit_error_event(tag, time, record, e) if tag && time && record
120
122
  end
121
123
  end
@@ -15,9 +15,10 @@
15
15
  #
16
16
 
17
17
 
18
+ require 'fluent/env'
18
19
  require 'fluent/plugin/input'
19
20
  require 'fluent/msgpack_factory'
20
- require 'yajl'
21
+ require 'json'
21
22
  require 'digest'
22
23
  require 'securerandom'
23
24
 
@@ -31,6 +32,12 @@ module Fluent::Plugin
31
32
  helpers :server
32
33
 
33
34
  LISTEN_PORT = 24224
35
+ OPTION_ACK = 'ack'.freeze
36
+ OPTION_CHUNK = 'chunk'.freeze
37
+ OPTION_COMPRESSED = 'compressed'.freeze
38
+ OPTION_FLUENT_SIGNAL = 'fluent_signal'.freeze
39
+ OPTION_SIZE = 'size'.freeze
40
+ OPTION_TEXT = 'text'.freeze
34
41
 
35
42
  desc 'The port to listen to.'
36
43
  config_param :port, :integer, default: LISTEN_PORT
@@ -250,13 +257,15 @@ module Fluent::Plugin
250
257
  unless feeder
251
258
  first = data[0]
252
259
  if first == '{' || first == '[' # json
253
- parser = Yajl::Parser.new
254
- parser.on_parse_complete = ->(obj){
255
- block.call(obj, bytes, serializer)
256
- bytes = 0
257
- }
260
+ parser = JSON::ResumableParser.new(**Fluent::DEFAULT_JSON_PARSE_OPTIONS)
258
261
  serializer = :to_json.to_proc
259
- feeder = ->(d){ parser << d }
262
+ feeder = ->(d){
263
+ parser << d
264
+ while parser.parse
265
+ block.call(parser.value, bytes, serializer)
266
+ bytes = 0
267
+ end
268
+ }
260
269
  else # msgpack
261
270
  parser = Fluent::MessagePackFactory.msgpack_unpacker
262
271
  serializer = :to_msgpack.to_proc
@@ -275,8 +284,8 @@ module Fluent::Plugin
275
284
  end
276
285
 
277
286
  def response(option)
278
- if option && option['chunk']
279
- return { 'ack' => option['chunk'] }
287
+ if option && option[OPTION_CHUNK]
288
+ return { OPTION_ACK => option[OPTION_CHUNK] }
280
289
  end
281
290
  nil
282
291
  end
@@ -310,14 +319,19 @@ module Fluent::Plugin
310
319
  when String
311
320
  # PackedForward
312
321
  option = msg[2] || {}
313
- size = option['size'] || 0
322
+ size = option[OPTION_SIZE] || 0
323
+ compressed = option[OPTION_COMPRESSED]
314
324
 
315
- if option['compressed'] && option['compressed'] != 'text'
316
- es = Fluent::CompressedMessagePackEventStream.new(entries, nil, size.to_i, compress: option['compressed'].to_sym, decompression_size_limit: @decompression_size_limit)
325
+ if compressed && compressed != OPTION_TEXT
326
+ es = Fluent::CompressedMessagePackEventStream.new(entries, nil, size.to_i, compress: compressed.to_sym, decompression_size_limit: @decompression_size_limit)
317
327
  else
318
328
  es = Fluent::MessagePackEventStream.new(entries, nil, size.to_i)
319
329
  end
320
- es = check_and_skip_invalid_event(tag, es, conn.remote_host) if @skip_invalid_event
330
+ if @skip_invalid_event
331
+ es = normalize_event_stream(tag, es, conn.remote_host)
332
+ elsif option[OPTION_FLUENT_SIGNAL] == 0
333
+ es = Fluent::MetadataTimeEventStream.new(es)
334
+ end
321
335
  if @enable_field_injection
322
336
  es = add_source_info(es, conn)
323
337
  end
@@ -325,19 +339,7 @@ module Fluent::Plugin
325
339
 
326
340
  when Array
327
341
  # Forward
328
- es = if @skip_invalid_event
329
- check_and_skip_invalid_event(tag, entries, conn.remote_host)
330
- else
331
- es = Fluent::MultiEventStream.new
332
- entries.each { |e|
333
- record = e[1]
334
- next if record.nil?
335
- time = e[0]
336
- time = Fluent::EventTime.now if time.nil? || time.to_i == 0 # `to_i == 0` for empty EventTime
337
- es.add(time, record)
338
- }
339
- es
340
- end
342
+ es = normalize_event_stream(tag, entries, conn.remote_host)
341
343
  if @enable_field_injection
342
344
  es = add_source_info(es, conn)
343
345
  end
@@ -346,7 +348,7 @@ module Fluent::Plugin
346
348
 
347
349
  else
348
350
  # Message
349
- time = msg[1]
351
+ time = extract_event_time(msg[1])
350
352
  record = msg[2]
351
353
  if @skip_invalid_event && invalid_event?(tag, time, record)
352
354
  log.warn "got invalid event and drop it:", host: conn.remote_host, tag: tag, time: time, record: record
@@ -367,21 +369,29 @@ module Fluent::Plugin
367
369
  end
368
370
 
369
371
  def invalid_event?(tag, time, record)
372
+ time = extract_event_time(time)
370
373
  !((time.is_a?(Integer) || time.is_a?(::Fluent::EventTime)) && record.is_a?(Hash) && tag.is_a?(String))
371
374
  end
372
375
 
373
- def check_and_skip_invalid_event(tag, es, remote_host)
376
+ def normalize_event_stream(tag, es, remote_host)
374
377
  new_es = Fluent::MultiEventStream.new
375
378
  es.each { |time, record|
376
- if invalid_event?(tag, time, record)
379
+ time = extract_event_time(time)
380
+ if @skip_invalid_event && invalid_event?(tag, time, record)
377
381
  log.warn "skip invalid event:", host: remote_host, tag: tag, time: time, record: record
378
382
  next
379
383
  end
384
+ next if record.nil?
385
+ time = Fluent::EventTime.now if time.nil? || time.to_i == 0 # `to_i == 0` for empty EventTime
380
386
  new_es.add(time, record)
381
387
  }
382
388
  new_es
383
389
  end
384
390
 
391
+ def extract_event_time(time)
392
+ time.is_a?(Array) ? time[0] : time
393
+ end
394
+
385
395
  def add_source_info(es, conn)
386
396
  new_es = Fluent::MultiEventStream.new
387
397
  if @source_address_key && @source_hostname_key
@@ -14,7 +14,7 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
- require 'yajl'
17
+ require 'json'
18
18
 
19
19
  require 'fluent/plugin/input'
20
20
 
@@ -86,7 +86,7 @@ module Fluent::Plugin
86
86
 
87
87
  router.emit(@tag, now, record)
88
88
  rescue => e
89
- log.error "object space failed to emit", error: e, tag: @tag, record: Yajl.dump(record)
89
+ log.error "object space failed to emit", error: e, tag: @tag, record: JSON.generate(record)
90
90
  log.error_backtrace
91
91
  end
92
92
  end
@@ -18,7 +18,7 @@ require 'fluent/plugin/input'
18
18
  require 'fluent/config/error'
19
19
  require 'fluent/plugin/parser'
20
20
 
21
- require 'yajl'
21
+ require 'json'
22
22
 
23
23
  module Fluent::Plugin
24
24
  class SyslogInput < Input
@@ -340,7 +340,7 @@ module Fluent::Plugin
340
340
  def emit(tag, time, record)
341
341
  router.emit(tag, time, record)
342
342
  rescue => e
343
- log.error "syslog failed to emit", error: e, tag: tag, record: Yajl.dump(record)
343
+ log.error "syslog failed to emit", error: e, tag: tag, record: JSON.generate(record)
344
344
  end
345
345
  end
346
346
  end
@@ -0,0 +1,111 @@
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
+
20
+ module Fluent::Plugin
21
+ class TailInput < Fluent::Plugin::Input
22
+ module Compatibility
23
+ # Keep these methods as compatibility entry points for plugins which
24
+ # inherit TailInput and override line processing methods. TailInput is a
25
+ # built-in plugin, not a public inheritance API, and these methods may be
26
+ # removed in a future release.
27
+ def flush_buffer(tw, buf)
28
+ @line_feeder.flush_buffer(tw, buf)
29
+ end
30
+
31
+ # Feeds lines through the per-file feed, or through LineFeeder for watchers
32
+ # created by plugins using the deprecated LineBufferTimerFlusher.
33
+ #
34
+ # @return true if no error or unrecoverable error happens in emit action. false if got BufferOverflowError
35
+ def receive_lines(lines, tail_watcher)
36
+ return async_receive_lines(lines, tail_watcher) if @worker_pool
37
+
38
+ file_feed = tail_watcher.file_feed
39
+ return file_feed.feed_lines(lines, tail_watcher) if file_feed.respond_to?(:feed_lines)
40
+
41
+ @line_feeder.feed_lines(lines, tail_watcher)
42
+ end
43
+
44
+ def convert_line_to_event(line, es, tail_watcher)
45
+ @line_feeder.convert_line_to_event(line, es, tail_watcher)
46
+ end
47
+
48
+ def parse_singleline(lines, tail_watcher)
49
+ @line_feeder.parse_singleline(lines, tail_watcher)
50
+ end
51
+
52
+ # TailInput's default #parse_multilines path starts the flush timer before
53
+ # parsing. An override that skips super must call
54
+ # tail_watcher.line_buffer_timer_flusher.reset_timer when needed.
55
+ def parse_multilines(lines, tail_watcher)
56
+ @line_feeder.parse_multilines(lines, tail_watcher)
57
+ end
58
+ end
59
+
60
+ class TailWatcher
61
+ # The object which keeps the line buffer of the multiline mode of the file:
62
+ # the FileFeed built by LineFeeder#new_file_feed, or the deprecated
63
+ # LineBufferTimerFlusher which a plugin overriding TailInput#setup_watcher
64
+ # passes to keep working without changes.
65
+ def line_buffer_timer_flusher
66
+ @file_feed
67
+ end
68
+
69
+ # Kept for compatibility with the plugins which build it by themselves and
70
+ # pass it to TailWatcher in the overridden TailInput#setup_watcher.
71
+ # LineFeeder#feed_lines feeds the lines of a watcher which keeps it, and
72
+ # LineFeeder::FileFeed built by LineFeeder#new_file_feed replaces it.
73
+ class LineBufferTimerFlusher
74
+ attr_accessor :line_buffer
75
+
76
+ def initialize(log, flush_interval, &flush_method)
77
+ @log = log
78
+ @flush_interval = flush_interval
79
+ @flush_method = flush_method
80
+ @start = nil
81
+ @line_buffer = nil
82
+ end
83
+
84
+ def on_notify(tw)
85
+ unless @start && @flush_method
86
+ return
87
+ end
88
+
89
+ if Fluent::Clock.now - @start >= @flush_interval
90
+ @flush_method.call(tw, @line_buffer) if @line_buffer
91
+ @line_buffer = nil
92
+ @start = nil
93
+ end
94
+ end
95
+
96
+ def close(tw)
97
+ return unless @line_buffer
98
+
99
+ @flush_method.call(tw, @line_buffer)
100
+ @line_buffer = nil
101
+ end
102
+
103
+ def reset_timer
104
+ return unless @flush_interval
105
+
106
+ @start = Fluent::Clock.now
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end