fluentd 1.16.5-x64-mingw32 → 1.17.0-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/DISCUSSION_TEMPLATE/q-a-japanese.yml +50 -0
  3. data/.github/DISCUSSION_TEMPLATE/q-a.yml +47 -0
  4. data/.github/workflows/test-ruby-head.yml +31 -0
  5. data/.github/workflows/test.yml +3 -3
  6. data/CHANGELOG.md +42 -0
  7. data/README.md +1 -1
  8. data/Rakefile +1 -1
  9. data/fluentd.gemspec +9 -1
  10. data/lib/fluent/command/binlog_reader.rb +1 -1
  11. data/lib/fluent/config/configure_proxy.rb +2 -2
  12. data/lib/fluent/config/types.rb +1 -1
  13. data/lib/fluent/configurable.rb +2 -2
  14. data/lib/fluent/counter/mutex_hash.rb +1 -1
  15. data/lib/fluent/fluent_log_event_router.rb +0 -2
  16. data/lib/fluent/plugin/buf_file.rb +1 -1
  17. data/lib/fluent/plugin/buffer/file_chunk.rb +1 -1
  18. data/lib/fluent/plugin/buffer/file_single_chunk.rb +2 -3
  19. data/lib/fluent/plugin/filter_parser.rb +26 -8
  20. data/lib/fluent/plugin/in_http.rb +18 -53
  21. data/lib/fluent/plugin/in_tail.rb +34 -2
  22. data/lib/fluent/plugin/out_http.rb +125 -13
  23. data/lib/fluent/plugin/owned_by_mixin.rb +0 -1
  24. data/lib/fluent/plugin/parser_json.rb +22 -5
  25. data/lib/fluent/plugin/parser_msgpack.rb +24 -3
  26. data/lib/fluent/plugin_helper/metrics.rb +2 -2
  27. data/lib/fluent/registry.rb +6 -6
  28. data/lib/fluent/test/output_test.rb +1 -1
  29. data/lib/fluent/unique_id.rb +1 -1
  30. data/lib/fluent/version.rb +1 -1
  31. data/test/log/test_console_adapter.rb +10 -3
  32. data/test/plugin/data/log_numeric/01.log +0 -0
  33. data/test/plugin/data/log_numeric/02.log +0 -0
  34. data/test/plugin/data/log_numeric/12.log +0 -0
  35. data/test/plugin/data/log_numeric/14.log +0 -0
  36. data/test/plugin/test_in_http.rb +23 -1
  37. data/test/plugin/test_in_tail.rb +141 -0
  38. data/test/plugin/test_out_http.rb +128 -0
  39. data/test/plugin/test_owned_by.rb +0 -1
  40. data/test/plugin/test_parser_json.rb +106 -0
  41. data/test/plugin/test_parser_msgpack.rb +127 -0
  42. data/test/plugin/test_storage.rb +0 -1
  43. data/test/plugin_helper/test_child_process.rb +4 -4
  44. metadata +101 -4
@@ -37,6 +37,8 @@ module Fluent::Plugin
37
37
 
38
38
  class RetryableResponse < StandardError; end
39
39
 
40
+ ConnectionCache = Struct.new(:uri, :conn)
41
+
40
42
  helpers :formatter
41
43
 
42
44
  desc 'The endpoint for HTTP request, e.g. http://example.com/api'
@@ -60,6 +62,8 @@ module Fluent::Plugin
60
62
  config_param :read_timeout, :integer, default: nil
61
63
  desc 'The TLS timeout in seconds'
62
64
  config_param :ssl_timeout, :integer, default: nil
65
+ desc 'Try to reuse connections'
66
+ config_param :reuse_connections, :bool, default: false
63
67
 
64
68
  desc 'The CA certificate path for TLS'
65
69
  config_param :tls_ca_cert_path, :string, default: nil
@@ -87,11 +91,29 @@ module Fluent::Plugin
87
91
 
88
92
  config_section :auth, required: false, multi: false do
89
93
  desc 'The method for HTTP authentication'
90
- config_param :method, :enum, list: [:basic], default: :basic
94
+ config_param :method, :enum, list: [:basic, :aws_sigv4], default: :basic
91
95
  desc 'The username for basic authentication'
92
96
  config_param :username, :string, default: nil
93
97
  desc 'The password for basic authentication'
94
98
  config_param :password, :string, default: nil, secret: true
99
+ desc 'The AWS service to authenticate against'
100
+ config_param :aws_service, :string, default: nil
101
+ desc 'The AWS region to use when authenticating'
102
+ config_param :aws_region, :string, default: nil
103
+ desc 'The AWS role ARN to assume when authenticating'
104
+ config_param :aws_role_arn, :string, default: nil
105
+ end
106
+
107
+ def connection_cache_id_thread_key
108
+ "#{plugin_id}_connection_cache_id"
109
+ end
110
+
111
+ def connection_cache_id_for_thread
112
+ Thread.current[connection_cache_id_thread_key]
113
+ end
114
+
115
+ def connection_cache_id_for_thread=(id)
116
+ Thread.current[connection_cache_id_thread_key] = id
95
117
  end
96
118
 
97
119
  def initialize
@@ -100,11 +122,23 @@ module Fluent::Plugin
100
122
  @uri = nil
101
123
  @proxy_uri = nil
102
124
  @formatter = nil
125
+
126
+ @connection_cache = []
127
+ @connection_cache_id_mutex = Mutex.new
128
+ @connection_cache_next_id = 0
129
+ end
130
+
131
+ def close
132
+ super
133
+
134
+ @connection_cache.each {|entry| entry.conn.finish if entry.conn&.started? }
103
135
  end
104
136
 
105
137
  def configure(conf)
106
138
  super
107
139
 
140
+ @connection_cache = Array.new(actual_flush_thread_count, ConnectionCache.new("", nil)) if @reuse_connections
141
+
108
142
  if @retryable_response_codes.nil?
109
143
  log.warn('Status code 503 is going to be removed from default `retryable_response_codes` from fluentd v2. Please add it by yourself if you wish')
110
144
  @retryable_response_codes = [503]
@@ -121,6 +155,36 @@ module Fluent::Plugin
121
155
  end
122
156
  define_singleton_method(:format, method(:format_json_array))
123
157
  end
158
+
159
+ if @auth and @auth.method == :aws_sigv4
160
+ begin
161
+ require 'aws-sigv4'
162
+ require 'aws-sdk-core'
163
+ rescue LoadError
164
+ raise Fluent::ConfigError, "The aws-sdk-core and aws-sigv4 gems are required for aws_sigv4 auth. Run: gem install aws-sdk-core -v '~> 3.191'"
165
+ end
166
+
167
+ raise Fluent::ConfigError, "aws_service is required for aws_sigv4 auth" unless @auth.aws_service != nil
168
+ raise Fluent::ConfigError, "aws_region is required for aws_sigv4 auth" unless @auth.aws_region != nil
169
+
170
+ if @auth.aws_role_arn == nil
171
+ aws_credentials = Aws::CredentialProviderChain.new.resolve
172
+ else
173
+ aws_credentials = Aws::AssumeRoleCredentials.new(
174
+ client: Aws::STS::Client.new(
175
+ region: @auth.aws_region
176
+ ),
177
+ role_arn: @auth.aws_role_arn,
178
+ role_session_name: "fluentd"
179
+ )
180
+ end
181
+
182
+ @aws_signer = Aws::Sigv4::Signer.new(
183
+ service: @auth.aws_service,
184
+ region: @auth.aws_region,
185
+ credentials_provider: aws_credentials
186
+ )
187
+ end
124
188
  end
125
189
 
126
190
  def multi_workers_ready?
@@ -215,7 +279,7 @@ module Fluent::Plugin
215
279
  URI.parse(endpoint)
216
280
  end
217
281
 
218
- def set_headers(req, chunk)
282
+ def set_headers(req, uri, chunk)
219
283
  if @headers
220
284
  @headers.each do |k, v|
221
285
  req[k] = v
@@ -229,6 +293,28 @@ module Fluent::Plugin
229
293
  req['Content-Type'] = @content_type
230
294
  end
231
295
 
296
+ def set_auth(req, uri)
297
+ return unless @auth
298
+
299
+ if @auth.method == :basic
300
+ req.basic_auth(@auth.username, @auth.password)
301
+ elsif @auth.method == :aws_sigv4
302
+ signature = @aws_signer.sign_request(
303
+ http_method: req.method,
304
+ url: uri.request_uri,
305
+ headers: {
306
+ 'Content-Type' => @content_type,
307
+ 'Host' => uri.host
308
+ },
309
+ body: req.body
310
+ )
311
+ req.add_field('x-amz-date', signature.headers['x-amz-date'])
312
+ req.add_field('x-amz-security-token', signature.headers['x-amz-security-token'])
313
+ req.add_field('x-amz-content-sha256', signature.headers['x-amz-content-sha256'])
314
+ req.add_field('authorization', signature.headers['authorization'])
315
+ end
316
+ end
317
+
232
318
  def create_request(chunk, uri)
233
319
  req = case @http_method
234
320
  when :post
@@ -236,23 +322,49 @@ module Fluent::Plugin
236
322
  when :put
237
323
  Net::HTTP::Put.new(uri.request_uri)
238
324
  end
239
- if @auth
240
- req.basic_auth(@auth.username, @auth.password)
241
- end
242
- set_headers(req, chunk)
325
+ set_headers(req, uri, chunk)
243
326
  req.body = @json_array ? "[#{chunk.read.chop}]" : chunk.read
327
+
328
+ # At least one authentication method requires the body and other headers, so the order of this call matters
329
+ set_auth(req, uri)
244
330
  req
245
331
  end
246
332
 
333
+ def make_request_cached(uri, req)
334
+ id = self.connection_cache_id_for_thread
335
+ if id.nil?
336
+ @connection_cache_id_mutex.synchronize {
337
+ id = @connection_cache_next_id
338
+ @connection_cache_next_id += 1
339
+ }
340
+ self.connection_cache_id_for_thread = id
341
+ end
342
+ uri_str = uri.to_s
343
+ if @connection_cache[id].uri != uri_str
344
+ @connection_cache[id].conn.finish if @connection_cache[id].conn&.started?
345
+ http = if @proxy_uri
346
+ Net::HTTP.start(uri.host, uri.port, @proxy_uri.host, @proxy_uri.port, @proxy_uri.user, @proxy_uri.password, @http_opt)
347
+ else
348
+ Net::HTTP.start(uri.host, uri.port, @http_opt)
349
+ end
350
+ @connection_cache[id] = ConnectionCache.new(uri_str, http)
351
+ end
352
+ @connection_cache[id].conn.request(req)
353
+ end
354
+
355
+ def make_request(uri, req, &block)
356
+ if @proxy_uri
357
+ Net::HTTP.start(uri.host, uri.port, @proxy_uri.host, @proxy_uri.port, @proxy_uri.user, @proxy_uri.password, @http_opt, &block)
358
+ else
359
+ Net::HTTP.start(uri.host, uri.port, @http_opt, &block)
360
+ end
361
+ end
362
+
247
363
  def send_request(uri, req)
248
- res = if @proxy_uri
249
- Net::HTTP.start(uri.host, uri.port, @proxy_uri.host, @proxy_uri.port, @proxy_uri.user, @proxy_uri.password, @http_opt) { |http|
250
- http.request(req)
251
- }
364
+ res = if @reuse_connections
365
+ make_request_cached(uri, req)
252
366
  else
253
- Net::HTTP.start(uri.host, uri.port, @http_opt) { |http|
254
- http.request(req)
255
- }
367
+ make_request(uri, req) { |http| http.request(req) }
256
368
  end
257
369
 
258
370
  if res.is_a?(Net::HTTPSuccess)
@@ -21,7 +21,6 @@ module Fluent
21
21
  @_owner = plugin
22
22
 
23
23
  @_plugin_id = plugin.plugin_id
24
- @_plugin_id_configured = plugin.plugin_id_configured?
25
24
 
26
25
  @log = plugin.log
27
26
  end
@@ -70,16 +70,33 @@ module Fluent
70
70
  end
71
71
 
72
72
  def parse(text)
73
- record = @load_proc.call(text)
74
- time = parse_time(record)
75
- if @execute_convert_values
76
- time, record = convert_values(time, record)
73
+ parsed_json = @load_proc.call(text)
74
+
75
+ if parsed_json.is_a?(Hash)
76
+ time, record = parse_one_record(parsed_json)
77
+ yield time, record
78
+ elsif parsed_json.is_a?(Array)
79
+ parsed_json.each do |record|
80
+ unless record.is_a?(Hash)
81
+ yield nil, nil
82
+ next
83
+ end
84
+ time, parsed_record = parse_one_record(record)
85
+ yield time, parsed_record
86
+ end
87
+ else
88
+ yield nil, nil
77
89
  end
78
- yield time, record
90
+
79
91
  rescue @error_class, EncodingError # EncodingError is for oj 3.x or later
80
92
  yield nil, nil
81
93
  end
82
94
 
95
+ def parse_one_record(record)
96
+ time = parse_time(record)
97
+ convert_values(time, record)
98
+ end
99
+
83
100
  def parser_type
84
101
  :text
85
102
  end
@@ -31,9 +31,9 @@ module Fluent
31
31
  :binary
32
32
  end
33
33
 
34
- def parse(data)
34
+ def parse(data, &block)
35
35
  @unpacker.feed_each(data) do |obj|
36
- yield convert_values(parse_time(obj), obj)
36
+ parse_unpacked_data(obj, &block)
37
37
  end
38
38
  end
39
39
  alias parse_partial_data parse
@@ -41,8 +41,29 @@ module Fluent
41
41
  def parse_io(io, &block)
42
42
  u = Fluent::MessagePackFactory.engine_factory.unpacker(io)
43
43
  u.each do |obj|
44
- time, record = convert_values(parse_time(obj), obj)
44
+ parse_unpacked_data(obj, &block)
45
+ end
46
+ end
47
+
48
+ def parse_unpacked_data(data)
49
+ if data.is_a?(Hash)
50
+ time, record = convert_values(parse_time(data), data)
45
51
  yield time, record
52
+ return
53
+ end
54
+
55
+ unless data.is_a?(Array)
56
+ yield nil, nil
57
+ return
58
+ end
59
+
60
+ data.each do |record|
61
+ unless record.is_a?(Hash)
62
+ yield nil, nil
63
+ next
64
+ end
65
+ time, converted_record = convert_values(parse_time(record), record)
66
+ yield time, converted_record
46
67
  end
47
68
  end
48
69
  end
@@ -65,9 +65,9 @@ module Fluent
65
65
  metrics.configure(config)
66
66
  # For multi workers environment, cmetrics should be distinguish with static labels.
67
67
  if Fluent::Engine.system_config.workers > 1
68
- labels.merge!(worker_id: fluentd_worker_id.to_s)
68
+ labels[:worker_id] = fluentd_worker_id.to_s
69
69
  end
70
- labels.merge!(plugin: @plugin_type_or_id)
70
+ labels[:plugin] = @plugin_type_or_id
71
71
  metrics.create(namespace: namespace, subsystem: subsystem, name: name, help_text: help_text, labels: labels)
72
72
 
73
73
  @_metrics["#{@plugin_type_or_id}_#{namespace}_#{subsystem}_#{name}"] = metrics
@@ -60,13 +60,13 @@ module Fluent
60
60
  # search from additional plugin directories
61
61
  if @dir_search_prefix
62
62
  path = "#{@dir_search_prefix}#{type}"
63
- files = @paths.map { |lp|
63
+ files = @paths.filter_map { |lp|
64
64
  lpath = File.expand_path(File.join(lp, "#{path}.rb"))
65
65
  File.exist?(lpath) ? lpath : nil
66
- }.compact
66
+ }
67
67
  unless files.empty?
68
68
  # prefer newer version
69
- require files.sort.last
69
+ require files.max
70
70
  return
71
71
  end
72
72
  end
@@ -74,17 +74,17 @@ module Fluent
74
74
  path = "#{@search_prefix}#{type}"
75
75
 
76
76
  # prefer LOAD_PATH than gems
77
- files = $LOAD_PATH.map { |lp|
77
+ files = $LOAD_PATH.filter_map { |lp|
78
78
  if lp == FLUENT_LIB_PATH
79
79
  nil
80
80
  else
81
81
  lpath = File.expand_path(File.join(lp, "#{path}.rb"))
82
82
  File.exist?(lpath) ? lpath : nil
83
83
  end
84
- }.compact
84
+ }
85
85
  unless files.empty?
86
86
  # prefer newer version
87
- require files.sort.last
87
+ require files.max
88
88
  return
89
89
  end
90
90
 
@@ -139,7 +139,7 @@ module Fluent
139
139
  assert_equal(@expected_buffer, buffer)
140
140
  end
141
141
 
142
- lines.keys.each do |meta|
142
+ lines.each_key do |meta|
143
143
  chunk = @instance.buffer.generate_chunk(meta).staged!
144
144
  chunk.append(lines[meta])
145
145
  begin
@@ -23,7 +23,7 @@ module Fluent
23
23
  end
24
24
 
25
25
  def self.hex(unique_id)
26
- unique_id.unpack('H*').first
26
+ unique_id.unpack1('H*')
27
27
  end
28
28
 
29
29
  module Mixin
@@ -16,6 +16,6 @@
16
16
 
17
17
  module Fluent
18
18
 
19
- VERSION = '1.16.5'
19
+ VERSION = '1.17.0'
20
20
 
21
21
  end
@@ -72,11 +72,18 @@ class ConsoleAdapterTest < Test::Unit::TestCase
72
72
  fatal: :fatal)
73
73
  def test_options(level)
74
74
  @console_logger.send(level, "subject", kwarg1: "opt1", kwarg2: "opt2")
75
+ lines = @logdev.logs[0].split("\n")
76
+ args = JSON.load(lines[1..].collect { |str| str.sub(/\s+\|/, "") }.join("\n"));
75
77
  assert_equal([
76
- "#{@timestamp_str} [#{level}]: 0.0s: subject\n" +
77
- " | {\"kwarg1\":\"opt1\",\"kwarg2\":\"opt2\"}\n"
78
+ 1,
79
+ "#{@timestamp_str} [#{level}]: 0.0s: subject",
80
+ { "kwarg1" => "opt1", "kwarg2" => "opt2" }
78
81
  ],
79
- @logdev.logs)
82
+ [
83
+ @logdev.logs.size,
84
+ lines[0],
85
+ args
86
+ ])
80
87
  end
81
88
 
82
89
  data(debug: :debug,
File without changes
File without changes
File without changes
File without changes
@@ -517,6 +517,28 @@ class HttpInputTest < Test::Unit::TestCase
517
517
  assert_equal_event_time time, d.events[1][1]
518
518
  end
519
519
 
520
+ def test_csp_report
521
+ d = create_driver
522
+ time = event_time("2011-01-02 13:14:15 UTC")
523
+ time_i = time.to_i
524
+ events = [
525
+ ["tag1", time, {"a"=>1}],
526
+ ["tag2", time, {"a"=>2}],
527
+ ]
528
+ res_codes = []
529
+
530
+ d.run(expect_records: 2) do
531
+ events.each do |tag, t, record|
532
+ res = post("/#{tag}?time=#{time_i.to_s}", record.to_json, {"Content-Type"=>"application/csp-report; charset=utf-8"})
533
+ res_codes << res.code
534
+ end
535
+ end
536
+ assert_equal ["200", "200"], res_codes
537
+ assert_equal events, d.events
538
+ assert_equal_event_time time, d.events[0][1]
539
+ assert_equal_event_time time, d.events[1][1]
540
+ end
541
+
520
542
  def test_application_msgpack
521
543
  d = create_driver
522
544
  time = event_time("2011-01-02 13:14:15 UTC")
@@ -982,7 +1004,7 @@ class HttpInputTest < Test::Unit::TestCase
982
1004
  assert_equal ["403", "403"], res_codes
983
1005
  end
984
1006
 
985
- def test_add_query_params
1007
+ def test_add_query_params
986
1008
  d = create_driver(config + "add_query_params true")
987
1009
  assert_equal true, d.instance.add_query_params
988
1010
 
@@ -1538,6 +1538,147 @@ class TailInputTest < Test::Unit::TestCase
1538
1538
  assert_equal(ex_paths - [ex_paths.last], plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
1539
1539
  end
1540
1540
 
1541
+ sub_test_case "expand_paths with glob" do |data|
1542
+ sub_test_case "extended_glob" do
1543
+ data("curly braces" => [true, "always", "test/plugin/data/log_numeric/{0,1}*.log"],
1544
+ "square brackets" => [true, "always", "test/plugin/data/log_numeric/[0-1][2-4].log"],
1545
+ "asterisk" => [true, "always", "test/plugin/data/log/*.log"],
1546
+ "one character matcher" => [true, "always", "test/plugin/data/log/tes?.log"],
1547
+ )
1548
+ def test_expand_paths_with_use_glob_p_and_almost_set_of_patterns
1549
+ result, option, path = data
1550
+ config = config_element("", "", {
1551
+ "tag" => "tail",
1552
+ "path" => path,
1553
+ "format" => "none",
1554
+ "pos_file" => "#{@tmp_dir}/tail.pos",
1555
+ "read_from_head" => true,
1556
+ "refresh_interval" => 30,
1557
+ "glob_policy" => option,
1558
+ "path_delimiter" => "|",
1559
+ "rotate_wait" => "#{EX_ROTATE_WAIT}s",
1560
+ "follow_inodes" => "#{EX_FOLLOW_INODES}",
1561
+ })
1562
+ plugin = create_driver(config, false).instance
1563
+ assert_equal(result, !!plugin.use_glob?(path))
1564
+ end
1565
+
1566
+ data("curly braces" => [true, false, "extended", "test/plugin/data/log_numeric/{0,1}*.log"],
1567
+ "square brackets" => [false, true, "extended", "test/plugin/data/log_numeric/[0-1][2-4].log"],
1568
+ "asterisk" => [false, true, "extended", "test/plugin/data/log/*.log"],
1569
+ "one character matcher" => [false, true, "extended", "test/plugin/data/log/tes?.log"],
1570
+ )
1571
+ def test_expand_paths_with_use_glob_p
1572
+ emit_exception_p, result, option, path = data
1573
+ config = config_element("", "", {
1574
+ "tag" => "tail",
1575
+ "path" => path,
1576
+ "format" => "none",
1577
+ "pos_file" => "#{@tmp_dir}/tail.pos",
1578
+ "read_from_head" => true,
1579
+ "refresh_interval" => 30,
1580
+ "glob_policy" => option,
1581
+ "rotate_wait" => "#{EX_ROTATE_WAIT}s",
1582
+ "follow_inodes" => "#{EX_FOLLOW_INODES}",
1583
+ })
1584
+ if emit_exception_p
1585
+ assert_raise(Fluent::ConfigError) do
1586
+ plugin = create_driver(config, false).instance
1587
+ end
1588
+ else
1589
+ plugin = create_driver(config, false).instance
1590
+ assert_equal(result, !!plugin.use_glob?(path))
1591
+ end
1592
+ end
1593
+ end
1594
+
1595
+ sub_test_case "only_use_backward_compatible" do
1596
+ data("square brackets" => [false, "backward_compatible", "test/plugin/data/log_numeric/[0-1][2-4].log"],
1597
+ "asterisk" => [true, "backward_compatible", "test/plugin/data/log/*.log"],
1598
+ "one character matcher" => [false, "backward_compatible", "test/plugin/data/log/tes?.log"],
1599
+ )
1600
+ def test_expand_paths_with_use_glob_p
1601
+ result, option, path = data
1602
+ config = config_element("", "", {
1603
+ "tag" => "tail",
1604
+ "path" => path,
1605
+ "format" => "none",
1606
+ "pos_file" => "#{@tmp_dir}/tail.pos",
1607
+ "read_from_head" => true,
1608
+ "refresh_interval" => 30,
1609
+ "glob_policy" => option,
1610
+ "rotate_wait" => "#{EX_ROTATE_WAIT}s",
1611
+ "follow_inodes" => "#{EX_FOLLOW_INODES}",
1612
+ })
1613
+ plugin = create_driver(config, false).instance
1614
+ assert_equal(result, !!plugin.use_glob?(path))
1615
+ end
1616
+ end
1617
+ end
1618
+
1619
+ def ex_config_with_brackets
1620
+ config_element("", "", {
1621
+ "tag" => "tail",
1622
+ "path" => "test/plugin/data/log_numeric/[0-1][2-4].log",
1623
+ "format" => "none",
1624
+ "pos_file" => "#{@tmp_dir}/tail.pos",
1625
+ "read_from_head" => true,
1626
+ "refresh_interval" => 30,
1627
+ "glob_policy" => "extended",
1628
+ "rotate_wait" => "#{EX_ROTATE_WAIT}s",
1629
+ "follow_inodes" => "#{EX_FOLLOW_INODES}",
1630
+ })
1631
+ end
1632
+
1633
+ def test_config_with_always_with_default_delimiter
1634
+ assert_raise(Fluent::ConfigError) do
1635
+ config = config_element("", "", {
1636
+ "tag" => "tail",
1637
+ "path" => "test/plugin/data/log_numeric/[0-1][2-4].log",
1638
+ "format" => "none",
1639
+ "pos_file" => "#{@tmp_dir}/tail.pos",
1640
+ "read_from_head" => true,
1641
+ "refresh_interval" => 30,
1642
+ "glob_policy" => "always",
1643
+ "rotate_wait" => "#{EX_ROTATE_WAIT}s",
1644
+ "follow_inodes" => "#{EX_FOLLOW_INODES}",
1645
+ })
1646
+
1647
+ create_driver(config, false).instance
1648
+ end
1649
+ end
1650
+
1651
+ def test_config_with_always_with_custom_delimiter
1652
+ assert_nothing_raised do
1653
+ config = config_element("", "", {
1654
+ "tag" => "tail",
1655
+ "path" => "test/plugin/data/log_numeric/[0-1][2-4].log",
1656
+ "format" => "none",
1657
+ "pos_file" => "#{@tmp_dir}/tail.pos",
1658
+ "read_from_head" => true,
1659
+ "refresh_interval" => 30,
1660
+ "glob_policy" => "always",
1661
+ "path_delimiter" => "|",
1662
+ "rotate_wait" => "#{EX_ROTATE_WAIT}s",
1663
+ "follow_inodes" => "#{EX_FOLLOW_INODES}",
1664
+ })
1665
+
1666
+ create_driver(config, false).instance
1667
+ end
1668
+ end
1669
+
1670
+ def test_expand_paths_with_brackets
1671
+ expanded_paths = [
1672
+ create_target_info('test/plugin/data/log_numeric/01.log'),
1673
+ create_target_info('test/plugin/data/log_numeric/02.log'),
1674
+ create_target_info('test/plugin/data/log_numeric/12.log'),
1675
+ create_target_info('test/plugin/data/log_numeric/14.log'),
1676
+ ]
1677
+
1678
+ plugin = create_driver(ex_config_with_brackets, false).instance
1679
+ assert_equal(expanded_paths - [expanded_paths.first], plugin.expand_paths.values.sort_by { |path_ino| path_ino.path })
1680
+ end
1681
+
1541
1682
  def test_expand_paths_with_duplicate_configuration
1542
1683
  expanded_paths = [
1543
1684
  create_target_info('test/plugin/data/log/foo/bar.log'),