fluent-plugin-kinesis 2.2.0 → 3.0.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/PULL_REQUEST_TEMPLATE.md +6 -0
- data/.travis.yml +18 -7
- data/CHANGELOG.md +9 -2
- data/CODE_OF_CONDUCT.md +4 -0
- data/CONTRIBUTING.md +61 -0
- data/README.md +34 -38
- data/benchmark/task.rake +6 -10
- data/fluent-plugin-kinesis.gemspec +20 -13
- data/gemfiles/{Gemfile.td-agent-2.3.5 → Gemfile.fluentd-0.14.10} +1 -6
- data/gemfiles/Gemfile.td-agent-3.1.1 +31 -0
- data/gemfiles/Gemfile.td-agent-3.2.0 +31 -0
- data/gemfiles/Gemfile.td-agent-3.2.1 +31 -0
- data/gemfiles/Gemfile.td-agent-3.3.0 +31 -0
- data/lib/fluent/plugin/kinesis.rb +112 -117
- data/lib/fluent/plugin/kinesis_helper/aggregator.rb +49 -47
- data/lib/fluent/plugin/kinesis_helper/api.rb +139 -137
- data/lib/fluent/plugin/kinesis_helper/client.rb +118 -128
- data/lib/fluent/plugin/out_kinesis_firehose.rb +31 -29
- data/lib/fluent/plugin/out_kinesis_streams.rb +41 -39
- data/lib/fluent/plugin/out_kinesis_streams_aggregated.rb +45 -43
- data/lib/fluent_plugin_kinesis/version.rb +1 -1
- metadata +57 -24
@@ -0,0 +1,31 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
5
|
+
# may not use this file except in compliance with the License. A copy of
|
6
|
+
# the License is located at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is
|
11
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
12
|
+
# ANY KIND, either express or implied. See the License for the specific
|
13
|
+
# language governing permissions and limitations under the License.
|
14
|
+
|
15
|
+
source 'https://rubygems.org'
|
16
|
+
|
17
|
+
# Specify your gem's dependencies in fluent-plugin-kinesis.gemspec
|
18
|
+
gemspec path: ".."
|
19
|
+
|
20
|
+
# Specify related gems for td-agent v3.2.0
|
21
|
+
# https://github.com/treasure-data/omnibus-td-agent/blob/v3.2.0/config/projects/td-agent3.rb#L27
|
22
|
+
gem "fluentd", "1.2.2"
|
23
|
+
# https://github.com/treasure-data/omnibus-td-agent/blob/v3.2.0/plugin_gems.rb#L16-L23
|
24
|
+
gem "jmespath", "1.4.0"
|
25
|
+
gem "aws-partitions", "1.87.0"
|
26
|
+
gem "aws-sigv4", "1.0.2"
|
27
|
+
gem "aws-sdk-core", "3.21.2"
|
28
|
+
gem "aws-sdk-kms", "1.5.0"
|
29
|
+
gem "aws-sdk-sqs", "1.3.0"
|
30
|
+
gem "aws-sdk-s3", "1.13.0"
|
31
|
+
gem "fluent-plugin-s3", "1.1.3"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
5
|
+
# may not use this file except in compliance with the License. A copy of
|
6
|
+
# the License is located at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is
|
11
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
12
|
+
# ANY KIND, either express or implied. See the License for the specific
|
13
|
+
# language governing permissions and limitations under the License.
|
14
|
+
|
15
|
+
source 'https://rubygems.org'
|
16
|
+
|
17
|
+
# Specify your gem's dependencies in fluent-plugin-kinesis.gemspec
|
18
|
+
gemspec path: ".."
|
19
|
+
|
20
|
+
# Specify related gems for td-agent v3.2.1
|
21
|
+
# https://github.com/treasure-data/omnibus-td-agent/blob/v3.2.1/config/projects/td-agent3.rb#L27
|
22
|
+
gem "fluentd", "1.2.6"
|
23
|
+
# https://github.com/treasure-data/omnibus-td-agent/blob/v3.2.1/plugin_gems.rb#L16-L23
|
24
|
+
gem "jmespath", "1.4.0"
|
25
|
+
gem "aws-partitions", "1.105.0"
|
26
|
+
gem "aws-sigv4", "1.0.3"
|
27
|
+
gem "aws-sdk-core", "3.30.0"
|
28
|
+
gem "aws-sdk-kms", "1.9.0"
|
29
|
+
gem "aws-sdk-sqs", "1.7.0"
|
30
|
+
gem "aws-sdk-s3", "1.21.0"
|
31
|
+
gem "fluent-plugin-s3", "1.1.6"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
5
|
+
# may not use this file except in compliance with the License. A copy of
|
6
|
+
# the License is located at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is
|
11
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
12
|
+
# ANY KIND, either express or implied. See the License for the specific
|
13
|
+
# language governing permissions and limitations under the License.
|
14
|
+
|
15
|
+
source 'https://rubygems.org'
|
16
|
+
|
17
|
+
# Specify your gem's dependencies in fluent-plugin-kinesis.gemspec
|
18
|
+
gemspec path: ".."
|
19
|
+
|
20
|
+
# Specify related gems for td-agent v3.3.0
|
21
|
+
# https://github.com/treasure-data/omnibus-td-agent/blob/v3.3.0/config/projects/td-agent3.rb#L27
|
22
|
+
gem "fluentd", "1.3.3"
|
23
|
+
# https://github.com/treasure-data/omnibus-td-agent/blob/v3.3.0/plugin_gems.rb#L16-L23
|
24
|
+
gem "jmespath", "1.4.0"
|
25
|
+
gem "aws-partitions", "1.127.0"
|
26
|
+
gem "aws-sigv4", "1.0.3"
|
27
|
+
gem "aws-sdk-core", "3.44.2"
|
28
|
+
gem "aws-sdk-kms", "1.13.0"
|
29
|
+
gem "aws-sdk-sqs", "1.10.0"
|
30
|
+
gem "aws-sdk-s3", "1.30.0"
|
31
|
+
gem "fluent-plugin-s3", "1.1.7"
|
@@ -12,154 +12,149 @@
|
|
12
12
|
# ANY KIND, either express or implied. See the License for the specific
|
13
13
|
# language governing permissions and limitations under the License.
|
14
14
|
|
15
|
+
require 'fluent/plugin/output'
|
15
16
|
require 'fluent/plugin/kinesis_helper/client'
|
16
17
|
require 'fluent/plugin/kinesis_helper/api'
|
17
18
|
require 'zlib'
|
18
19
|
|
19
20
|
module Fluent
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@record_message = if record.is_a? Array
|
35
|
-
record.reverse.map(&:to_s).join(', ')
|
36
|
-
else
|
37
|
-
record.to_s
|
21
|
+
module Plugin
|
22
|
+
class KinesisOutput < Fluent::Plugin::Output
|
23
|
+
include Fluent::MessagePackFactory::Mixin
|
24
|
+
include KinesisHelper::Client
|
25
|
+
include KinesisHelper::API
|
26
|
+
|
27
|
+
class SkipRecordError < ::StandardError
|
28
|
+
def initialize(message, record)
|
29
|
+
super message
|
30
|
+
@record_message = if record.is_a? Array
|
31
|
+
record.reverse.map(&:to_s).join(', ')
|
32
|
+
else
|
33
|
+
record.to_s
|
34
|
+
end
|
38
35
|
end
|
39
|
-
end
|
40
36
|
|
41
|
-
|
42
|
-
|
37
|
+
def to_s
|
38
|
+
super + ": " + @record_message
|
39
|
+
end
|
43
40
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
41
|
+
class KeyNotFoundError < SkipRecordError
|
42
|
+
def initialize(key, record)
|
43
|
+
super "Key '#{key}' doesn't exist", record
|
44
|
+
end
|
48
45
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
46
|
+
class ExceedMaxRecordSizeError < SkipRecordError
|
47
|
+
def initialize(size, record)
|
48
|
+
super "Record size limit exceeded in #{size/1024} KB", record
|
49
|
+
end
|
53
50
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
51
|
+
class InvalidRecordError < SkipRecordError
|
52
|
+
def initialize(record)
|
53
|
+
super "Invalid type of record", record
|
54
|
+
end
|
58
55
|
end
|
59
|
-
end
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
config_section :format do
|
65
|
-
config_set_default :@type, 'json'
|
66
|
-
end
|
67
|
-
config_section :inject do
|
68
|
-
config_set_default :time_type, 'string'
|
69
|
-
config_set_default :time_format, '%Y-%m-%dT%H:%M:%S.%N%z'
|
70
|
-
end
|
57
|
+
config_param :data_key, :string, default: nil
|
58
|
+
config_param :log_truncate_max_size, :integer, default: 1024
|
59
|
+
config_param :compression, :string, default: nil
|
71
60
|
|
72
|
-
|
61
|
+
desc "Formatter calls chomp and removes separator from the end of each record. This option is for compatible format with plugin v2. (default: false)"
|
62
|
+
# https://github.com/awslabs/aws-fluent-plugin-kinesis/issues/142
|
63
|
+
config_param :chomp_record, :bool, default: false
|
73
64
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
65
|
+
config_section :format do
|
66
|
+
config_set_default :@type, 'json'
|
67
|
+
end
|
68
|
+
config_section :inject do
|
69
|
+
config_set_default :time_type, 'string'
|
70
|
+
config_set_default :time_format, '%Y-%m-%dT%H:%M:%S.%N%z'
|
71
|
+
end
|
79
72
|
|
80
|
-
|
81
|
-
super
|
82
|
-
@data_formatter = data_formatter_create(conf)
|
83
|
-
end
|
73
|
+
config_param :debug, :bool, default: false
|
84
74
|
|
85
|
-
|
86
|
-
true
|
87
|
-
end
|
75
|
+
helpers :formatter, :inject
|
88
76
|
|
89
|
-
|
77
|
+
def configure(conf)
|
78
|
+
super
|
79
|
+
@data_formatter = data_formatter_create(conf)
|
80
|
+
end
|
90
81
|
|
91
|
-
|
92
|
-
|
93
|
-
|
82
|
+
def multi_workers_ready?
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
94
87
|
|
95
|
-
|
96
|
-
if fluentd_v0_12?
|
97
|
-
formatter = Fluent::Plugin.new_formatter(@format)
|
98
|
-
formatter.configure(conf)
|
99
|
-
else
|
88
|
+
def data_formatter_create(conf)
|
100
89
|
formatter = formatter_create
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
90
|
+
compressor = compressor_create
|
91
|
+
if @data_key.nil?
|
92
|
+
if @chomp_record
|
93
|
+
->(tag, time, record) {
|
94
|
+
record = inject_values_to_record(tag, time, record)
|
95
|
+
# Formatter calls chomp and removes separator from the end of each record.
|
96
|
+
# This option is for compatible format with plugin v2.
|
97
|
+
# https://github.com/awslabs/aws-fluent-plugin-kinesis/issues/142
|
98
|
+
compressor.call(formatter.format(tag, time, record).chomp.b)
|
99
|
+
}
|
100
|
+
else
|
101
|
+
->(tag, time, record) {
|
102
|
+
record = inject_values_to_record(tag, time, record)
|
103
|
+
compressor.call(formatter.format(tag, time, record).b)
|
104
|
+
}
|
107
105
|
end
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
}
|
106
|
+
else
|
107
|
+
->(tag, time, record) {
|
108
|
+
raise InvalidRecordError, record unless record.is_a? Hash
|
109
|
+
raise KeyNotFoundError.new(@data_key, record) if record[@data_key].nil?
|
110
|
+
compressor.call(record[@data_key].to_s.b)
|
111
|
+
}
|
112
|
+
end
|
116
113
|
end
|
117
|
-
end
|
118
114
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
115
|
+
def compressor_create
|
116
|
+
case @compression
|
117
|
+
when "zlib"
|
118
|
+
->(data) { Zlib::Deflate.deflate(data) }
|
119
|
+
else
|
120
|
+
->(data) { data }
|
121
|
+
end
|
125
122
|
end
|
126
|
-
end
|
127
123
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
124
|
+
def format_for_api(&block)
|
125
|
+
converted = block.call
|
126
|
+
size = size_of_values(converted)
|
127
|
+
if size > @max_record_size
|
128
|
+
raise ExceedMaxRecordSizeError.new(size, converted)
|
129
|
+
end
|
130
|
+
converted.to_msgpack
|
131
|
+
rescue SkipRecordError => e
|
132
|
+
log.error(truncate e)
|
133
|
+
''
|
133
134
|
end
|
134
|
-
converted.to_msgpack
|
135
|
-
rescue SkipRecordError => e
|
136
|
-
log.error(truncate e)
|
137
|
-
''
|
138
|
-
end
|
139
135
|
|
140
|
-
|
141
|
-
if fluentd_v0_12?
|
142
|
-
unique_id = chunk.unique_id.unpack('H*').first
|
143
|
-
else
|
136
|
+
def write_records_batch(chunk, &block)
|
144
137
|
unique_id = chunk.dump_unique_id_hex(chunk.unique_id)
|
138
|
+
chunk.open do |io|
|
139
|
+
records = msgpack_unpacker(io).to_enum
|
140
|
+
split_to_batches(records) do |batch, size|
|
141
|
+
log.debug(sprintf "Write chunk %s / %3d records / %4d KB", unique_id, batch.size, size/1024)
|
142
|
+
batch_request_with_retry(batch, &block)
|
143
|
+
log.debug("Finish writing chunk")
|
144
|
+
end
|
145
|
+
end
|
145
146
|
end
|
146
|
-
records = chunk.to_enum(:msgpack_each)
|
147
|
-
split_to_batches(records) do |batch, size|
|
148
|
-
log.debug(sprintf "Write chunk %s / %3d records / %4d KB", unique_id, batch.size, size/1024)
|
149
|
-
batch_request_with_retry(batch, &block)
|
150
|
-
log.debug("Finish writing chunk")
|
151
|
-
end
|
152
|
-
end
|
153
147
|
|
154
|
-
|
155
|
-
|
156
|
-
|
148
|
+
def request_type
|
149
|
+
self.class::RequestType
|
150
|
+
end
|
157
151
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
152
|
+
def truncate(msg)
|
153
|
+
if @log_truncate_max_size == 0 or (msg.to_s.size <= @log_truncate_max_size)
|
154
|
+
msg.to_s
|
155
|
+
else
|
156
|
+
msg.to_s[0...@log_truncate_max_size]
|
157
|
+
end
|
163
158
|
end
|
164
159
|
end
|
165
160
|
end
|
@@ -34,64 +34,66 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
|
34
34
|
end
|
35
35
|
|
36
36
|
module Fluent
|
37
|
-
module
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
module Plugin
|
38
|
+
module KinesisHelper
|
39
|
+
class Aggregator
|
40
|
+
AggregatedRecord = Google::Protobuf::DescriptorPool.generated_pool.lookup("AggregatedRecord").msgclass
|
41
|
+
Tag = Google::Protobuf::DescriptorPool.generated_pool.lookup("Tag").msgclass
|
42
|
+
Record = Google::Protobuf::DescriptorPool.generated_pool.lookup("Record").msgclass
|
42
43
|
|
43
|
-
|
44
|
+
class InvalidEncodingError < ::StandardError; end
|
44
45
|
|
45
|
-
|
46
|
+
MagicNumber = ['F3899AC2'].pack('H*')
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
def deaggregate(encoded)
|
58
|
-
unless aggregated?(encoded)
|
59
|
-
raise InvalidEncodingError, "Invalid MagicNumber #{encoded[0..3]}}"
|
48
|
+
def aggregate(records, partition_key)
|
49
|
+
message = AggregatedRecord.encode(AggregatedRecord.new(
|
50
|
+
partition_key_table: ['a', partition_key],
|
51
|
+
records: records.map{|data|
|
52
|
+
Record.new(partition_key_index: 1, data: data)
|
53
|
+
},
|
54
|
+
))
|
55
|
+
[MagicNumber, message, Digest::MD5.digest(message)].pack("A4A*A16")
|
60
56
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
57
|
+
|
58
|
+
def deaggregate(encoded)
|
59
|
+
unless aggregated?(encoded)
|
60
|
+
raise InvalidEncodingError, "Invalid MagicNumber #{encoded[0..3]}}"
|
61
|
+
end
|
62
|
+
message, digest = encoded[4..encoded.length-17], encoded[encoded.length-16..-1]
|
63
|
+
if Digest::MD5.digest(message) != digest
|
64
|
+
raise InvalidEncodingError, "Digest mismatch #{digest}"
|
65
|
+
end
|
66
|
+
decoded = AggregatedRecord.decode(message)
|
67
|
+
records = decoded.records.map(&:data)
|
68
|
+
partition_key = decoded.partition_key_table[1]
|
69
|
+
[records, partition_key]
|
64
70
|
end
|
65
|
-
decoded = AggregatedRecord.decode(message)
|
66
|
-
records = decoded.records.map(&:data)
|
67
|
-
partition_key = decoded.partition_key_table[1]
|
68
|
-
[records, partition_key]
|
69
|
-
end
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
def aggregated?(encoded)
|
73
|
+
encoded[0..3] == MagicNumber
|
74
|
+
end
|
74
75
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
76
|
+
def aggregated_size_offset(partition_key)
|
77
|
+
data = 'd'
|
78
|
+
encoded = aggregate([record(data)], partition_key)
|
79
|
+
finalize(encoded).size - data.size
|
80
|
+
end
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
module Mixin
|
83
|
+
AggregateOffset = 25
|
84
|
+
RecordOffset = 10
|
84
85
|
|
85
|
-
|
86
|
-
|
87
|
-
|
86
|
+
module Params
|
87
|
+
include Fluent::Configurable
|
88
|
+
end
|
88
89
|
|
89
|
-
|
90
|
-
|
91
|
-
|
90
|
+
def self.included(mod)
|
91
|
+
mod.include Params
|
92
|
+
end
|
92
93
|
|
93
|
-
|
94
|
-
|
94
|
+
def aggregator
|
95
|
+
@aggregator ||= Aggregator.new
|
96
|
+
end
|
95
97
|
end
|
96
98
|
end
|
97
99
|
end
|