fluentd 1.16.5-x64-mingw-ucrt → 1.17.0-x64-mingw-ucrt

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 (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'),