fluent-plugin-kafka 0.17.0 → 0.17.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15805411e4029813123b9b636b6faadb937cf38c9841adcd9d998a0f54d8b687
4
- data.tar.gz: 75fef11595c86beb4a54d2d2ff659f77075e328426463f7ef830982c9724ff16
3
+ metadata.gz: 230266adf4ba3d77b8b2fd743863377c4dd2532297f45e4489df62bfcbee1db5
4
+ data.tar.gz: 4b82d2f33bbbb3294f547a7af154783c432c6316f1155a0e3a822361232743e4
5
5
  SHA512:
6
- metadata.gz: db08ffbde4fe36ce38abe6eebf83e6e0dc157bc5d8dd95141ef911d0c5b59074a208c3a8f836e258ffddc34f73fda303006eaef1cd8fc12ae4dd4c79d101d0c7
7
- data.tar.gz: 9b3b098b1bc58654924d50ac8d685af007763942c1cc68187bbd6d39510a27ca31ae795df0de312d97ad67463ae0c428882118640fce052119288e27e3fb0df5
6
+ metadata.gz: 9321cd8bd10dcd603b653c101e6695b7071d7b9dc61fb49bc79248b22cbe9f1db46416c40132243c9862e9c4a3888c818f7a8906906ccf0dda545032f2ac53fd
7
+ data.tar.gz: 832517ac39c4f775d95775454b07bc0f3ecc2f27b1c0bd4c3f8025e795dfbe869d3e349084f868bdb54cfa72188d4cbd96173192f728fae4281f7f84e5d973aa
@@ -5,6 +5,8 @@ on:
5
5
  jobs:
6
6
  build:
7
7
  runs-on: ${{ matrix.os }}
8
+ env:
9
+ USE_RDKAFKA: 1
8
10
  strategy:
9
11
  fail-fast: false
10
12
  matrix:
data/ChangeLog CHANGED
@@ -1,3 +1,8 @@
1
+ Release 0.17.1 - 2021/09/24
2
+ * out_rdkafka/out_rdkafka2: Support rdkafka 0.9.0 or later
3
+ * out_rdkafka/out_rdkafka2: Add `exclude_fields` parameter
4
+ * out_kafka2.rb: Fix one more Ruby 3.0 keyword arguments issue
5
+
1
6
  Release 0.17.0 - 2021/08/30
2
7
  * out_kafka/out_kafka_buffered/out_kafka2: Provide murmur2 partitioner hash function choice
3
8
  * in_kafka/in_kafka_group/out_kafka/out_kafka_buffered/out_kafka2: Use Ruby Kafka's ssl_ca_cert_file_path parameter to feed the CA certs
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in fluent-plugin-kafka.gemspec
4
4
  gemspec
5
+
6
+ gem 'rdkafka', '>= 0.6.0' if ENV["USE_RDKAFKA"]
data/README.md CHANGED
@@ -40,14 +40,14 @@ If you want to use zookeeper related parameters, you also need to install zookee
40
40
 
41
41
  Set path to SSL related files. See [Encryption and Authentication using SSL](https://github.com/zendesk/ruby-kafka#encryption-and-authentication-using-ssl) for more detail.
42
42
 
43
- #### SASL authentication
43
+ #### SASL authentication
44
44
 
45
45
  ##### with GSSAPI
46
46
 
47
47
  - principal
48
48
  - keytab
49
49
 
50
- Set principal and path to keytab for SASL/GSSAPI authentication.
50
+ Set principal and path to keytab for SASL/GSSAPI authentication.
51
51
  See [Authentication using SASL](https://github.com/zendesk/ruby-kafka#authentication-using-sasl) for more details.
52
52
 
53
53
  ##### with Plain/SCRAM
@@ -57,7 +57,7 @@ See [Authentication using SASL](https://github.com/zendesk/ruby-kafka#authentica
57
57
  - scram_mechanism
58
58
  - sasl_over_ssl
59
59
 
60
- Set username, password, scram_mechanism and sasl_over_ssl for SASL/Plain or Scram authentication.
60
+ Set username, password, scram_mechanism and sasl_over_ssl for SASL/Plain or Scram authentication.
61
61
  See [Authentication using SASL](https://github.com/zendesk/ruby-kafka#authentication-using-sasl) for more details.
62
62
 
63
63
  ### Input plugin (@type 'kafka')
@@ -316,6 +316,23 @@ The Kafka message will have a header of source_ip=12.7.0.0.1.
316
316
 
317
317
  The configuration format is jsonpath. It is descibed in https://docs.fluentd.org/plugin-helper-overview/api-plugin-helper-record_accessor
318
318
 
319
+ #### Excluding fields
320
+ Fields can be excluded from output data. Only works for kafka2 and rdkafka2 output plugin.
321
+
322
+ Fields must be specified using an array of dot notation `$.`, for example:
323
+
324
+ <match app.**>
325
+ @type kafka2
326
+ [...]
327
+ exclude_fields $.source.ip,$.HTTP_FOO
328
+ <match>
329
+
330
+ This config can be used to remove fields used on another configs.
331
+
332
+ For example, `$.source.ip` can be extracted with config `headers_from_record` and excluded from message payload.
333
+
334
+ > Using this config to remove unused fields is discouraged. A [filter plugin](https://docs.fluentd.org/v/0.12/filter) can be used for this purpose.
335
+
319
336
  ### Buffered output plugin
320
337
 
321
338
  This plugin uses ruby-kafka producer for writing data. This plugin is for v0.12. If you use v1, see `kafka2`.
@@ -13,7 +13,7 @@ Gem::Specification.new do |gem|
13
13
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
14
  gem.name = "fluent-plugin-kafka"
15
15
  gem.require_paths = ["lib"]
16
- gem.version = '0.17.0'
16
+ gem.version = '0.17.1'
17
17
  gem.required_ruby_version = ">= 2.1.0"
18
18
 
19
19
  gem.add_dependency "fluentd", [">= 0.10.58", "< 2"]
@@ -71,6 +71,7 @@ class Fluent::KafkaInput < Fluent::Input
71
71
  require 'kafka'
72
72
 
73
73
  @time_parser = nil
74
+ @zookeeper = nil
74
75
  end
75
76
 
76
77
  def configure(conf)
@@ -88,6 +88,7 @@ DESC
88
88
  require 'kafka'
89
89
 
90
90
  @kafka = nil
91
+ @field_separator = nil
91
92
  end
92
93
 
93
94
  def refresh_client
@@ -42,6 +42,8 @@ DESC
42
42
  :desc => 'Set true to remove message key from data'
43
43
  config_param :exclude_topic_key, :bool, :default => false,
44
44
  :desc => 'Set true to remove topic name key from data'
45
+ config_param :exclude_fields, :array, :default => [], value_type: :string,
46
+ :desc => 'Fields to remove from data where the value is a jsonpath to a record value'
45
47
  config_param :use_event_time, :bool, :default => false, :desc => 'Use fluentd event time for kafka create_time'
46
48
  config_param :headers, :hash, default: {}, symbolize_keys: true, value_type: :string,
47
49
  :desc => 'Kafka message headers'
@@ -177,6 +179,10 @@ DESC
177
179
  @headers_from_record.each do |key, value|
178
180
  @headers_from_record_accessors[key] = record_accessor_create(value)
179
181
  end
182
+
183
+ @exclude_field_accessors = @exclude_fields.map do |field|
184
+ record_accessor_create(field)
185
+ end
180
186
  end
181
187
 
182
188
  def multi_workers_ready?
@@ -235,7 +241,7 @@ DESC
235
241
  mutate_headers = !@headers_from_record_accessors.empty?
236
242
 
237
243
  begin
238
- producer = @kafka.topic_producer(topic, @producer_opts)
244
+ producer = @kafka.topic_producer(topic, **@producer_opts)
239
245
  chunk.msgpack_each { |time, record|
240
246
  begin
241
247
  record = inject_values_to_record(tag, time, record)
@@ -253,6 +259,12 @@ DESC
253
259
  headers = base_headers
254
260
  end
255
261
 
262
+ unless @exclude_fields.empty?
263
+ @exclude_field_accessors.each do |exclude_field_accessor|
264
+ exclude_field_accessor.delete(record)
265
+ end
266
+ end
267
+
256
268
  record_buf = @formatter_proc.call(tag, time, record)
257
269
  record_buf_bytes = record_buf.bytesize
258
270
  if @max_send_limit_bytes && record_buf_bytes > @max_send_limit_bytes
@@ -107,6 +107,7 @@ DESC
107
107
  @kafka = nil
108
108
  @producers = {}
109
109
  @producers_mutex = Mutex.new
110
+ @field_separator = nil
110
111
  end
111
112
 
112
113
  def multi_workers_ready?
@@ -91,23 +91,29 @@ DESC
91
91
  def configure(conf)
92
92
  super
93
93
  log.instance_eval {
94
- def add(level, &block)
95
- return unless block
94
+ def add(level, message = nil)
95
+ if message.nil?
96
+ if block_given?
97
+ message = yield
98
+ else
99
+ return
100
+ end
101
+ end
96
102
 
97
103
  # Follow rdkakfa's log level. See also rdkafka-ruby's bindings.rb: https://github.com/appsignal/rdkafka-ruby/blob/e5c7261e3f2637554a5c12b924be297d7dca1328/lib/rdkafka/bindings.rb#L117
98
104
  case level
99
105
  when Logger::FATAL
100
- self.fatal(block.call)
106
+ self.fatal(message)
101
107
  when Logger::ERROR
102
- self.error(block.call)
108
+ self.error(message)
103
109
  when Logger::WARN
104
- self.warn(block.call)
110
+ self.warn(message)
105
111
  when Logger::INFO
106
- self.info(block.call)
112
+ self.info(message)
107
113
  when Logger::DEBUG
108
- self.debug(block.call)
114
+ self.debug(message)
109
115
  else
110
- self.trace(block.call)
116
+ self.trace(message)
111
117
  end
112
118
  end
113
119
  }
@@ -56,6 +56,8 @@ DESC
56
56
  :desc => <<-DESC
57
57
  Set true to remove topic key from data
58
58
  DESC
59
+ config_param :exclude_fields, :array, :default => [], value_type: :string,
60
+ :desc => 'Fields to remove from data where the value is a jsonpath to a record value'
59
61
  config_param :headers, :hash, default: {}, symbolize_keys: true, value_type: :string,
60
62
  :desc => 'Kafka message headers'
61
63
  config_param :headers_from_record, :hash, default: {}, symbolize_keys: true, value_type: :string,
@@ -110,23 +112,29 @@ DESC
110
112
  def configure(conf)
111
113
  super
112
114
  log.instance_eval {
113
- def add(level, &block)
114
- return unless block
115
+ def add(level, message = nil)
116
+ if message.nil?
117
+ if block_given?
118
+ message = yield
119
+ else
120
+ return
121
+ end
122
+ end
115
123
 
116
124
  # Follow rdkakfa's log level. See also rdkafka-ruby's bindings.rb: https://github.com/appsignal/rdkafka-ruby/blob/e5c7261e3f2637554a5c12b924be297d7dca1328/lib/rdkafka/bindings.rb#L117
117
125
  case level
118
126
  when Logger::FATAL
119
- self.fatal(block.call)
127
+ self.fatal(message)
120
128
  when Logger::ERROR
121
- self.error(block.call)
129
+ self.error(message)
122
130
  when Logger::WARN
123
- self.warn(block.call)
131
+ self.warn(message)
124
132
  when Logger::INFO
125
- self.info(block.call)
133
+ self.info(message)
126
134
  when Logger::DEBUG
127
- self.debug(block.call)
135
+ self.debug(message)
128
136
  else
129
- self.trace(block.call)
137
+ self.trace(message)
130
138
  end
131
139
  end
132
140
  }
@@ -158,6 +166,10 @@ DESC
158
166
  @headers_from_record.each do |key, value|
159
167
  @headers_from_record_accessors[key] = record_accessor_create(value)
160
168
  end
169
+
170
+ @exclude_field_accessors = @exclude_fields.map do |field|
171
+ record_accessor_create(field)
172
+ end
161
173
  end
162
174
 
163
175
  def build_config
@@ -305,6 +317,12 @@ DESC
305
317
  headers[key] = header_accessor.call(record)
306
318
  end
307
319
 
320
+ unless @exclude_fields.empty?
321
+ @exclude_field_accessors.each do |exclude_field_acessor|
322
+ exclude_field_acessor.delete(record)
323
+ end
324
+ end
325
+
308
326
  record_buf = @formatter_proc.call(tag, time, record)
309
327
  record_buf_bytes = record_buf.bytesize
310
328
  if @max_send_limit_bytes && record_buf_bytes > @max_send_limit_bytes
@@ -1,6 +1,8 @@
1
1
  require 'helper'
2
2
  require 'fluent/test/helpers'
3
- require 'fluent/output'
3
+ require 'fluent/test/driver/input'
4
+ require 'fluent/test/driver/output'
5
+ require 'securerandom'
4
6
 
5
7
  class Kafka2OutputTest < Test::Unit::TestCase
6
8
  include Fluent::Test::Helpers
@@ -15,8 +17,8 @@ class Kafka2OutputTest < Test::Unit::TestCase
15
17
  ])
16
18
  end
17
19
 
18
- def config
19
- base_config + config_element('ROOT', '', {"default_topic" => "kitagawakeiko",
20
+ def config(default_topic: "kitagawakeiko")
21
+ base_config + config_element('ROOT', '', {"default_topic" => default_topic,
20
22
  "brokers" => "localhost:9092"}, [
21
23
  ])
22
24
  end
@@ -57,4 +59,58 @@ class Kafka2OutputTest < Test::Unit::TestCase
57
59
  d = create_driver
58
60
  assert_equal true, d.instance.multi_workers_ready?
59
61
  end
62
+
63
+ class WriteTest < self
64
+ TOPIC_NAME = "kafka-output-#{SecureRandom.uuid}"
65
+
66
+ INPUT_CONFIG = %[
67
+ @type kafka
68
+ brokers localhost:9092
69
+ format json
70
+ @label @kafka
71
+ topics #{TOPIC_NAME}
72
+ ]
73
+
74
+ def create_target_driver(conf = INPUT_CONFIG)
75
+ Fluent::Test::Driver::Input.new(Fluent::KafkaInput).configure(conf)
76
+ end
77
+
78
+ def setup
79
+ @kafka = Kafka.new(["localhost:9092"], client_id: 'kafka')
80
+ end
81
+
82
+ def teardown
83
+ @kafka.delete_topic(TOPIC_NAME)
84
+ @kafka.close
85
+ end
86
+
87
+ def test_write
88
+ target_driver = create_target_driver
89
+ expected_message = {"a" => 2}
90
+ target_driver.run(expect_records: 1, timeout: 5) do
91
+ sleep 2
92
+ d = create_driver(config(default_topic: TOPIC_NAME))
93
+ d.run do
94
+ d.feed("test", event_time, expected_message)
95
+ end
96
+ end
97
+ actual_messages = target_driver.events.collect { |event| event[2] }
98
+ assert_equal([expected_message], actual_messages)
99
+ end
100
+
101
+ def test_exclude_fields
102
+ conf = config(default_topic: TOPIC_NAME) +
103
+ config_element('ROOT', '', {"exclude_fields" => "$.foo"}, [])
104
+ target_driver = create_target_driver
105
+ target_driver.run(expect_records: 1, timeout: 5) do
106
+ sleep 2
107
+ d = create_driver(conf)
108
+ d.run do
109
+ d.feed('test', event_time, {'a' => 'b', 'foo' => 'bar', 'message' => 'test'})
110
+ end
111
+ end
112
+ actual_messages = target_driver.events.collect { |event| event[2] }
113
+ assert_equal([{'a' => 'b', 'message' => 'test'}], actual_messages)
114
+ end
115
+ end
60
116
  end
@@ -0,0 +1,120 @@
1
+ require 'helper'
2
+ require 'fluent/test/helpers'
3
+ require 'fluent/test/driver/input'
4
+ require 'fluent/test/driver/output'
5
+ require 'securerandom'
6
+
7
+ class Rdkafka2OutputTest < Test::Unit::TestCase
8
+ include Fluent::Test::Helpers
9
+
10
+ def have_rdkafka
11
+ begin
12
+ require 'fluent/plugin/out_rdkafka2'
13
+ true
14
+ rescue LoadError
15
+ false
16
+ end
17
+ end
18
+
19
+ def setup
20
+ omit_unless(have_rdkafka, "rdkafka isn't installed")
21
+ Fluent::Test.setup
22
+ end
23
+
24
+ def base_config
25
+ config_element('ROOT', '', {"@type" => "rdkafka2"}, [
26
+ config_element('format', "", {"@type" => "json"})
27
+ ])
28
+ end
29
+
30
+ def config(default_topic: "kitagawakeiko")
31
+ base_config + config_element('ROOT', '', {"default_topic" => default_topic,
32
+ "brokers" => "localhost:9092"}, [
33
+ ])
34
+ end
35
+
36
+ def create_driver(conf = config, tag='test')
37
+ Fluent::Test::Driver::Output.new(Fluent::Rdkafka2Output).configure(conf)
38
+ end
39
+
40
+ def test_configure
41
+ assert_nothing_raised(Fluent::ConfigError) {
42
+ create_driver(base_config)
43
+ }
44
+
45
+ assert_nothing_raised(Fluent::ConfigError) {
46
+ create_driver(config)
47
+ }
48
+
49
+ assert_nothing_raised(Fluent::ConfigError) {
50
+ create_driver(config + config_element('buffer', "", {"@type" => "memory"}))
51
+ }
52
+
53
+ d = create_driver
54
+ assert_equal 'kitagawakeiko', d.instance.default_topic
55
+ assert_equal 'localhost:9092', d.instance.brokers
56
+ end
57
+
58
+ def test_mutli_worker_support
59
+ d = create_driver
60
+ assert_equal true, d.instance.multi_workers_ready?
61
+ end
62
+
63
+ class WriteTest < self
64
+ TOPIC_NAME = "kafka-output-#{SecureRandom.uuid}"
65
+
66
+ INPUT_CONFIG = %[
67
+ @type kafka
68
+ brokers localhost:9092
69
+ format json
70
+ @label @kafka
71
+ topics #{TOPIC_NAME}
72
+ ]
73
+
74
+ def create_target_driver(conf = INPUT_CONFIG)
75
+ Fluent::Test::Driver::Input.new(Fluent::KafkaInput).configure(conf)
76
+ end
77
+
78
+ def setup
79
+ @kafka = nil
80
+ omit_unless(have_rdkafka, "rdkafka isn't installed")
81
+ @kafka = Kafka.new(["localhost:9092"], client_id: 'kafka')
82
+ end
83
+
84
+ def teardown
85
+ if @kafka
86
+ @kafka.delete_topic(TOPIC_NAME)
87
+ @kafka.close
88
+ end
89
+ end
90
+
91
+ def test_write
92
+ target_driver = create_target_driver
93
+ expected_message = {"a" => 2}
94
+ target_driver.run(expect_records: 1, timeout: 5) do
95
+ sleep 2
96
+ d = create_driver(config(default_topic: TOPIC_NAME))
97
+ d.run do
98
+ d.feed("test", event_time, expected_message)
99
+ end
100
+ end
101
+ actual_messages = target_driver.events.collect { |event| event[2] }
102
+ assert_equal([expected_message], actual_messages)
103
+ end
104
+
105
+ def test_exclude_fields
106
+ conf = config(default_topic: TOPIC_NAME) +
107
+ config_element('ROOT', '', {"exclude_fields" => "$.foo"}, [])
108
+ target_driver = create_target_driver
109
+ target_driver.run(expect_records: 1, timeout: 5) do
110
+ sleep 2
111
+ d = create_driver(conf)
112
+ d.run do
113
+ d.feed('test', event_time, {'a' => 'b', 'foo' => 'bar', 'message' => 'test'})
114
+ end
115
+ end
116
+ actual_messages = target_driver.events.collect { |event| event[2] }
117
+ assert_equal([{'a' => 'b', 'message' => 'test'}], actual_messages)
118
+ end
119
+ end
120
+ end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-kafka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.17.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hidemasa Togashi
8
8
  - Masahiro Nakagawa
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-08-30 00:00:00.000000000 Z
12
+ date: 2021-09-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fluentd
@@ -173,11 +173,12 @@ files:
173
173
  - test/plugin/test_out_kafka.rb
174
174
  - test/plugin/test_out_kafka2.rb
175
175
  - test/plugin/test_out_kafka_buffered.rb
176
+ - test/plugin/test_out_rdkafka2.rb
176
177
  homepage: https://github.com/fluent/fluent-plugin-kafka
177
178
  licenses:
178
179
  - Apache-2.0
179
180
  metadata: {}
180
- post_install_message:
181
+ post_install_message:
181
182
  rdoc_options: []
182
183
  require_paths:
183
184
  - lib
@@ -192,8 +193,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
193
  - !ruby/object:Gem::Version
193
194
  version: '0'
194
195
  requirements: []
195
- rubygems_version: 3.1.4
196
- signing_key:
196
+ rubygems_version: 3.2.5
197
+ signing_key:
197
198
  specification_version: 4
198
199
  summary: Fluentd plugin for Apache Kafka > 0.8
199
200
  test_files:
@@ -204,3 +205,4 @@ test_files:
204
205
  - test/plugin/test_out_kafka.rb
205
206
  - test/plugin/test_out_kafka2.rb
206
207
  - test/plugin/test_out_kafka_buffered.rb
208
+ - test/plugin/test_out_rdkafka2.rb