fluentd 1.6.3 → 1.7.0.rc1

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.

Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.drone.yml +35 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +2 -0
  4. data/README.md +5 -1
  5. data/fluentd.gemspec +1 -1
  6. data/lib/fluent/clock.rb +4 -0
  7. data/lib/fluent/compat/output.rb +3 -3
  8. data/lib/fluent/compat/socket_util.rb +1 -1
  9. data/lib/fluent/config/element.rb +3 -3
  10. data/lib/fluent/config/literal_parser.rb +1 -1
  11. data/lib/fluent/config/section.rb +4 -1
  12. data/lib/fluent/error.rb +4 -0
  13. data/lib/fluent/event.rb +28 -24
  14. data/lib/fluent/event_router.rb +2 -1
  15. data/lib/fluent/log.rb +1 -1
  16. data/lib/fluent/msgpack_factory.rb +8 -0
  17. data/lib/fluent/plugin/bare_output.rb +4 -4
  18. data/lib/fluent/plugin/buf_file_single.rb +211 -0
  19. data/lib/fluent/plugin/buffer.rb +62 -63
  20. data/lib/fluent/plugin/buffer/chunk.rb +21 -3
  21. data/lib/fluent/plugin/buffer/file_chunk.rb +37 -12
  22. data/lib/fluent/plugin/buffer/file_single_chunk.rb +314 -0
  23. data/lib/fluent/plugin/buffer/memory_chunk.rb +2 -1
  24. data/lib/fluent/plugin/compressable.rb +10 -6
  25. data/lib/fluent/plugin/filter_grep.rb +2 -2
  26. data/lib/fluent/plugin/formatter_csv.rb +10 -6
  27. data/lib/fluent/plugin/in_syslog.rb +10 -3
  28. data/lib/fluent/plugin/in_tail.rb +7 -2
  29. data/lib/fluent/plugin/in_tcp.rb +34 -7
  30. data/lib/fluent/plugin/multi_output.rb +4 -4
  31. data/lib/fluent/plugin/out_exec_filter.rb +1 -0
  32. data/lib/fluent/plugin/out_file.rb +13 -3
  33. data/lib/fluent/plugin/out_forward.rb +126 -588
  34. data/lib/fluent/plugin/out_forward/ack_handler.rb +161 -0
  35. data/lib/fluent/plugin/out_forward/connection_manager.rb +113 -0
  36. data/lib/fluent/plugin/out_forward/error.rb +28 -0
  37. data/lib/fluent/plugin/out_forward/failure_detector.rb +84 -0
  38. data/lib/fluent/plugin/out_forward/handshake_protocol.rb +121 -0
  39. data/lib/fluent/plugin/out_forward/load_balancer.rb +111 -0
  40. data/lib/fluent/plugin/out_forward/socket_cache.rb +138 -0
  41. data/lib/fluent/plugin/out_http.rb +231 -0
  42. data/lib/fluent/plugin/output.rb +29 -35
  43. data/lib/fluent/plugin/parser.rb +77 -0
  44. data/lib/fluent/plugin/parser_csv.rb +75 -0
  45. data/lib/fluent/plugin_helper/server.rb +1 -1
  46. data/lib/fluent/plugin_helper/thread.rb +1 -0
  47. data/lib/fluent/root_agent.rb +1 -1
  48. data/lib/fluent/time.rb +4 -2
  49. data/lib/fluent/timezone.rb +21 -7
  50. data/lib/fluent/version.rb +1 -1
  51. data/test/command/test_fluentd.rb +1 -1
  52. data/test/command/test_plugin_generator.rb +18 -2
  53. data/test/config/test_configurable.rb +78 -40
  54. data/test/counter/test_store.rb +1 -1
  55. data/test/helper.rb +1 -0
  56. data/test/helpers/process_extenstion.rb +33 -0
  57. data/test/plugin/out_forward/test_ack_handler.rb +101 -0
  58. data/test/plugin/out_forward/test_connection_manager.rb +145 -0
  59. data/test/plugin/out_forward/test_handshake_protocol.rb +103 -0
  60. data/test/plugin/out_forward/test_load_balancer.rb +60 -0
  61. data/test/plugin/out_forward/test_socket_cache.rb +139 -0
  62. data/test/plugin/test_buf_file.rb +118 -2
  63. data/test/plugin/test_buf_file_single.rb +734 -0
  64. data/test/plugin/test_buffer.rb +4 -48
  65. data/test/plugin/test_buffer_file_chunk.rb +19 -1
  66. data/test/plugin/test_buffer_file_single_chunk.rb +620 -0
  67. data/test/plugin/test_formatter_csv.rb +16 -0
  68. data/test/plugin/test_in_syslog.rb +56 -6
  69. data/test/plugin/test_in_tail.rb +1 -1
  70. data/test/plugin/test_in_tcp.rb +25 -0
  71. data/test/plugin/test_out_forward.rb +75 -201
  72. data/test/plugin/test_out_http.rb +352 -0
  73. data/test/plugin/test_output_as_buffered.rb +27 -24
  74. data/test/plugin/test_parser.rb +40 -0
  75. data/test/plugin/test_parser_csv.rb +83 -0
  76. data/test/plugin_helper/test_record_accessor.rb +1 -1
  77. data/test/test_time_formatter.rb +140 -121
  78. metadata +35 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf4c49c147ccaa5c7ad0c4585a4e2a0e156b525ff5eb0a4a163df1c20c1c8e1d
4
- data.tar.gz: 813578f6a63b400fadad9fead1b0edd323be8f92e31b1522133a886a28c9978b
3
+ metadata.gz: f5ce0f9135cf13b32692d41e65dbb869738702a0d328ee1decec0b8848000feb
4
+ data.tar.gz: c803e8f6ece1fa166c1a988867f04c660f0f7dd821c5d7f09dbd1a96d478b9c7
5
5
  SHA512:
6
- metadata.gz: fbe1fbbe488245d3bda830a5ec3a0419e347ce7d6a685d5fd5f1d933c71382792cb047a45ffdbe982a763cdb336c1589958e9f84536c67452d1d608f9fea0db3
7
- data.tar.gz: d9d2370bf3ce08c0500aa99f83c16af9ef3ebb97b15e8992bf88a0b76834b1eaad82a1bab27d3f39b07be90cf20fdf8646d31ee401bdf98a6709b65496417c5d
6
+ metadata.gz: 18909799465547cfe11c00f701e5aad6845a346be63a29eddf872d56d3ae680ad967e852af5f468a451d1decb051cffce5d8d5e68d8ab3c97307137ccf22a9fa
7
+ data.tar.gz: 6fbed649b9f164f3f699667e57dd2706e98f11c69b2c31210767f520a4f62310e34a14ac12c5ddde5244f5232c83f6b7ad83dc925a096054ec71abd493ceeecd
@@ -0,0 +1,35 @@
1
+ kind: pipeline
2
+ name: fluentd-test-arm64-2-6-3
3
+
4
+ platform:
5
+ os: linux
6
+ arch: arm64
7
+
8
+ steps:
9
+ - name: fluentd-test-arm64-2-6-3
10
+ image: arm64v8/ruby:2.6.3
11
+ commands:
12
+ - apt update
13
+ - apt -y install libgmp3-dev
14
+ - export BUNDLE_GEMFILE=$PWD/Gemfile
15
+ - gem update --system
16
+ - bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}
17
+ - bundle exec rake test
18
+ ---
19
+ kind: pipeline
20
+ name: fluentd-test-arm64-latest
21
+
22
+ platform:
23
+ os: linux
24
+ arch: arm64
25
+
26
+ steps:
27
+ - name: fluentd-test-arm64-latest
28
+ image: arm64v8/ruby:latest
29
+ commands:
30
+ - apt update
31
+ - apt -y install libgmp3-dev
32
+ - export BUNDLE_GEMFILE=$PWD/Gemfile
33
+ - gem update --system
34
+ - bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}
35
+ - bundle exec rake test
@@ -22,6 +22,8 @@ Check [CONTRIBUTING guideline](https://github.com/fluent/fluentd/blob/master/CON
22
22
  - Operating system: `cat /etc/os-release`
23
23
  - Kernel version: `uname -r`
24
24
 
25
+ If you hit the problem with older fluentd version, try latest version first.
26
+
25
27
  **Your Configuration**
26
28
 
27
29
  ```
data/README.md CHANGED
@@ -10,6 +10,10 @@ GitLab CI:
10
10
 
11
11
  [![pipeline status](https://gitlab.com/fluent/fluentd/badges/master/pipeline.svg)](https://gitlab.com/fluent/fluentd/commits/master)
12
12
 
13
+ Drone CI for Arm64:
14
+
15
+ [![pipeline status](https://cloud.drone.io/api/badges/fluent/fluentd/status.svg?branch=master)](https://cloud.drone.io/fluent/fluentd)
16
+
13
17
  [Fluentd](https://www.fluentd.org/) collects events from various data sources and writes them to files, RDBMS, NoSQL, IaaS, SaaS, Hadoop and so on. Fluentd helps you unify your logging infrastructure (Learn more about the [Unified Logging Layer](https://www.fluentd.org/blog/unified-logging-layer)).
14
18
 
15
19
  <p align="center">
@@ -78,7 +82,7 @@ You can run specified test via `TEST` environment variable:
78
82
  - Slack / Community: https://slack.fluentd.org
79
83
  - Newsletters: https://www.fluentd.org/newsletter
80
84
  - Author: [Sadayuki Furuhashi](https://github.com/frsyuki)
81
- - Copyright: 2011-2018 Fluentd Authors
85
+ - Copyright: 2011-2019 Fluentd Authors
82
86
  - License: Apache License, Version 2.0
83
87
 
84
88
  ## Contributors:
@@ -24,7 +24,7 @@ Gem::Specification.new do |gem|
24
24
  gem.add_runtime_dependency("serverengine", [">= 2.0.4", "< 3.0.0"])
25
25
  gem.add_runtime_dependency("http_parser.rb", [">= 0.5.1", "< 0.7.0"])
26
26
  gem.add_runtime_dependency("sigdump", ["~> 0.2.2"])
27
- gem.add_runtime_dependency("tzinfo", ["~> 1.0"])
27
+ gem.add_runtime_dependency("tzinfo", ["~> 2.0"])
28
28
  gem.add_runtime_dependency("tzinfo-data", ["~> 1.0"])
29
29
  gem.add_runtime_dependency("strptime", [">= 0.2.2", "< 1.0.0"])
30
30
  gem.add_runtime_dependency("dig_rb", ["~> 1.0.0"])
@@ -43,6 +43,10 @@ module Fluent
43
43
  Process.clock_gettime(CLOCK_ID)
44
44
  end
45
45
 
46
+ def self.real_now(unit = :second)
47
+ Process.clock_gettime(Process::CLOCK_REALTIME, unit)
48
+ end
49
+
46
50
  def self.dst_clock_from_time(time)
47
51
  diff_sec = Time.now - time
48
52
  now_raw - diff_sec
@@ -352,7 +352,7 @@ module Fluent
352
352
  write_guard do
353
353
  @buffer.write({meta => data}, format: ->(_data){ _data }, size: ->(){ size }, enqueue: enqueue)
354
354
  end
355
- @counters_monitor.synchronize{ @emit_records += size }
355
+ @counter_mutex.synchronize{ @emit_records += size }
356
356
  return [meta]
357
357
  end
358
358
 
@@ -363,7 +363,7 @@ module Fluent
363
363
  write_guard do
364
364
  @buffer.write({meta => bulk}, format: ->(_data){ _data }, size: ->(){ size }, enqueue: enqueue)
365
365
  end
366
- @counters_monitor.synchronize{ @emit_records += size }
366
+ @counter_mutex.synchronize{ @emit_records += size }
367
367
  return [meta]
368
368
  end
369
369
 
@@ -373,7 +373,7 @@ module Fluent
373
373
  write_guard do
374
374
  @buffer.write({meta => data}, enqueue: enqueue)
375
375
  end
376
- @counters_monitor.synchronize{ @emit_records += size }
376
+ @counter_mutex.synchronize{ @emit_records += size }
377
377
  [meta]
378
378
  end
379
379
 
@@ -52,7 +52,7 @@ module Fluent
52
52
  end
53
53
 
54
54
  class TcpHandler < Coolio::Socket
55
- PEERADDR_FAILED = ["?", "?", "name resolusion failed", "?"]
55
+ PEERADDR_FAILED = ["?", "?", "name resolution failed", "?"]
56
56
 
57
57
  def initialize(io, log, delimiter, callback)
58
58
  super(io)
@@ -31,7 +31,7 @@ module Fluent
31
31
  @unused = unused || attrs.keys
32
32
  @v1_config = false
33
33
  @corresponding_proxies = [] # some plugins use flat parameters, e.g. in_http doesn't provide <format> section for parser.
34
- @unused_in = false # if this element is not used in plugins, correspoing plugin name and parent element name is set, e.g. [source, plugin class].
34
+ @unused_in = nil # if this element is not used in plugins, correspoing plugin name and parent element name is set, e.g. [source, plugin class].
35
35
 
36
36
  # it's global logger, not plugin logger: deprecated message should be global warning, not plugin level.
37
37
  @logger = defined?($log) ? $log : nil
@@ -110,13 +110,13 @@ module Fluent
110
110
  end
111
111
 
112
112
  def has_key?(key)
113
- @unused_in = false # some sections, e.g. <store> in copy, is not defined by config_section so clear unused flag for better warning message in check_not_fetched.
113
+ @unused_in = [] # some sections, e.g. <store> in copy, is not defined by config_section so clear unused flag for better warning message in check_not_fetched.
114
114
  @unused.delete(key)
115
115
  super
116
116
  end
117
117
 
118
118
  def [](key)
119
- @unused_in = false # ditto
119
+ @unused_in = [] # ditto
120
120
  @unused.delete(key)
121
121
 
122
122
  if RESERVED_PARAMETERS.include?(key) && !has_key?(key) && has_key?(RESERVED_PARAMETERS_COMPAT[key])
@@ -112,7 +112,7 @@ module Fluent
112
112
  elsif s = scan(/./)
113
113
  string << s
114
114
  else
115
- parse_error! "unexpected end of file in a signle quoted string"
115
+ parse_error! "unexpected end of file in a single quoted string"
116
116
  end
117
117
  end
118
118
  end
@@ -207,8 +207,11 @@ module Fluent
207
207
  elems = conf.respond_to?(:elements) ? conf.elements : []
208
208
  elems.each { |e|
209
209
  next if plugin_class.nil? && Fluent::Config::V1Parser::ELEM_SYMBOLS.include?(e.name) # skip pre-defined non-plugin elements because it doens't have proxy section
210
+ next if e.unused_in && e.unused_in.empty? # the section is used at least once
210
211
 
211
- unless proxy.sections.any? { |name, subproxy| e.name == subproxy.name.to_s || e.name == subproxy.alias.to_s }
212
+ if proxy.sections.any? { |name, subproxy| e.name == subproxy.name.to_s || e.name == subproxy.alias.to_s }
213
+ e.unused_in = []
214
+ else
212
215
  parent_name = if conf.arg.empty?
213
216
  conf.name
214
217
  else
@@ -27,4 +27,8 @@ module Fluent
27
27
 
28
28
  class InvalidRootDirectory < UnrecoverableError
29
29
  end
30
+
31
+ # For internal use
32
+ class UncatchableError < Exception
33
+ end
30
34
  end
@@ -50,30 +50,30 @@ module Fluent
50
50
  raise NotImplementedError, "DO NOT USE THIS CLASS directly."
51
51
  end
52
52
 
53
- def each(&block)
53
+ def each(unapcker: nil, &block)
54
54
  raise NotImplementedError, "DO NOT USE THIS CLASS directly."
55
55
  end
56
56
 
57
- def to_msgpack_stream(time_int: false)
58
- return to_msgpack_stream_forced_integer if time_int
59
- out = msgpack_packer
57
+ def to_msgpack_stream(time_int: false, packer: nil)
58
+ return to_msgpack_stream_forced_integer(packer: packer) if time_int
59
+ out = packer || msgpack_packer
60
60
  each {|time,record|
61
61
  out.write([time,record])
62
62
  }
63
- out.to_s
63
+ out.full_pack
64
64
  end
65
65
 
66
- def to_compressed_msgpack_stream(time_int: false)
67
- packed = to_msgpack_stream(time_int: time_int)
66
+ def to_compressed_msgpack_stream(time_int: false, packer: nil)
67
+ packed = to_msgpack_stream(time_int: time_int, packer: packer)
68
68
  compress(packed)
69
69
  end
70
70
 
71
- def to_msgpack_stream_forced_integer
72
- out = msgpack_packer
71
+ def to_msgpack_stream_forced_integer(packer: nil)
72
+ out = packer || msgpack_packer
73
73
  each {|time,record|
74
74
  out.write([time.to_i,record])
75
75
  }
76
- out.to_s
76
+ out.full_pack
77
77
  end
78
78
  end
79
79
 
@@ -87,6 +87,10 @@ module Fluent
87
87
  OneEventStream.new(@time, @record.dup)
88
88
  end
89
89
 
90
+ def empty?
91
+ false
92
+ end
93
+
90
94
  def size
91
95
  1
92
96
  end
@@ -103,7 +107,7 @@ module Fluent
103
107
  end
104
108
  end
105
109
 
106
- def each(&block)
110
+ def each(unpacker: nil, &block)
107
111
  block.call(@time, @record)
108
112
  nil
109
113
  end
@@ -139,7 +143,7 @@ module Fluent
139
143
  ArrayEventStream.new(@entries.slice(index, num))
140
144
  end
141
145
 
142
- def each(&block)
146
+ def each(unpacker: nil, &block)
143
147
  @entries.each(&block)
144
148
  nil
145
149
  end
@@ -186,7 +190,7 @@ module Fluent
186
190
  MultiEventStream.new(@time_array.slice(index, num), @record_array.slice(index, num))
187
191
  end
188
192
 
189
- def each(&block)
193
+ def each(unpacker: nil, &block)
190
194
  time_array = @time_array
191
195
  record_array = @record_array
192
196
  for i in 0..time_array.length-1
@@ -230,11 +234,11 @@ module Fluent
230
234
  true
231
235
  end
232
236
 
233
- def ensure_unpacked!
237
+ def ensure_unpacked!(unpacker: nil)
234
238
  return if @unpacked_times && @unpacked_records
235
239
  @unpacked_times = []
236
240
  @unpacked_records = []
237
- msgpack_unpacker.feed_each(@data) do |time, record|
241
+ (unpacker || msgpack_unpacker).feed_each(@data) do |time, record|
238
242
  @unpacked_times << time
239
243
  @unpacked_records << record
240
244
  end
@@ -250,7 +254,7 @@ module Fluent
250
254
  MultiEventStream.new(@unpacked_times.slice(index, num), @unpacked_records.slice(index, num))
251
255
  end
252
256
 
253
- def each(&block)
257
+ def each(unpacker: nil, &block)
254
258
  if @unpacked_times
255
259
  @unpacked_times.each_with_index do |time, i|
256
260
  block.call(time, @unpacked_records[i])
@@ -258,7 +262,7 @@ module Fluent
258
262
  else
259
263
  @unpacked_times = []
260
264
  @unpacked_records = []
261
- msgpack_unpacker.feed_each(@data) do |time, record|
265
+ (unpacker || msgpack_unpacker).feed_each(@data) do |time, record|
262
266
  @unpacked_times << time
263
267
  @unpacked_records << record
264
268
  block.call(time, record)
@@ -268,7 +272,7 @@ module Fluent
268
272
  nil
269
273
  end
270
274
 
271
- def to_msgpack_stream(time_int: false)
275
+ def to_msgpack_stream(time_int: false, packer: nil)
272
276
  # time_int is always ignored because @data is always packed binary in this class
273
277
  @data
274
278
  end
@@ -286,17 +290,17 @@ module Fluent
286
290
  super
287
291
  end
288
292
 
289
- def ensure_unpacked!
293
+ def ensure_unpacked!(unpacker: nil)
290
294
  ensure_decompressed!
291
295
  super
292
296
  end
293
297
 
294
- def each(&block)
298
+ def each(unpacker: nil, &block)
295
299
  ensure_decompressed!
296
300
  super
297
301
  end
298
302
 
299
- def to_msgpack_stream(time_int: false)
303
+ def to_msgpack_stream(time_int: false, packer: nil)
300
304
  ensure_decompressed!
301
305
  super
302
306
  end
@@ -318,15 +322,15 @@ module Fluent
318
322
  include MessagePackFactory::Mixin
319
323
  # chunk.extend(ChunkEventStreamer)
320
324
  # => chunk.each{|time, record| ... }
321
- def each(&block)
325
+ def each(unpacker: nil, &block)
322
326
  open do |io|
323
- msgpack_unpacker(io).each(&block)
327
+ (unpacker || msgpack_unpacker(io)).each(&block)
324
328
  end
325
329
  nil
326
330
  end
327
331
  alias :msgpack_each :each
328
332
 
329
- def to_msgpack_stream(time_int: false)
333
+ def to_msgpack_stream(time_int: false, packer: nil)
330
334
  # time_int is always ignored because data is already packed and written in chunk
331
335
  read
332
336
  end
@@ -17,6 +17,7 @@
17
17
  require 'fluent/match'
18
18
  require 'fluent/event'
19
19
  require 'fluent/filter'
20
+ require 'fluent/msgpack_factory'
20
21
 
21
22
  module Fluent
22
23
  #
@@ -182,7 +183,7 @@ module Fluent
182
183
 
183
184
  def optimized_filter_stream(tag, es)
184
185
  new_es = MultiEventStream.new
185
- es.each do |time, record|
186
+ es.each(unpacker: Fluent::MessagePackFactory.thread_local_msgpack_unpacker) do |time, record|
186
187
  filtered_record = record
187
188
  filtered_time = time
188
189
 
@@ -606,7 +606,7 @@ module Fluent
606
606
  end
607
607
  end
608
608
 
609
- # This class delegetes some methods which are used in `Fluent::Logger` to a instance variable(`dev`) in `Logger::LogDevice` class
609
+ # This class delegates some methods which are used in `Fluent::Logger` to a instance variable(`dev`) in `Logger::LogDevice` class
610
610
  # https://github.com/ruby/ruby/blob/7b2d47132ff8ee950b0f978ab772dee868d9f1b0/lib/logger.rb#L661
611
611
  class LogDeviceIO < ::Logger::LogDevice
612
612
  def flush
@@ -58,5 +58,13 @@ module Fluent
58
58
  factory.register_type(Fluent::EventTime::TYPE, Fluent::EventTime)
59
59
  @@engine_factory = factory
60
60
  end
61
+
62
+ def self.thread_local_msgpack_packer
63
+ Thread.current[:local_msgpack_packer] ||= MessagePackFactory.engine_factory.packer
64
+ end
65
+
66
+ def self.thread_local_msgpack_unpacker
67
+ Thread.current[:local_msgpack_unpacker] ||= MessagePackFactory.engine_factory.unpacker
68
+ end
61
69
  end
62
70
  end
@@ -40,7 +40,7 @@ module Fluent
40
40
 
41
41
  def initialize
42
42
  super
43
- @counters_monitor = Monitor.new
43
+ @counter_mutex = Mutex.new
44
44
  # TODO: well organized counters
45
45
  @num_errors = 0
46
46
  @emit_count = 0
@@ -48,12 +48,12 @@ module Fluent
48
48
  end
49
49
 
50
50
  def emit_sync(tag, es)
51
- @counters_monitor.synchronize{ @emit_count += 1 }
51
+ @counter_mutex.synchronize{ @emit_count += 1 }
52
52
  begin
53
53
  process(tag, es)
54
- @counters_monitor.synchronize{ @emit_records += es.size }
54
+ @counter_mutex.synchronize{ @emit_records += es.size }
55
55
  rescue
56
- @counters_monitor.synchronize{ @num_errors += 1 }
56
+ @counter_mutex.synchronize{ @num_errors += 1 }
57
57
  raise
58
58
  end
59
59
  end
@@ -0,0 +1,211 @@
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 'fileutils'
18
+
19
+ require 'fluent/plugin/buffer'
20
+ require 'fluent/plugin/buffer/file_single_chunk'
21
+ require 'fluent/system_config'
22
+
23
+ module Fluent
24
+ module Plugin
25
+ class FileSingleBuffer < Fluent::Plugin::Buffer
26
+ Plugin.register_buffer('file_single', self)
27
+
28
+ include SystemConfig::Mixin
29
+
30
+ DEFAULT_CHUNK_LIMIT_SIZE = 256 * 1024 * 1024 # 256MB
31
+ DEFAULT_TOTAL_LIMIT_SIZE = 64 * 1024 * 1024 * 1024 # 64GB
32
+
33
+ PATH_SUFFIX = ".#{Fluent::Plugin::Buffer::FileSingleChunk::PATH_EXT}"
34
+ DIR_PERMISSION = 0755
35
+
36
+ desc 'The path where buffer chunks are stored.'
37
+ config_param :path, :string, default: nil
38
+ desc 'Calculate the number of record in chunk during resume'
39
+ config_param :calc_num_records, :bool, default: true
40
+ desc 'The format of chunk. This is used to calculate the number of record'
41
+ config_param :chunk_format, :enum, list: [:msgpack, :text, :auto], default: :auto
42
+
43
+ config_set_default :chunk_limit_size, DEFAULT_CHUNK_LIMIT_SIZE
44
+ config_set_default :total_limit_size, DEFAULT_TOTAL_LIMIT_SIZE
45
+
46
+ desc 'The permission of chunk file. If no specified, <system> setting or 0644 is used'
47
+ config_param :file_permission, :string, default: nil
48
+ desc 'The permission of chunk directory. If no specified, <system> setting or 0755 is used'
49
+ config_param :dir_permission, :string, default: nil
50
+
51
+ @@buffer_paths = {}
52
+
53
+ def initialize
54
+ super
55
+
56
+ @multi_workers_available = false
57
+ @additional_resume_path = nil
58
+ end
59
+
60
+ def configure(conf)
61
+ super
62
+
63
+ if @chunk_format == :auto
64
+ @chunk_format = owner.formatted_to_msgpack_binary? ? :msgpack : :text
65
+ end
66
+
67
+ @key_in_path = nil
68
+ if owner.chunk_keys.empty?
69
+ log.debug "use event tag for buffer key"
70
+ else
71
+ if owner.chunk_key_tag
72
+ raise Fluent::ConfigError, "chunk keys must be tag or one field"
73
+ elsif owner.chunk_keys.size > 1
74
+ raise Fluent::ConfigError, "2 or more chunk keys is not allowed"
75
+ else
76
+ @key_in_path = owner.chunk_keys.first.to_sym
77
+ end
78
+ end
79
+
80
+ multi_workers_configured = owner.system_config.workers > 1
81
+ using_plugin_root_dir = false
82
+ unless @path
83
+ if root_dir = owner.plugin_root_dir
84
+ @path = File.join(root_dir, 'buffer')
85
+ using_plugin_root_dir = true # plugin_root_dir path contains worker id
86
+ else
87
+ raise Fluent::ConfigError, "buffer path is not configured. specify 'path' in <buffer>"
88
+ end
89
+ end
90
+
91
+ type_of_owner = Plugin.lookup_type_from_class(@_owner.class)
92
+ if @@buffer_paths.has_key?(@path) && !called_in_test?
93
+ type_using_this_path = @@buffer_paths[@path]
94
+ raise Fluent::ConfigError, "Other '#{type_using_this_path}' plugin already uses same buffer path: type = #{type_of_owner}, buffer path = #{@path}"
95
+ end
96
+
97
+ @@buffer_paths[@path] = type_of_owner
98
+
99
+ specified_directory_exists = File.exist?(@path) && File.directory?(@path)
100
+ unexisting_path_for_directory = !File.exist?(@path) && !@path.include?('.*')
101
+
102
+ if specified_directory_exists || unexisting_path_for_directory # directory
103
+ if using_plugin_root_dir || !multi_workers_configured
104
+ @path = File.join(@path, "fsb.*#{PATH_SUFFIX}")
105
+ else
106
+ @path = File.join(@path, "worker#{fluentd_worker_id}", "fsb.*#{PATH_SUFFIX}")
107
+ if fluentd_worker_id == 0
108
+ # worker 0 always checks unflushed buffer chunks to be resumed (might be created while non-multi-worker configuration)
109
+ @additional_resume_path = File.join(File.expand_path("../../", @path), "fsb.*#{PATH_SUFFIX}")
110
+ end
111
+ end
112
+ @multi_workers_available = true
113
+ else # specified path is file path
114
+ if File.basename(@path).include?('.*.')
115
+ new_path = File.join(File.dirname(@path), "fsb.*#{PATH_SUFFIX}")
116
+ log.warn "file_single doesn't allow user specified 'prefix.*.suffix' style path. Use '#{new_path}' for file instead: #{@path}"
117
+ @path = new_path
118
+ elsif File.basename(@path).end_with?('.*')
119
+ @path = @path + PATH_SUFFIX
120
+ else
121
+ # existing file will be ignored
122
+ @path = @path + ".*#{PATH_SUFFIX}"
123
+ end
124
+ @multi_workers_available = false
125
+ end
126
+
127
+ @dir_permission = if @dir_permission
128
+ @dir_permission.to_i(8)
129
+ else
130
+ system_config.dir_permission || DIR_PERMISSION
131
+ end
132
+ end
133
+
134
+ # This method is called only when multi worker is configured
135
+ def multi_workers_ready?
136
+ unless @multi_workers_available
137
+ log.error "file_single buffer with multi workers should be configured to use directory 'path', or system root_dir and plugin id"
138
+ end
139
+ @multi_workers_available
140
+ end
141
+
142
+ def start
143
+ FileUtils.mkdir_p(File.dirname(@path), mode: @dir_permission)
144
+
145
+ super
146
+ end
147
+
148
+ def persistent?
149
+ true
150
+ end
151
+
152
+ def resume
153
+ stage = {}
154
+ queue = []
155
+
156
+ patterns = [@path]
157
+ patterns.unshift @additional_resume_path if @additional_resume_path
158
+ Dir.glob(patterns) do |path|
159
+ next unless File.file?(path)
160
+
161
+ log.debug { "restoring buffer file: path = #{path}" }
162
+
163
+ m = new_metadata() # this metadata will be updated in FileSingleChunk.new
164
+ mode = Fluent::Plugin::Buffer::FileSingleChunk.assume_chunk_state(path)
165
+ if mode == :unknown
166
+ log.debug "unknown state chunk found", path: path
167
+ next
168
+ end
169
+
170
+ begin
171
+ chunk = Fluent::Plugin::Buffer::FileSingleChunk.new(m, path, mode, @key_in_path)
172
+ chunk.restore_size(@chunk_format) if @calc_num_records
173
+ rescue Fluent::Plugin::Buffer::FileSingleChunk::FileChunkError => e
174
+ handle_broken_files(path, mode, e)
175
+ next
176
+ end
177
+
178
+ case chunk.state
179
+ when :staged
180
+ stage[chunk.metadata] = chunk
181
+ when :queued
182
+ queue << chunk
183
+ end
184
+ end
185
+
186
+ queue.sort_by!(&:modified_at)
187
+
188
+ return stage, queue
189
+ end
190
+
191
+ def generate_chunk(metadata)
192
+ # FileChunk generates real path with unique_id
193
+ if @file_permission
194
+ chunk = Fluent::Plugin::Buffer::FileSingleChunk.new(metadata, @path, :create, @key_in_path, perm: @file_permission, compress: @compress)
195
+ else
196
+ chunk = Fluent::Plugin::Buffer::FileSingleChunk.new(metadata, @path, :create, @key_in_path, compress: @compress)
197
+ end
198
+
199
+ log.debug "Created new chunk", chunk_id: dump_unique_id_hex(chunk.unique_id), metadata: metadata
200
+
201
+ chunk
202
+ end
203
+
204
+ def handle_broken_files(path, mode, e)
205
+ log.error "found broken chunk file during resume. Delete corresponding files:", path: path, mode: mode, err_msg: e.message
206
+ # After support 'backup_dir' feature, these files are moved to backup_dir instead of unlink.
207
+ File.unlink(path) rescue nil
208
+ end
209
+ end
210
+ end
211
+ end