fluent-plugin-kafka 0.19.7 → 0.19.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a709cad3615bca0c45f0606644142278acbb75b8e43f766dd4a1821a624aa75
4
- data.tar.gz: 269bfdaa2ef8fb7b6cbb254971cc728769aba7215a472a73b34af2c04d735f6f
3
+ metadata.gz: f3e5a24c5a5c936b9d502631d6382faa9315a54f0813e5b636cf025ecb64212c
4
+ data.tar.gz: 43c1b088840c92c330c70c456d92e3849d9de326b6b772c55bae63affea9f3e3
5
5
  SHA512:
6
- metadata.gz: b15df023bd526fb22752da54b460ed12ce9a2d3c35e3ad9959c86d1ba8ac06fb9915799da95135fec3c6911ae776375ee8bc13f4803cad6663e2b60351b302c1
7
- data.tar.gz: f9bfe163282d53f4f583ac905bffd50cab6eeaaaf5da2b9bf7511c7dad064c3aff458252af784ff8a52a3b6b5b63c80d47fccc80677692f58dc8a9fed814e6f4
6
+ metadata.gz: 608800269cb2f306911e4a1a6a6aeea8b7e0e69fde4b34b5a165f67d4a306e82226de890151ffff813a9ba91ef4399d87aeb531243cb63cdf625a023cf92d3e7
7
+ data.tar.gz: e818390d3f8946d0449a80d8c8ddd612e4a96632b2d6765fdcbf3beeb394c0cdc7c95e0cdce8c0780a05e66e39812328ddb48cd32135476cf5a85dac244485ef
@@ -4,3 +4,20 @@ updates:
4
4
  directory: '/'
5
5
  schedule:
6
6
  interval: 'monthly'
7
+ groups:
8
+ # PR: "Security update [package] from [old] to [new]"
9
+ # This PR should be merged in hurry
10
+ security-updates:
11
+ applies-to: security-updates
12
+ patterns:
13
+ - '*'
14
+
15
+ # PR: "Bump [package] from [old] to [new]"
16
+ # No need to be merged this PR in hurry. It is enough to merge
17
+ # once in a month.
18
+ monthly-updates:
19
+ applies-to: version-updates
20
+ patterns:
21
+ - '*'
22
+ # Allow to create PR both of security and normal updates.
23
+ open-pull-requests-limit: 1
@@ -46,8 +46,8 @@ jobs:
46
46
  rdkafka_versions: { min: '>= 0.12.0', max: '= 0.15.2' }
47
47
  name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }} with rdkafka gem version (min ${{ matrix.rdkafka_versions.min }} max ${{ matrix.rdkafka_versions.max }})
48
48
  steps:
49
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
50
- - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
49
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
50
+ - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
51
51
  with:
52
52
  ruby-version: ${{ matrix.ruby }}
53
53
  - name: Install confluent-kafka
@@ -10,7 +10,7 @@ jobs:
10
10
  issues: write
11
11
  pull-requests: write
12
12
  steps:
13
- - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
13
+ - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
14
14
  with:
15
15
  repo-token: ${{ secrets.GITHUB_TOKEN }}
16
16
  days-before-stale: 90
data/ChangeLog CHANGED
@@ -1,3 +1,56 @@
1
+ Release 0.19.8 - 2026/09/04
2
+ * in_kafka: Emit each record with its own tag when `tag_source record` is
3
+ set. The whole fetch batch used to be emitted with the tag of the
4
+ last parsed record, so `tag_source record` never routed anything.
5
+ A record whose tag field is not a non-empty String is now skipped
6
+ with a warning, and the offset advances even when every message of
7
+ a batch is skipped. (#581)
8
+ * in_rdkafka_group: Apply SSL and SASL parameters to the consumer.
9
+ `ssl_ca_cert`, `ssl_client_cert`, `username`, `password`,
10
+ `scram_mechanism` and `principal` were accepted and then discarded,
11
+ so a consumer configured for SSL connected in the clear. NOTE: these
12
+ consumers start negotiating TLS after this change, and passing SASL
13
+ credentials through `kafka_configs` with `security.protocol
14
+ SASL_PLAINTEXT` is now rejected at startup unless `sasl_over_ssl
15
+ false` is set. (#580)
16
+ * out_rdkafka2: Apply `unrecoverable_error_codes` to delivery report
17
+ errors. The check only ran for errors raised by `produce`, so even the
18
+ default `msg_size_too_large` kept the chunk retrying until
19
+ `retry_timeout`. NOTE: such a chunk is now passed to the secondary
20
+ output, or written to the backup directory. (#579)
21
+ * out_rdkafka2: Fix the rdkafka patch version dispatch. The 0_16_0 patch
22
+ had been unreachable since it was added, and rdkafka-ruby 0.12.1
23
+ through 0.13.x got no patch at all, where `producer.close(10)` raises
24
+ ArgumentError on shutdown. Add a patch for 0.13.x. (#578)
25
+ * out_kafka2, out_rdkafka2: Skip an event whose `partition` is not a
26
+ decimal integer within the int32 range, and coerce `message_key` with
27
+ `to_s`. Such an event used to raise outside the per-event guard and
28
+ Fluentd retried the same chunk forever, stalling every source feeding
29
+ the output. NOTE: `partition` is now read as base 10, so "010" means
30
+ 10 rather than 8. (#576, #577)
31
+ * out_rdkafka2: Fix `scram_mechanism` being ignored. SCRAM was silently
32
+ downgraded to PLAIN. `scram_mechanism` is now an enum of the
33
+ mechanisms ruby-kafka accepts, so an unknown value is rejected at
34
+ startup instead of falling back. (#574, #575)
35
+ * out_rdkafka, out_rdkafka2: Fix `ssl_verify_hostname` being ignored, and
36
+ set the SSL flag for any of `ssl_ca_cert`, `ssl_ca_certs_from_system`,
37
+ `ssl_client_cert` and `ssl_client_cert_key`. A mutual TLS setup
38
+ without `ssl_ca_cert` used to connect in the clear. NOTE: these
39
+ configurations start negotiating TLS after this change. (#572, #573)
40
+ * Validate that `ssl_client_cert`, `ssl_client_cert_key`,
41
+ `ssl_client_cert_chain` and `ssl_client_cert_key_password` are set
42
+ together, in every plugin that accepts them. librdkafka used to accept
43
+ a certificate without a key and connect with no client identity.
44
+ NOTE: such a configuration is now rejected at startup. (#571)
45
+ * out_rdkafka2: Add `sasl_over_ssl` parameter and reject SASL credentials
46
+ over a plaintext connection. NOTE: a configuration that sends
47
+ `username` and `password` over `PLAINTEXT` or `SASL_PLAINTEXT` now
48
+ fails at startup. Set `sasl_over_ssl false` only if you deliberately
49
+ keep sending them in the clear. (#570)
50
+ * Replace yajl-ruby with the json gem for JSON handling, and keep
51
+ accepting duplicate keys so that json 3.0 does not raise
52
+ `JSON::ParserError`. (#566, #568)
53
+
1
54
  Release 0.19.7 - 2026/06/02
2
55
  * in_rdkafka_group: support regexp pattern in topics (#541)
3
56
 
data/README.md CHANGED
@@ -166,6 +166,12 @@ With the introduction of the rdkafka-ruby based input plugin we hope to support
166
166
  time_source <source for message timestamp (now|kafka|record)> :default => now
167
167
  time_format <string (Optional when use_record_time is used)>
168
168
 
169
+ # The SSL and SASL parameters in "Common parameters" are mapped to librdkafka
170
+ # properties. Keys given in kafka_configs take precedence over the mapped values.
171
+ # ssl_ca_cert uses only its first entry, and ssl_client_cert_chain is not mapped.
172
+ # When true, SASL authentication requires an SSL connection
173
+ sasl_over_ssl (bool) :default => true
174
+
169
175
  # kafka consumer options
170
176
  max_wait_time_ms 500
171
177
  max_batch_size 10000
@@ -523,6 +529,8 @@ You need to install rdkafka gem.
523
529
  discard_kafka_delivery_failed (bool) :default => false (No discard)
524
530
  discard_kafka_delivery_failed_regex (regexp) :default => nil (No discard)
525
531
  use_event_time (bool) :default => false
532
+ # When true, SASL authentication requires an SSL connection
533
+ sasl_over_ssl (bool) :default => true
526
534
 
527
535
  # same with kafka2
528
536
  headers (hash) :default => {}
@@ -569,6 +577,11 @@ You need to install rdkafka gem.
569
577
  `rdkafka2` supports `discard_kafka_delivery_failed_regex` parameter:
570
578
  - `discard_kafka_delivery_failed_regex` - default: nil - discard the record where the Kafka::DeliveryFailed occurred and the emitted message matches the given regex pattern, such as `/unknown_topic/`.
571
579
 
580
+ `rdkafka2` supports `unrecoverable_error_codes` parameter:
581
+ - `unrecoverable_error_codes` - default: `["topic_authorization_failed", "msg_size_too_large"]` - librdkafka error codes to treat as unrecoverable errors. When a listed code is returned, `Fluent::UnrecoverableError` is raised, so Fluentd hands the whole chunk to the secondary output or to the backup directory instead of retrying it until `retry_timeout` expires. Both the produce call and the delivery report are covered, the latter only while `rdkafka_delivery_handle_poll_timeout` is not 0. Set the parameter to an empty value keeping retrying every error, as this plugin did before v0.19.x.
582
+ - A code is spelled the way `Rdkafka::RdkafkaError#code` reports it, that is the librdkafka name lowercased with the leading underscore removed, so `RD_KAFKA_RESP_ERR__UNKNOWN_PARTITION` is written as `unknown_partition`. A name that matches no error code is accepted and never matches anything.
583
+ - `discard_kafka_delivery_failed` and `discard_kafka_delivery_failed_regex` take precedence over this parameter. The regex is matched against the librdkafka message, such as `Broker: Message size too large (msg_size_too_large)`.
584
+
572
585
  If you use v0.12, use `rdkafka` instead.
573
586
 
574
587
  <match kafka.**>
@@ -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.19.7'
16
+ gem.version = '0.19.8'
17
17
  gem.required_ruby_version = ">= 2.1.0"
18
18
 
19
19
  gem.add_dependency "fluentd", [">= 0.10.58", "< 2"]
@@ -143,9 +143,9 @@ class Fluent::KafkaInput < Fluent::Input
143
143
  r
144
144
  }
145
145
  rescue LoadError
146
- require 'yajl'
146
+ require 'json'
147
147
  Proc.new { |msg, te|
148
- r = Yajl::Parser.parse(msg.value)
148
+ r = JSON.parse(msg.value, allow_duplicate_key: true)
149
149
  add_offset_in_hash(r, te, msg.offset) if @add_offset_in_record
150
150
  r
151
151
  }
@@ -302,18 +302,24 @@ class Fluent::KafkaInput < Fluent::Input
302
302
 
303
303
  return if messages.size.zero?
304
304
 
305
- es = Fluent::MultiEventStream.new
306
- tag = @topic_entry.topic
307
- tag = @add_prefix + "." + tag if @add_prefix
308
- tag = tag + "." + @add_suffix if @add_suffix
305
+ event_streams = {}
306
+ topic_tag = @topic_entry.topic
307
+ topic_tag = @add_prefix + "." + topic_tag if @add_prefix
308
+ topic_tag = topic_tag + "." + @add_suffix if @add_suffix
309
309
 
310
310
  messages.each { |msg|
311
311
  begin
312
312
  record = @parser.call(msg, @topic_entry)
313
313
  if @tag_source == :record
314
314
  tag = record[@record_tag_key]
315
+ unless tag.is_a?(String) && !tag.empty?
316
+ $log.warn "skipped record with invalid tag in #{@topic_entry.topic}/#{@topic_entry.partition}", :key => @record_tag_key, :value => tag, :offset => msg.offset
317
+ next
318
+ end
315
319
  tag = @add_prefix + "." + tag if @add_prefix
316
320
  tag = tag + "." + @add_suffix if @add_suffix
321
+ else
322
+ tag = topic_tag
317
323
  end
318
324
  case @time_source
319
325
  when :kafka
@@ -332,7 +338,8 @@ class Fluent::KafkaInput < Fluent::Input
332
338
  if @kafka_message_key
333
339
  record[@kafka_message_key] = msg.key
334
340
  end
335
- es.add(record_time, record)
341
+ event_streams[tag] ||= Fluent::MultiEventStream.new
342
+ event_streams[tag].add(record_time, record)
336
343
  rescue => e
337
344
  $log.warn "parser error in #{@topic_entry.topic}/#{@topic_entry.partition}", :error => e.to_s, :value => msg.value, :offset => msg.offset
338
345
  $log.debug_backtrace
@@ -340,14 +347,14 @@ class Fluent::KafkaInput < Fluent::Input
340
347
  }
341
348
  offset = messages.last.offset + 1
342
349
 
343
- unless es.empty?
350
+ event_streams.each { |tag, es|
344
351
  @router.emit_stream(tag, es)
352
+ }
345
353
 
346
- if @offset_manager
347
- @offset_manager.save_offset(offset)
348
- end
349
- @next_offset = offset
354
+ if @offset_manager
355
+ @offset_manager.save_offset(offset)
350
356
  end
357
+ @next_offset = offset
351
358
  end
352
359
  end
353
360
 
@@ -160,8 +160,8 @@ class Fluent::KafkaGroupInput < Fluent::Input
160
160
  Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
161
161
  Proc.new { |msg| Oj.load(msg.value) }
162
162
  rescue LoadError
163
- require 'yajl'
164
- Proc.new { |msg| Yajl::Parser.parse(msg.value) }
163
+ require 'json'
164
+ Proc.new { |msg| JSON.parse(msg.value, allow_duplicate_key: true) }
165
165
  end
166
166
  when 'ltsv'
167
167
  require 'ltsv'
@@ -265,7 +265,7 @@ class Fluent::KafkaGroupInput < Fluent::Input
265
265
  end
266
266
 
267
267
  def process_batch_with_record_tag(batch)
268
- es = {}
268
+ es = {}
269
269
  batch.messages.each { |msg|
270
270
  begin
271
271
  record = @parser_proc.call(msg)
@@ -361,7 +361,7 @@ class Fluent::KafkaGroupInput < Fluent::Input
361
361
  if @tag_source == :record
362
362
  process_batch_with_record_tag(batch)
363
363
  else
364
- process_batch(batch)
364
+ process_batch(batch)
365
365
  end
366
366
  }
367
367
  rescue ForShutdown
@@ -56,6 +56,7 @@ class Fluent::Plugin::RdKafkaGroupInput < Fluent::Plugin::Input
56
56
 
57
57
  include Fluent::KafkaPluginUtil::SSLSettings
58
58
  include Fluent::KafkaPluginUtil::SaslSettings
59
+ include Fluent::KafkaPluginUtil::RdkafkaSecuritySettings
59
60
 
60
61
  class ForShutdown < StandardError
61
62
  end
@@ -126,6 +127,8 @@ class Fluent::Plugin::RdKafkaGroupInput < Fluent::Plugin::Input
126
127
  if @time_source == :record and @time_format
127
128
  @time_parser = Fluent::TimeParser.new(@time_format)
128
129
  end
130
+
131
+ @rdkafka_config = build_config
129
132
  end
130
133
 
131
134
  def setup_parser(parser_conf)
@@ -137,8 +140,8 @@ class Fluent::Plugin::RdKafkaGroupInput < Fluent::Plugin::Input
137
140
  Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
138
141
  Proc.new { |msg| Oj.load(msg.payload) }
139
142
  rescue LoadError
140
- require 'yajl'
141
- Proc.new { |msg| Yajl::Parser.parse(msg.payload) }
143
+ require 'json'
144
+ Proc.new { |msg| JSON.parse(msg.payload, allow_duplicate_key: true) }
142
145
  end
143
146
  when 'ltsv'
144
147
  require 'ltsv'
@@ -177,8 +180,17 @@ class Fluent::Plugin::RdKafkaGroupInput < Fluent::Plugin::Input
177
180
  super
178
181
  end
179
182
 
183
+ def build_config
184
+ config = rdkafka_security_config
185
+ @kafka_configs.each { |k, v|
186
+ config[k.to_sym] = v
187
+ }
188
+ validate_sasl_over_ssl(config)
189
+ config
190
+ end
191
+
180
192
  def setup_consumer
181
- consumer = Rdkafka::Config.new(@kafka_configs).consumer
193
+ consumer = Rdkafka::Config.new(@rdkafka_config).consumer
182
194
  consumer.subscribe(*@topics)
183
195
  consumer
184
196
  end
@@ -1,5 +1,24 @@
1
1
  module Fluent
2
2
  module KafkaPluginUtil
3
+ module PartitionSettings
4
+ # -1 is the unassigned partition; the Kafka protocol takes the index as int32
5
+ PARTITION_RANGE = -1..(2**31 - 1)
6
+
7
+ def coerce_partition(partition)
8
+ partition = case partition
9
+ when Integer
10
+ partition
11
+ when String
12
+ Integer(partition, 10)
13
+ else
14
+ raise TypeError, "partition must be an Integer or a decimal String, got #{partition.class}"
15
+ end
16
+ raise RangeError, "partition #{partition} is out of range" unless PARTITION_RANGE.cover?(partition)
17
+
18
+ partition
19
+ end
20
+ end
21
+
3
22
  module AwsIamSettings
4
23
  def self.included(klass)
5
24
  klass.instance_eval do
@@ -36,6 +55,20 @@ module Fluent
36
55
 
37
56
  DummyFormatter = Object.new
38
57
 
58
+ def configure(conf)
59
+ super
60
+
61
+ if @ssl_client_cert && !@ssl_client_cert_key
62
+ raise Fluent::ConfigError, "ssl_client_cert is set but ssl_client_cert_key is not. Please provide both."
63
+ elsif !@ssl_client_cert && @ssl_client_cert_key
64
+ raise Fluent::ConfigError, "ssl_client_cert_key is set but ssl_client_cert is not. Please provide both."
65
+ elsif @ssl_client_cert_chain && !@ssl_client_cert
66
+ raise Fluent::ConfigError, "ssl_client_cert_chain is set but ssl_client_cert is not. Please provide cert, key and chain."
67
+ elsif @ssl_client_cert_key_password && !@ssl_client_cert_key
68
+ raise Fluent::ConfigError, "ssl_client_cert_key_password is set but ssl_client_cert_key is not. Please provide both."
69
+ end
70
+ end
71
+
39
72
  def start
40
73
  super
41
74
 
@@ -75,10 +108,82 @@ module Fluent
75
108
  :desc => "a username when using PLAIN/SCRAM SASL authentication"
76
109
  config_param :password, :string, :default => nil, secret: true,
77
110
  :desc => "a password when using PLAIN/SCRAM SASL authentication"
78
- config_param :scram_mechanism, :string, :default => nil,
111
+ config_param :scram_mechanism, :enum, :list => [:sha256, :sha512], :default => nil,
79
112
  :desc => "if set, use SCRAM authentication with specified mechanism. When unset, default to PLAIN authentication"
80
113
  }
81
114
  end
115
+
116
+ def configure(conf)
117
+ super
118
+
119
+ @scram_mechanism = @scram_mechanism.to_s if @scram_mechanism
120
+ end
121
+ end
122
+
123
+ module RdkafkaSecuritySettings
124
+ SCRAM_MECHANISMS = {
125
+ "sha256" => "SCRAM-SHA-256",
126
+ "sha512" => "SCRAM-SHA-512",
127
+ }.freeze
128
+
129
+ def self.included(klass)
130
+ klass.instance_eval {
131
+ config_param :service_name, :string, :default => nil, :desc => 'Used for sasl.kerberos.service.name'
132
+ config_param :sasl_over_ssl, :bool, :default => true,
133
+ :desc => 'When true, SASL authentication requires an SSL connection'
134
+ }
135
+ end
136
+
137
+ def rdkafka_security_config
138
+ config = {}
139
+
140
+ if (@ssl_ca_cert && @ssl_ca_cert[0]) || @ssl_ca_certs_from_system || @ssl_client_cert || @ssl_client_cert_key
141
+ ssl = true
142
+ config[:"ssl.ca.location"] = @ssl_ca_cert[0] if @ssl_ca_cert && @ssl_ca_cert[0]
143
+ config[:"ssl.certificate.location"] = @ssl_client_cert if @ssl_client_cert
144
+ config[:"ssl.key.location"] = @ssl_client_cert_key if @ssl_client_cert_key
145
+ config[:"ssl.key.password"] = @ssl_client_cert_key_password if @ssl_client_cert_key_password
146
+ config[:"ssl.endpoint.identification.algorithm"] = @ssl_verify_hostname ? "https" : "none"
147
+ config[:"enable.ssl.certificate.verification"] = true
148
+ end
149
+
150
+ if @principal
151
+ sasl = true
152
+ config[:"sasl.mechanisms"] = "GSSAPI"
153
+ config[:"sasl.kerberos.principal"] = @principal
154
+ config[:"sasl.kerberos.service.name"] = @service_name if @service_name
155
+ config[:"sasl.kerberos.keytab"] = @keytab if @keytab
156
+ end
157
+
158
+ if @username && @password
159
+ sasl = true
160
+ config[:"sasl.mechanisms"] = SCRAM_MECHANISMS.fetch(@scram_mechanism, 'PLAIN')
161
+ elsif @scram_mechanism
162
+ log.warn "scram_mechanism is ignored because username and password are not set"
163
+ end
164
+
165
+ if ssl && sasl
166
+ security_protocol = "SASL_SSL"
167
+ elsif ssl && !sasl
168
+ security_protocol = "SSL"
169
+ elsif !ssl && sasl
170
+ security_protocol = "SASL_PLAINTEXT"
171
+ else
172
+ security_protocol = "PLAINTEXT"
173
+ end
174
+ config[:"security.protocol"] = security_protocol
175
+
176
+ config[:"sasl.username"] = @username if @username
177
+ config[:"sasl.password"] = @password if @password
178
+
179
+ config
180
+ end
181
+
182
+ def validate_sasl_over_ssl(config)
183
+ if @sasl_over_ssl && config[:"sasl.password"] && config[:"security.protocol"].to_s.upcase == "SASL_PLAINTEXT"
184
+ raise Fluent::ConfigError, "SASL authentication requires that SSL is configured. Set 'sasl_over_ssl false' to send SASL credentials over a plaintext connection"
185
+ end
186
+ end
82
187
  end
83
188
  end
84
189
  end
@@ -96,7 +96,7 @@ DESC
96
96
  @seed_brokers = []
97
97
  z = Zookeeper.new(@zookeeper)
98
98
  z.get_children(:path => @zookeeper_path)[:children].each do |id|
99
- broker = Yajl.load(z.get(:path => @zookeeper_path + "/#{id}")[:data])
99
+ broker = JSON.parse(z.get(:path => @zookeeper_path + "/#{id}")[:data], allow_duplicate_key: true)
100
100
  if @ssl_client_cert
101
101
  @seed_brokers.push(pickup_ssl_endpoint(broker))
102
102
  else
@@ -192,8 +192,8 @@ DESC
192
192
 
193
193
  def setup_formatter(conf)
194
194
  if @output_data_type == 'json'
195
- require 'yajl'
196
- Proc.new { |tag, time, record| Yajl::Encoder.encode(record) }
195
+ require 'json'
196
+ Proc.new { |tag, time, record| JSON.generate(record) }
197
197
  elsif @output_data_type == 'ltsv'
198
198
  require 'ltsv'
199
199
  Proc.new { |tag, time, record| LTSV.dump(record) }
@@ -98,6 +98,7 @@ DESC
98
98
  include Fluent::KafkaPluginUtil::AwsIamSettings
99
99
  include Fluent::KafkaPluginUtil::SSLSettings
100
100
  include Fluent::KafkaPluginUtil::SaslSettings
101
+ include Fluent::KafkaPluginUtil::PartitionSettings
101
102
 
102
103
  def initialize
103
104
  super
@@ -314,8 +315,8 @@ DESC
314
315
  Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
315
316
  Proc.new { |tag, time, record| Oj.dump(record) }
316
317
  rescue LoadError
317
- require 'yajl'
318
- Proc.new { |tag, time, record| Yajl::Encoder.encode(record) }
318
+ require 'json'
319
+ Proc.new { |tag, time, record| JSON.generate(record) }
319
320
  end
320
321
  when 'ltsv'
321
322
  require 'ltsv'
@@ -351,6 +352,7 @@ DESC
351
352
  partition_key = (@exclude_partition_key ? record.delete(@partition_key_key) : record[@partition_key_key]) || @default_partition_key
352
353
  partition = (@exclude_partition ? record.delete(@partition_key) : record[@partition_key]) || @default_partition
353
354
  message_key = (@exclude_message_key ? record.delete(@message_key_key) : record[@message_key_key]) || @default_message_key
355
+ partition = coerce_partition(partition) unless partition.nil?
354
356
 
355
357
  if mutate_headers
356
358
  headers = base_headers.clone
@@ -119,7 +119,7 @@ DESC
119
119
  @seed_brokers = []
120
120
  z = Zookeeper.new(@zookeeper)
121
121
  z.get_children(:path => @zookeeper_path)[:children].each do |id|
122
- broker = Yajl.load(z.get(:path => @zookeeper_path + "/#{id}")[:data])
122
+ broker = JSON.parse(z.get(:path => @zookeeper_path + "/#{id}")[:data], allow_duplicate_key: true)
123
123
  if @ssl_client_cert
124
124
  @seed_brokers.push(pickup_ssl_endpoint(broker))
125
125
  else
@@ -259,8 +259,8 @@ DESC
259
259
  Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
260
260
  Proc.new { |tag, time, record| Oj.dump(record) }
261
261
  rescue LoadError
262
- require 'yajl'
263
- Proc.new { |tag, time, record| Yajl::Encoder.encode(record) }
262
+ require 'json'
263
+ Proc.new { |tag, time, record| JSON.generate(record) }
264
264
  end
265
265
  elsif @output_data_type == 'ltsv'
266
266
  require 'ltsv'
@@ -130,12 +130,14 @@ DESC
130
130
  :"bootstrap.servers" => @brokers,
131
131
  }
132
132
 
133
- if @ssl_ca_cert && @ssl_ca_cert[0]
133
+ if (@ssl_ca_cert && @ssl_ca_cert[0]) || @ssl_ca_certs_from_system || @ssl_client_cert || @ssl_client_cert_key
134
134
  ssl = true
135
- config[:"ssl.ca.location"] = @ssl_ca_cert[0]
135
+ config[:"ssl.ca.location"] = @ssl_ca_cert[0] if @ssl_ca_cert && @ssl_ca_cert[0]
136
136
  config[:"ssl.certificate.location"] = @ssl_client_cert if @ssl_client_cert
137
137
  config[:"ssl.key.location"] = @ssl_client_cert_key if @ssl_client_cert_key
138
138
  config[:"ssl.key.password"] = @ssl_client_cert_key_password if @ssl_client_cert_key_password
139
+ config[:"ssl.endpoint.identification.algorithm"] = @ssl_verify_hostname ? "https" : "none"
140
+ config[:"enable.ssl.certificate.verification"] = true
139
141
  end
140
142
 
141
143
  if @principal
@@ -228,8 +230,8 @@ DESC
228
230
  Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
229
231
  Proc.new { |tag, time, record| Oj.dump(record) }
230
232
  rescue LoadError
231
- require 'yajl'
232
- Proc.new { |tag, time, record| Yajl::Encoder.encode(record) }
233
+ require 'json'
234
+ Proc.new { |tag, time, record| JSON.generate(record) }
233
235
  end
234
236
  elsif @output_data_type == 'ltsv'
235
237
  require 'ltsv'
@@ -7,14 +7,16 @@ require 'rdkafka'
7
7
 
8
8
  begin
9
9
  rdkafka_version = Gem::Version::create(Rdkafka::VERSION)
10
- if rdkafka_version < Gem::Version.create('0.12.0')
11
- require_relative 'rdkafka_patch/0_11_0'
12
- elsif rdkafka_version == Gem::Version.create('0.12.0')
13
- require_relative 'rdkafka_patch/0_12_0'
10
+ if rdkafka_version >= Gem::Version.create('0.16.0')
11
+ require_relative 'rdkafka_patch/0_16_0'
14
12
  elsif rdkafka_version >= Gem::Version.create('0.14.0')
15
13
  require_relative 'rdkafka_patch/0_14_0'
16
- elsif rdkafka_version >= Gem::Version.create('0.16.0')
17
- require_relative 'rdkafka_patch/0_16_0'
14
+ elsif rdkafka_version >= Gem::Version.create('0.13.0')
15
+ require_relative 'rdkafka_patch/0_13_0'
16
+ elsif rdkafka_version >= Gem::Version.create('0.12.0')
17
+ require_relative 'rdkafka_patch/0_12_0'
18
+ else
19
+ require_relative 'rdkafka_patch/0_11_0'
18
20
  end
19
21
  rescue LoadError, NameError
20
22
  raise "unable to patch rdkafka."
@@ -97,7 +99,6 @@ DESC
97
99
  config_param :enqueue_retry_backoff, :integer, :default => 3
98
100
  config_param :max_enqueue_bytes_per_second, :size, :default => nil, :desc => 'The maximum number of enqueueing bytes per second'
99
101
 
100
- config_param :service_name, :string, :default => nil, :desc => 'Used for sasl.kerberos.service.name'
101
102
  config_param :unrecoverable_error_codes, :array, :default => ["topic_authorization_failed", "msg_size_too_large"],
102
103
  :desc => 'Handle some of the error codes should be unrecoverable if specified'
103
104
 
@@ -111,6 +112,8 @@ DESC
111
112
 
112
113
  include Fluent::KafkaPluginUtil::SSLSettings
113
114
  include Fluent::KafkaPluginUtil::SaslSettings
115
+ include Fluent::KafkaPluginUtil::PartitionSettings
116
+ include Fluent::KafkaPluginUtil::RdkafkaSecuritySettings
114
117
 
115
118
  class EnqueueRate
116
119
  class LimitExceeded < StandardError
@@ -250,39 +253,7 @@ DESC
250
253
  end
251
254
 
252
255
  def build_config
253
- config = {:"bootstrap.servers" => @brokers}
254
-
255
- if @ssl_ca_cert && @ssl_ca_cert[0]
256
- ssl = true
257
- config[:"ssl.ca.location"] = @ssl_ca_cert[0]
258
- config[:"ssl.certificate.location"] = @ssl_client_cert if @ssl_client_cert
259
- config[:"ssl.key.location"] = @ssl_client_cert_key if @ssl_client_cert_key
260
- config[:"ssl.key.password"] = @ssl_client_cert_key_password if @ssl_client_cert_key_password
261
- end
262
-
263
- if @principal
264
- sasl = true
265
- config[:"sasl.mechanisms"] = "GSSAPI"
266
- config[:"sasl.kerberos.principal"] = @principal
267
- config[:"sasl.kerberos.service.name"] = @service_name if @service_name
268
- config[:"sasl.kerberos.keytab"] = @keytab if @keytab
269
- end
270
-
271
- if @username && @password
272
- sasl = true
273
- config[:"sasl.mechanisms"] = 'PLAIN'
274
- end
275
-
276
- if ssl && sasl
277
- security_protocol = "SASL_SSL"
278
- elsif ssl && !sasl
279
- security_protocol = "SSL"
280
- elsif !ssl && sasl
281
- security_protocol = "SASL_PLAINTEXT"
282
- else
283
- security_protocol = "PLAINTEXT"
284
- end
285
- config[:"security.protocol"] = security_protocol
256
+ config = {:"bootstrap.servers" => @brokers}.merge(rdkafka_security_config)
286
257
 
287
258
  config[:"compression.codec"] = @compression_codec if @compression_codec
288
259
  config[:"message.send.max.retries"] = @max_send_retries if @max_send_retries
@@ -292,14 +263,14 @@ DESC
292
263
  config[:"queue.buffering.max.messages"] = @rdkafka_buffering_max_messages if @rdkafka_buffering_max_messages
293
264
  config[:"message.max.bytes"] = @rdkafka_message_max_bytes if @rdkafka_message_max_bytes
294
265
  config[:"batch.num.messages"] = @rdkafka_message_max_num if @rdkafka_message_max_num
295
- config[:"sasl.username"] = @username if @username
296
- config[:"sasl.password"] = @password if @password
297
266
  config[:"enable.idempotence"] = @idempotent if @idempotent
298
267
 
299
268
  @rdkafka_options.each { |k, v|
300
269
  config[k.to_sym] = v
301
270
  }
302
271
 
272
+ validate_sasl_over_ssl(config)
273
+
303
274
  config
304
275
  end
305
276
 
@@ -406,6 +377,8 @@ DESC
406
377
  record.delete(@topic_key) if @exclude_topic_key
407
378
  partition = (@exclude_partition ? record.delete(@partition_key) : record[@partition_key]) || @default_partition
408
379
  message_key = (@exclude_message_key ? record.delete(@message_key_key) : record[@message_key_key]) || @default_message_key
380
+ partition = coerce_partition(partition) unless partition.nil?
381
+ message_key = message_key.to_s unless message_key.nil?
409
382
 
410
383
  @headers_from_record_accessors.each do |key, header_accessor|
411
384
  headers[key] = header_accessor.call(record)
@@ -451,6 +424,7 @@ DESC
451
424
  log.warn "Delivery failed and matched regexp pattern #{@discard_kafka_delivery_failed_regex}. Discard events:", :error => e.to_s, :error_class => e.class.to_s, :tag => tag
452
425
  else
453
426
  log.warn "Send exception occurred: #{e} at #{e.backtrace.first}"
427
+ raise Fluent::UnrecoverableError, "Rejected due to #{e}" if unrecoverable_error?(e)
454
428
  # Raise exception to retry sendind messages
455
429
  raise e
456
430
  end
@@ -507,15 +481,14 @@ DESC
507
481
 
508
482
  raise e
509
483
  else
510
- if unrecoverable_error_codes.include?(e.code.to_s)
511
- # some of the errors should be handled as an unrecoverable error
512
- raise Fluent::UnrecoverableError, "Rejected due to #{e}"
513
- else
514
- raise e
515
- end
484
+ raise e
516
485
  end
517
486
  end
518
487
  end
519
488
  end
489
+
490
+ def unrecoverable_error?(e)
491
+ e.is_a?(Rdkafka::RdkafkaError) && unrecoverable_error_codes.include?(e.code.to_s)
492
+ end
520
493
  end
521
494
  end