fluentd 1.16.3 → 1.17.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.
- checksums.yaml +4 -4
- data/.github/DISCUSSION_TEMPLATE/q-a-japanese.yml +50 -0
- data/.github/DISCUSSION_TEMPLATE/q-a.yml +47 -0
- data/.github/workflows/test-ruby-head.yml +31 -0
- data/.github/workflows/{linux-test.yaml → test.yml} +9 -13
- data/CHANGELOG.md +66 -0
- data/README.md +1 -1
- data/Rakefile +1 -1
- data/fluentd.gemspec +9 -1
- data/lib/fluent/command/binlog_reader.rb +1 -1
- data/lib/fluent/config/configure_proxy.rb +2 -2
- data/lib/fluent/config/types.rb +1 -1
- data/lib/fluent/configurable.rb +2 -2
- data/lib/fluent/counter/mutex_hash.rb +1 -1
- data/lib/fluent/fluent_log_event_router.rb +0 -2
- data/lib/fluent/plugin/buf_file.rb +1 -1
- data/lib/fluent/plugin/buffer/file_chunk.rb +1 -1
- data/lib/fluent/plugin/buffer/file_single_chunk.rb +2 -3
- data/lib/fluent/plugin/buffer.rb +95 -86
- data/lib/fluent/plugin/filter_parser.rb +26 -8
- data/lib/fluent/plugin/in_http.rb +18 -53
- data/lib/fluent/plugin/in_tail.rb +52 -8
- data/lib/fluent/plugin/out_http.rb +125 -13
- data/lib/fluent/plugin/owned_by_mixin.rb +0 -1
- data/lib/fluent/plugin/parser_json.rb +22 -5
- data/lib/fluent/plugin/parser_msgpack.rb +24 -3
- data/lib/fluent/plugin_helper/metrics.rb +2 -2
- data/lib/fluent/registry.rb +6 -6
- data/lib/fluent/test/output_test.rb +1 -1
- data/lib/fluent/unique_id.rb +1 -1
- data/lib/fluent/version.rb +1 -1
- data/test/command/test_fluentd.rb +8 -2
- data/test/log/test_console_adapter.rb +10 -3
- data/test/plugin/data/log_numeric/01.log +0 -0
- data/test/plugin/data/log_numeric/02.log +0 -0
- data/test/plugin/data/log_numeric/12.log +0 -0
- data/test/plugin/data/log_numeric/14.log +0 -0
- data/test/plugin/test_buffer.rb +110 -0
- data/test/plugin/test_in_http.rb +23 -1
- data/test/plugin/test_in_tail.rb +307 -3
- data/test/plugin/test_out_forward.rb +34 -39
- data/test/plugin/test_out_http.rb +128 -0
- data/test/plugin/test_owned_by.rb +0 -1
- data/test/plugin/test_parser_json.rb +106 -0
- data/test/plugin/test_parser_msgpack.rb +127 -0
- data/test/plugin/test_storage.rb +0 -1
- data/test/plugin_helper/test_child_process.rb +17 -7
- metadata +104 -9
- data/.github/workflows/macos-test.yaml +0 -34
- data/.github/workflows/windows-test.yaml +0 -49
- /data/.github/ISSUE_TEMPLATE/{bug_report.yaml → bug_report.yml} +0 -0
- /data/.github/ISSUE_TEMPLATE/{feature_request.yaml → feature_request.yml} +0 -0
|
@@ -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
|
-
|
|
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 @
|
|
249
|
-
|
|
250
|
-
http.request(req)
|
|
251
|
-
}
|
|
364
|
+
res = if @reuse_connections
|
|
365
|
+
make_request_cached(uri, req)
|
|
252
366
|
else
|
|
253
|
-
|
|
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)
|
|
@@ -70,16 +70,33 @@ module Fluent
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def parse(text)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if
|
|
76
|
-
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
68
|
+
labels[:worker_id] = fluentd_worker_id.to_s
|
|
69
69
|
end
|
|
70
|
-
labels
|
|
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
|
data/lib/fluent/registry.rb
CHANGED
|
@@ -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.
|
|
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
|
-
}
|
|
66
|
+
}
|
|
67
67
|
unless files.empty?
|
|
68
68
|
# prefer newer version
|
|
69
|
-
require files.
|
|
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.
|
|
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
|
-
}
|
|
84
|
+
}
|
|
85
85
|
unless files.empty?
|
|
86
86
|
# prefer newer version
|
|
87
|
-
require files.
|
|
87
|
+
require files.max
|
|
88
88
|
return
|
|
89
89
|
end
|
|
90
90
|
|
data/lib/fluent/unique_id.rb
CHANGED
data/lib/fluent/version.rb
CHANGED
|
@@ -941,7 +941,7 @@ CONF
|
|
|
941
941
|
'-external-encoding' => '--external-encoding=utf-8',
|
|
942
942
|
'-internal-encoding' => '--internal-encoding=utf-8',
|
|
943
943
|
)
|
|
944
|
-
test "-E option is set to RUBYOPT" do |
|
|
944
|
+
test "-E option is set to RUBYOPT" do |base_opt|
|
|
945
945
|
conf = <<CONF
|
|
946
946
|
<source>
|
|
947
947
|
@type dummy
|
|
@@ -952,6 +952,7 @@ CONF
|
|
|
952
952
|
</match>
|
|
953
953
|
CONF
|
|
954
954
|
conf_path = create_conf_file('rubyopt_test.conf', conf)
|
|
955
|
+
opt = base_opt.dup
|
|
955
956
|
opt << " #{ENV['RUBYOPT']}" if ENV['RUBYOPT']
|
|
956
957
|
assert_log_matches(
|
|
957
958
|
create_cmdline(conf_path),
|
|
@@ -991,9 +992,14 @@ CONF
|
|
|
991
992
|
</match>
|
|
992
993
|
CONF
|
|
993
994
|
conf_path = create_conf_file('rubyopt_invalid_test.conf', conf)
|
|
995
|
+
if Gem::Version.create(RUBY_VERSION) >= Gem::Version.create('3.3.0')
|
|
996
|
+
expected_phrase = 'ruby: invalid switch in RUBYOPT'
|
|
997
|
+
else
|
|
998
|
+
expected_phrase = 'Invalid option is passed to RUBYOPT'
|
|
999
|
+
end
|
|
994
1000
|
assert_log_matches(
|
|
995
1001
|
create_cmdline(conf_path),
|
|
996
|
-
|
|
1002
|
+
expected_phrase,
|
|
997
1003
|
env: { 'RUBYOPT' => 'a' },
|
|
998
1004
|
)
|
|
999
1005
|
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
|
-
|
|
77
|
-
"
|
|
78
|
+
1,
|
|
79
|
+
"#{@timestamp_str} [#{level}]: 0.0s: subject",
|
|
80
|
+
{ "kwarg1" => "opt1", "kwarg2" => "opt2" }
|
|
78
81
|
],
|
|
79
|
-
|
|
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
|
data/test/plugin/test_buffer.rb
CHANGED
|
@@ -850,6 +850,116 @@ class BufferTest < Test::Unit::TestCase
|
|
|
850
850
|
test '#compress returns :text' do
|
|
851
851
|
assert_equal :text, @p.compress
|
|
852
852
|
end
|
|
853
|
+
|
|
854
|
+
# https://github.com/fluent/fluentd/issues/3089
|
|
855
|
+
test "closed chunk should not be committed" do
|
|
856
|
+
assert_equal 8 * 1024 * 1024, @p.chunk_limit_size
|
|
857
|
+
assert_equal 0.95, @p.chunk_full_threshold
|
|
858
|
+
|
|
859
|
+
purge_count = 0
|
|
860
|
+
|
|
861
|
+
stub.proxy(@p).generate_chunk(anything) do |chunk|
|
|
862
|
+
stub.proxy(chunk).purge do |result|
|
|
863
|
+
purge_count += 1
|
|
864
|
+
result
|
|
865
|
+
end
|
|
866
|
+
stub.proxy(chunk).commit do |result|
|
|
867
|
+
assert_false(chunk.closed?)
|
|
868
|
+
result
|
|
869
|
+
end
|
|
870
|
+
stub.proxy(chunk).rollback do |result|
|
|
871
|
+
assert_false(chunk.closed?)
|
|
872
|
+
result
|
|
873
|
+
end
|
|
874
|
+
chunk
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
m = @p.metadata(timekey: Time.parse('2016-04-11 16:40:00 +0000').to_i)
|
|
878
|
+
small_row = "x" * 1024 * 400
|
|
879
|
+
big_row = "x" * 1024 * 1024 * 8 # just `chunk_size_limit`, it does't cause BufferOverFlowError.
|
|
880
|
+
|
|
881
|
+
# Write 42 events in 1 event stream, last one is for triggering `ShouldRetry`
|
|
882
|
+
@p.write({m => [small_row] * 40 + [big_row] + ["x"]})
|
|
883
|
+
|
|
884
|
+
# Above event strem will be splitted twice by `Buffer#write_step_by_step`
|
|
885
|
+
#
|
|
886
|
+
# 1. `write_once`: 42 [events] * 1 [stream]
|
|
887
|
+
# 2. `write_step_by_step`: 4 [events]* 10 [streams] + 2 [events] * 1 [stream]
|
|
888
|
+
# 3. `write_step_by_step` (by `ShouldRetry`): 1 [event] * 42 [streams]
|
|
889
|
+
#
|
|
890
|
+
# The problematic data is built in the 2nd stage.
|
|
891
|
+
# In the 2nd stage, 5 streams are packed in a chunk.
|
|
892
|
+
# ((1024 * 400) [bytes] * 4 [events] * 5 [streams] = 8192000 [bytes] < `chunk_limit_size` (8MB)).
|
|
893
|
+
# So 3 chunks are used to store all data.
|
|
894
|
+
# The 1st chunk is already staged by `write_once`.
|
|
895
|
+
# The 2nd & 3rd chunks are newly created as unstaged.
|
|
896
|
+
# The 3rd chunk is purged before `ShouldRetry`, it's no problem:
|
|
897
|
+
# https://github.com/fluent/fluentd/blob/7e9eba736ff40ad985341be800ddc46558be75f2/lib/fluent/plugin/buffer.rb#L850
|
|
898
|
+
# The 2nd chunk is purged in `rescue ShouldRetry`:
|
|
899
|
+
# https://github.com/fluent/fluentd/blob/7e9eba736ff40ad985341be800ddc46558be75f2/lib/fluent/plugin/buffer.rb#L862
|
|
900
|
+
# It causes the issue described in https://github.com/fluent/fluentd/issues/3089#issuecomment-1811839198
|
|
901
|
+
|
|
902
|
+
assert_equal 2, purge_count
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
# https://github.com/fluent/fluentd/issues/4446
|
|
906
|
+
test "#write_step_by_step keeps chunks kept in locked in entire #write process" do
|
|
907
|
+
assert_equal 8 * 1024 * 1024, @p.chunk_limit_size
|
|
908
|
+
assert_equal 0.95, @p.chunk_full_threshold
|
|
909
|
+
|
|
910
|
+
mon_enter_counts_by_chunk = {}
|
|
911
|
+
mon_exit_counts_by_chunk = {}
|
|
912
|
+
|
|
913
|
+
stub.proxy(@p).generate_chunk(anything) do |chunk|
|
|
914
|
+
stub(chunk).mon_enter do
|
|
915
|
+
enter_count = 1 + mon_enter_counts_by_chunk.fetch(chunk, 0)
|
|
916
|
+
exit_count = mon_exit_counts_by_chunk.fetch(chunk, 0)
|
|
917
|
+
mon_enter_counts_by_chunk[chunk] = enter_count
|
|
918
|
+
|
|
919
|
+
# Assert that chunk is passed to &block of write_step_by_step before exiting the lock.
|
|
920
|
+
# (i.e. The lock count must be 2 greater than the exit count).
|
|
921
|
+
# Since ShouldRetry occurs once, the staged chunk takes the lock 3 times when calling the block.
|
|
922
|
+
if chunk.staged?
|
|
923
|
+
lock_in_block = enter_count == 3
|
|
924
|
+
assert_equal(enter_count - 2, exit_count) if lock_in_block
|
|
925
|
+
else
|
|
926
|
+
lock_in_block = enter_count == 2
|
|
927
|
+
assert_equal(enter_count - 2, exit_count) if lock_in_block
|
|
928
|
+
end
|
|
929
|
+
end
|
|
930
|
+
stub(chunk).mon_exit do
|
|
931
|
+
exit_count = 1 + mon_exit_counts_by_chunk.fetch(chunk, 0)
|
|
932
|
+
mon_exit_counts_by_chunk[chunk] = exit_count
|
|
933
|
+
end
|
|
934
|
+
chunk
|
|
935
|
+
end
|
|
936
|
+
|
|
937
|
+
m = @p.metadata(timekey: Time.parse('2016-04-11 16:40:00 +0000').to_i)
|
|
938
|
+
small_row = "x" * 1024 * 400
|
|
939
|
+
big_row = "x" * 1024 * 1024 * 8 # just `chunk_size_limit`, it does't cause BufferOverFlowError.
|
|
940
|
+
|
|
941
|
+
# Write 42 events in 1 event stream, last one is for triggering `ShouldRetry`
|
|
942
|
+
@p.write({m => [small_row] * 40 + [big_row] + ["x"]})
|
|
943
|
+
|
|
944
|
+
# Above event strem will be splitted twice by `Buffer#write_step_by_step`
|
|
945
|
+
#
|
|
946
|
+
# 1. `write_once`: 42 [events] * 1 [stream]
|
|
947
|
+
# 2. `write_step_by_step`: 4 [events]* 10 [streams] + 2 [events] * 1 [stream]
|
|
948
|
+
# 3. `write_step_by_step` (by `ShouldRetry`): 1 [event] * 42 [streams]
|
|
949
|
+
#
|
|
950
|
+
# Example of staged chunk lock behavior:
|
|
951
|
+
#
|
|
952
|
+
# 1. mon_enter in write_step_by_step
|
|
953
|
+
# 2. ShouldRetry occurs
|
|
954
|
+
# 3. mon_exit in write_step_by_step
|
|
955
|
+
# 4. mon_enter again in write_step_by_step (retry)
|
|
956
|
+
# 5. passed to &block of write_step_by_step
|
|
957
|
+
# 6. mon_enter in the block (write)
|
|
958
|
+
# 7. mon_exit in write_step_by_step
|
|
959
|
+
# 8. mon_exit in write
|
|
960
|
+
|
|
961
|
+
assert_equal(mon_enter_counts_by_chunk.values, mon_exit_counts_by_chunk.values)
|
|
962
|
+
end
|
|
853
963
|
end
|
|
854
964
|
|
|
855
965
|
sub_test_case 'standard format with configuration for test with lower chunk limit size' do
|
data/test/plugin/test_in_http.rb
CHANGED
|
@@ -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
|
|