fluent-plugin-kinesis 2.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|