fluent-plugin-kafka 0.19.7 → 0.19.9

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: 2eeb3b37a48db8cde6fda0eb9e413f507076c231abd06456a35540a10eed5667
4
+ data.tar.gz: 90b95493d3445829123ee569af66f581b109dcea7f91cf3d9aee6b2b6b9caeab
5
5
  SHA512:
6
- metadata.gz: b15df023bd526fb22752da54b460ed12ce9a2d3c35e3ad9959c86d1ba8ac06fb9915799da95135fec3c6911ae776375ee8bc13f4803cad6663e2b60351b302c1
7
- data.tar.gz: f9bfe163282d53f4f583ac905bffd50cab6eeaaaf5da2b9bf7511c7dad064c3aff458252af784ff8a52a3b6b5b63c80d47fccc80677692f58dc8a9fed814e6f4
6
+ metadata.gz: 3abf77244f8a7b239e92f63c798f0dc70b0d078a3b75de8f17ddc9e7cb17a8108088cc4f4bc3e44005496c68c89e960ca727def67cb7418f8e001bb713c938b4
7
+ data.tar.gz: 892d0c09e810b9fe78f87917a8468ac392b206991e99c4080f755e80953db79d8637e41717ef3ae0acbf13baa26e2fd1fe032bef7650ca70bc62df44b1dead7a
@@ -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
@@ -11,7 +11,7 @@ jobs:
11
11
  uses: ruby/actions/.github/workflows/ruby_versions.yml@master
12
12
  with:
13
13
  engine: cruby
14
- min_version: 3.0
14
+ min_version: 3.2
15
15
  build:
16
16
  needs: ruby-versions
17
17
  runs-on: ${{ matrix.os }}
@@ -29,25 +29,12 @@ jobs:
29
29
  - { min: '>= 0.14.0', max: '< 0.16.0' }
30
30
  - { min: '>= 0.16.0', max: '< 0.25.0' }
31
31
  - { min: '>= 0.25.0', max: '>= 0.25.0' }
32
- # rdkafka 0.25.0 supports Ruby 3.2+, so we exclude it for Ruby 3.0 and 3.1
33
32
  exclude:
34
33
  - ruby: head
35
- - ruby: '3.1'
36
- rdkafka_versions: { min: '>= 0.25.0', max: '>= 0.25.0' }
37
- - ruby: '3.0'
38
- rdkafka_versions: { min: '>= 0.25.0', max: '>= 0.25.0' }
39
- # rdkafka 0.15.2 is the last version which supports Ruby 2.7
40
- include:
41
- - ruby: '2.7'
42
- os: ubuntu-latest
43
- rdkafka_versions: { min: '>= 0.6.0', max: '< 0.12.0' }
44
- - ruby: '2.7'
45
- os: ubuntu-latest
46
- rdkafka_versions: { min: '>= 0.12.0', max: '= 0.15.2' }
47
34
  name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }} with rdkafka gem version (min ${{ matrix.rdkafka_versions.min }} max ${{ matrix.rdkafka_versions.max }})
48
35
  steps:
49
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
50
- - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
36
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
37
+ - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
51
38
  with:
52
39
  ruby-version: ${{ matrix.ruby }}
53
40
  - 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,78 @@
1
+ Release 0.19.9 - 2026/10/30
2
+ * out_kafka2: Fix duplicated records and failing writes with
3
+ `share_producer true`. All flush threads used one ruby-kafka producer
4
+ at the same time, although it is not thread safe. NOTE: only one flush
5
+ thread at a time now uses the shared producer, so chunks are no longer
6
+ sent in parallel even if `flush_thread_count` is 2 or more. Keep
7
+ `share_producer false` to send them in parallel. (#589)
8
+ * out_kafka2: Fix later writes failing until restart after a delivery
9
+ error. Since 0.18.0, the messages of a failed delivery stayed in the
10
+ producer, so later writes could keep failing with
11
+ `Kafka::MessageSizeTooLarge`. The producer is now discarded after a
12
+ failure and created again on the next write. (#588, #589)
13
+ * in_kafka: Fix endless re-fetching of a batch with a record larger than
14
+ `chunk_limit_size`. The offset did not advance, and the records that
15
+ did fit were emitted again on every `interval`. NOTE: such a record is
16
+ now dropped with a warning. Use `<label @ERROR>` to keep it. (#584)
17
+ * in_kafka_group: Fix endless consumer restarts on a record larger than
18
+ `chunk_limit_size`. The consumer restarted without committing offsets,
19
+ so the same batch was fetched forever, and the records that did fit
20
+ were emitted again each time. NOTE: such a record is now dropped with a
21
+ warning. Use `<label @ERROR>` to keep it. (#583)
22
+
23
+ Release 0.19.8 - 2026/09/04
24
+ * in_kafka: Emit each record with its own tag when `tag_source record` is
25
+ set. The whole fetch batch used to be emitted with the tag of the
26
+ last parsed record, so `tag_source record` never routed anything.
27
+ A record whose tag field is not a non-empty String is now skipped
28
+ with a warning, and the offset advances even when every message of
29
+ a batch is skipped. (#581)
30
+ * in_rdkafka_group: Apply SSL and SASL parameters to the consumer.
31
+ `ssl_ca_cert`, `ssl_client_cert`, `username`, `password`,
32
+ `scram_mechanism` and `principal` were accepted and then discarded,
33
+ so a consumer configured for SSL connected in the clear. NOTE: these
34
+ consumers start negotiating TLS after this change, and passing SASL
35
+ credentials through `kafka_configs` with `security.protocol
36
+ SASL_PLAINTEXT` is now rejected at startup unless `sasl_over_ssl
37
+ false` is set. (#580)
38
+ * out_rdkafka2: Apply `unrecoverable_error_codes` to delivery report
39
+ errors. The check only ran for errors raised by `produce`, so even the
40
+ default `msg_size_too_large` kept the chunk retrying until
41
+ `retry_timeout`. NOTE: such a chunk is now passed to the secondary
42
+ output, or written to the backup directory. (#579)
43
+ * out_rdkafka2: Fix the rdkafka patch version dispatch. The 0_16_0 patch
44
+ had been unreachable since it was added, and rdkafka-ruby 0.12.1
45
+ through 0.13.x got no patch at all, where `producer.close(10)` raises
46
+ ArgumentError on shutdown. Add a patch for 0.13.x. (#578)
47
+ * out_kafka2, out_rdkafka2: Skip an event whose `partition` is not a
48
+ decimal integer within the int32 range, and coerce `message_key` with
49
+ `to_s`. Such an event used to raise outside the per-event guard and
50
+ Fluentd retried the same chunk forever, stalling every source feeding
51
+ the output. NOTE: `partition` is now read as base 10, so "010" means
52
+ 10 rather than 8. (#576, #577)
53
+ * out_rdkafka2: Fix `scram_mechanism` being ignored. SCRAM was silently
54
+ downgraded to PLAIN. `scram_mechanism` is now an enum of the
55
+ mechanisms ruby-kafka accepts, so an unknown value is rejected at
56
+ startup instead of falling back. (#574, #575)
57
+ * out_rdkafka, out_rdkafka2: Fix `ssl_verify_hostname` being ignored, and
58
+ set the SSL flag for any of `ssl_ca_cert`, `ssl_ca_certs_from_system`,
59
+ `ssl_client_cert` and `ssl_client_cert_key`. A mutual TLS setup
60
+ without `ssl_ca_cert` used to connect in the clear. NOTE: these
61
+ configurations start negotiating TLS after this change. (#572, #573)
62
+ * Validate that `ssl_client_cert`, `ssl_client_cert_key`,
63
+ `ssl_client_cert_chain` and `ssl_client_cert_key_password` are set
64
+ together, in every plugin that accepts them. librdkafka used to accept
65
+ a certificate without a key and connect with no client identity.
66
+ NOTE: such a configuration is now rejected at startup. (#571)
67
+ * out_rdkafka2: Add `sasl_over_ssl` parameter and reject SASL credentials
68
+ over a plaintext connection. NOTE: a configuration that sends
69
+ `username` and `password` over `PLAINTEXT` or `SASL_PLAINTEXT` now
70
+ fails at startup. Set `sasl_over_ssl false` only if you deliberately
71
+ keep sending them in the clear. (#570)
72
+ * Replace yajl-ruby with the json gem for JSON handling, and keep
73
+ accepting duplicate keys so that json 3.0 does not raise
74
+ `JSON::ParserError`. (#566, #568)
75
+
1
76
  Release 0.19.7 - 2026/06/02
2
77
  * in_rdkafka_group: support regexp pattern in topics (#541)
3
78
 
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
@@ -257,6 +263,8 @@ The `<formatter name>` in `<format>` uses fluentd's formatter plugins. See [form
257
263
 
258
264
  **Note:** Java based Kafka client uses `murmur2` as partitioner function by default. If you want to use same partitioning behavior with fluent-plugin-kafka, change it to `murmur2` instead of `crc32`. Note that for using `murmur2` hash partitioner function, you must install `digest-murmurhash` gem.
259
265
 
266
+ **Note:** `share_producer true` makes all flush threads use one ruby-kafka producer. This reduces the connections to the brokers. However, the ruby-kafka producer is not thread safe, so the flush threads send chunks one at a time. To send chunks in parallel with `flush_thread_count`, keep `share_producer` false. `rdkafka2` can share its producer without this limit, because librdkafka is thread safe.
267
+
260
268
  ruby-kafka sometimes returns `Kafka::DeliveryFailed` error without good information.
261
269
  In this case, `get_kafka_client_log` is useful for identifying the error cause.
262
270
  ruby-kafka's log is routed to fluentd log so you can see ruby-kafka's log in fluentd logs.
@@ -267,7 +275,7 @@ Supports following ruby-kafka's producer options.
267
275
  - required_acks - default: -1 - The number of acks required per request. If you need flush performance, set lower value, e.g. 1, 2.
268
276
  - ack_timeout - default: nil - How long the producer waits for acks. The unit is seconds.
269
277
  - compression_codec - default: nil - The codec the producer uses to compress messages.
270
- - max_send_limit_bytes - default: nil - Max byte size to send message to avoid MessageSizeTooLarge. For example, if you set 1000000(message.max.bytes in kafka), Message more than 1000000 byes will be dropped.
278
+ - max_send_limit_bytes - default: nil - Max byte size of one record after formatting. A larger record is dropped with a warning, and the other records in the chunk are sent. For example, if you set 1000000, a record larger than 1000000 bytes is dropped. This does not limit the size of a record batch, so it does not prevent MessageSizeTooLarge by itself. See [FAQ](#why-does-fluentd-fail-with-kafkamessagesizetoolarge-or-msg_size_too_large).
271
279
  - discard_kafka_delivery_failed - default: false - discard the record where [Kafka::DeliveryFailed](http://www.rubydoc.info/gems/ruby-kafka/Kafka/DeliveryFailed) occurred
272
280
 
273
281
  If you want to know about detail of monitoring, see also https://github.com/zendesk/ruby-kafka#monitoring
@@ -445,7 +453,7 @@ Support of fluentd v0.12 has ended. `kafka_buffered` will be an alias of `kafka2
445
453
  - required_acks - default: -1 - The number of acks required per request. If you need flush performance, set lower value, e.g. 1, 2.
446
454
  - ack_timeout - default: nil - How long the producer waits for acks. The unit is seconds.
447
455
  - compression_codec - default: nil - The codec the producer uses to compress messages.
448
- - max_send_limit_bytes - default: nil - Max byte size to send message to avoid MessageSizeTooLarge. For example, if you set 1000000(message.max.bytes in kafka), Message more than 1000000 byes will be dropped.
456
+ - max_send_limit_bytes - default: nil - Max byte size of one record after formatting. A larger record is dropped with a warning, and the other records in the chunk are sent. For example, if you set 1000000, a record larger than 1000000 bytes is dropped. This does not limit the size of a record batch, so it does not prevent MessageSizeTooLarge by itself. See [FAQ](#why-does-fluentd-fail-with-kafkamessagesizetoolarge-or-msg_size_too_large).
449
457
  - discard_kafka_delivery_failed - default: false - discard the record where [Kafka::DeliveryFailed](http://www.rubydoc.info/gems/ruby-kafka/Kafka/DeliveryFailed) occurred
450
458
  - monitoring_list - default: [] - library to be used to monitor. statsd and datadog are supported
451
459
 
@@ -523,6 +531,8 @@ You need to install rdkafka gem.
523
531
  discard_kafka_delivery_failed (bool) :default => false (No discard)
524
532
  discard_kafka_delivery_failed_regex (regexp) :default => nil (No discard)
525
533
  use_event_time (bool) :default => false
534
+ # When true, SASL authentication requires an SSL connection
535
+ sasl_over_ssl (bool) :default => true
526
536
 
527
537
  # same with kafka2
528
538
  headers (hash) :default => {}
@@ -556,7 +566,8 @@ You need to install rdkafka gem.
556
566
  share_producer (bool) :default => false
557
567
  # Timeout for polling message wait. If 0, no wait.
558
568
  rdkafka_delivery_handle_poll_timeout (integer) :default => 30
559
- # If the record size is larger than this value, such records are ignored. Default is no limit
569
+ # Max byte size of one record after formatting. Larger records are dropped.
570
+ # This does not limit the size of a record batch. Default is no limit
560
571
  max_send_limit_bytes (integer) :default => nil
561
572
  # The maximum number of enqueueing bytes per second. It can reduce the
562
573
  # load of both Fluentd and Kafka when excessive messages are attempted
@@ -569,6 +580,11 @@ You need to install rdkafka gem.
569
580
  `rdkafka2` supports `discard_kafka_delivery_failed_regex` parameter:
570
581
  - `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
582
 
583
+ `rdkafka2` supports `unrecoverable_error_codes` parameter:
584
+ - `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.
585
+ - 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.
586
+ - `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)`.
587
+
572
588
  If you use v0.12, use `rdkafka` instead.
573
589
 
574
590
  <match kafka.**>
@@ -595,6 +611,40 @@ To avoid the problem, there are 2 approaches:
595
611
  - Upgrade your kafka cluster to latest version. This is better because recent version is faster and robust.
596
612
  - Downgrade ruby-kafka/fluent-plugin-kafka to work with your older kafka.
597
613
 
614
+ ### Why does Fluentd fail with Kafka::MessageSizeTooLarge (or msg_size_too_large)?
615
+
616
+ Kafka rejects a record batch that is larger than the size limit of the topic. The limit is `max.message.bytes` of the topic if it is set, and `message.max.bytes` of the broker otherwise. The topic value can be larger or smaller than the broker value. Below, "the topic limit" means this value. If one output writes to several topics, use the smallest one.
617
+
618
+ `kafka2` sends all records of a buffer chunk at once, and the records for the same partition go into one record batch. In the worst case, the whole chunk becomes one record batch. To make the batches smaller, lower `chunk_limit_size` in `<buffer>`.
619
+
620
+ `chunk_limit_size` is the size of a chunk in the buffer, where records are kept as msgpack. The records sent to Kafka can be larger, because of `<format>`, `<inject>` and the overhead of each Kafka record. Start with about half of the topic limit, and lower it if the error continues. If `<buffer>` has `compress gzip` or `compress zstd`, `chunk_limit_size` limits the compressed size, and a chunk can hold several times more data. In that case, divide it further by the compression ratio of your data. `compression_codec` works the other way. Kafka checks the size of a record batch after compression, so compressed batches leave more room. How much depends on how well your data compresses.
621
+
622
+ `max_send_limit_bytes` does not make the batch smaller. It checks only the formatted value of each record, and drops the records larger than this setting. The message key, headers and the overhead of the batch are not counted. To drop a single record that Kafka would reject, set it below the topic limit with some room for them.
623
+
624
+ The following example assumes a topic limit of 1MB.
625
+
626
+ <match app.**>
627
+ @type kafka2
628
+ brokers <broker1_host>:<broker1_port>
629
+ default_topic app
630
+
631
+ max_send_limit_bytes 1000000
632
+
633
+ <format>
634
+ @type json
635
+ </format>
636
+
637
+ <buffer topic>
638
+ chunk_limit_size 512k
639
+ </buffer>
640
+ </match>
641
+
642
+ Fluentd may log `chunk bytes limit exceeds for an emitted event stream`. This warning comes from the Fluentd buffer. It means that one event stream emitted to the output was larger than `chunk_limit_size`, and Fluentd splits it into several chunks. The number in the warning is the size of that event stream in the buffer, not the growth by formatting. If a single record is larger than `chunk_limit_size`, that record cannot be stored, and Fluentd raises `BufferChunkOverflowError`.
643
+
644
+ `kafka_buffered` splits a chunk into smaller deliveries by `kafka_agg_max_bytes` (default: 4096) and `kafka_agg_max_messages`, so `chunk_limit_size` does not decide its batch size.
645
+
646
+ `rdkafka2` passes records to librdkafka one by one, and librdkafka builds the record batches within its own `batch.size` and `message.max.bytes`. You can set them in `rdkafka_options`. Keep them below the topic limit. `msg_size_too_large` is in the default of `unrecoverable_error_codes`, so Fluentd does not retry a chunk that hits it.
647
+
598
648
  ## Contributing
599
649
 
600
650
  1. Fork it
@@ -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.9'
17
17
  gem.required_ruby_version = ">= 2.1.0"
18
18
 
19
19
  gem.add_dependency "fluentd", [">= 0.10.58", "< 2"]
@@ -62,6 +62,12 @@ class Fluent::KafkaInput < Fluent::Input
62
62
  include Fluent::KafkaPluginUtil::SSLSettings
63
63
  include Fluent::KafkaPluginUtil::SaslSettings
64
64
 
65
+ BufferChunkOverflowError = if defined?(Fluent::Plugin::Buffer::BufferChunkOverflowError)
66
+ Fluent::Plugin::Buffer::BufferChunkOverflowError
67
+ else
68
+ Fluent::BufferChunkLimitError
69
+ end
70
+
65
71
  unless method_defined?(:router)
66
72
  define_method("router") { Fluent::Engine }
67
73
  end
@@ -143,9 +149,9 @@ class Fluent::KafkaInput < Fluent::Input
143
149
  r
144
150
  }
145
151
  rescue LoadError
146
- require 'yajl'
152
+ require 'json'
147
153
  Proc.new { |msg, te|
148
- r = Yajl::Parser.parse(msg.value)
154
+ r = JSON.parse(msg.value, allow_duplicate_key: true)
149
155
  add_offset_in_hash(r, te, msg.offset) if @add_offset_in_record
150
156
  r
151
157
  }
@@ -302,18 +308,24 @@ class Fluent::KafkaInput < Fluent::Input
302
308
 
303
309
  return if messages.size.zero?
304
310
 
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
311
+ event_streams = {}
312
+ topic_tag = @topic_entry.topic
313
+ topic_tag = @add_prefix + "." + topic_tag if @add_prefix
314
+ topic_tag = topic_tag + "." + @add_suffix if @add_suffix
309
315
 
310
316
  messages.each { |msg|
311
317
  begin
312
318
  record = @parser.call(msg, @topic_entry)
313
319
  if @tag_source == :record
314
320
  tag = record[@record_tag_key]
321
+ unless tag.is_a?(String) && !tag.empty?
322
+ $log.warn "skipped record with invalid tag in #{@topic_entry.topic}/#{@topic_entry.partition}", :key => @record_tag_key, :value => tag, :offset => msg.offset
323
+ next
324
+ end
315
325
  tag = @add_prefix + "." + tag if @add_prefix
316
326
  tag = tag + "." + @add_suffix if @add_suffix
327
+ else
328
+ tag = topic_tag
317
329
  end
318
330
  case @time_source
319
331
  when :kafka
@@ -332,7 +344,8 @@ class Fluent::KafkaInput < Fluent::Input
332
344
  if @kafka_message_key
333
345
  record[@kafka_message_key] = msg.key
334
346
  end
335
- es.add(record_time, record)
347
+ event_streams[tag] ||= Fluent::MultiEventStream.new
348
+ event_streams[tag].add(record_time, record)
336
349
  rescue => e
337
350
  $log.warn "parser error in #{@topic_entry.topic}/#{@topic_entry.partition}", :error => e.to_s, :value => msg.value, :offset => msg.offset
338
351
  $log.debug_backtrace
@@ -340,14 +353,18 @@ class Fluent::KafkaInput < Fluent::Input
340
353
  }
341
354
  offset = messages.last.offset + 1
342
355
 
343
- unless es.empty?
344
- @router.emit_stream(tag, es)
345
-
346
- if @offset_manager
347
- @offset_manager.save_offset(offset)
356
+ event_streams.each { |tag, es|
357
+ begin
358
+ @router.emit_stream(tag, es)
359
+ rescue BufferChunkOverflowError => e
360
+ $log.warn "Skipped records larger than the buffer chunk limit size in #{@topic_entry.topic}/#{@topic_entry.partition}", :tag => tag, :error => e.to_s
348
361
  end
349
- @next_offset = offset
362
+ }
363
+
364
+ if @offset_manager
365
+ @offset_manager.save_offset(offset)
350
366
  end
367
+ @next_offset = offset
351
368
  end
352
369
  end
353
370
 
@@ -85,6 +85,12 @@ class Fluent::KafkaGroupInput < Fluent::Input
85
85
  Fluent::BufferQueueLimitError
86
86
  end
87
87
 
88
+ BufferChunkOverflowError = if defined?(Fluent::Plugin::Buffer::BufferChunkOverflowError)
89
+ Fluent::Plugin::Buffer::BufferChunkOverflowError
90
+ else
91
+ Fluent::BufferChunkLimitError
92
+ end
93
+
88
94
  unless method_defined?(:router)
89
95
  define_method("router") { Fluent::Engine }
90
96
  end
@@ -160,8 +166,8 @@ class Fluent::KafkaGroupInput < Fluent::Input
160
166
  Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
161
167
  Proc.new { |msg| Oj.load(msg.value) }
162
168
  rescue LoadError
163
- require 'yajl'
164
- Proc.new { |msg| Yajl::Parser.parse(msg.value) }
169
+ require 'json'
170
+ Proc.new { |msg| JSON.parse(msg.value, allow_duplicate_key: true) }
165
171
  end
166
172
  when 'ltsv'
167
173
  require 'ltsv'
@@ -265,7 +271,7 @@ class Fluent::KafkaGroupInput < Fluent::Input
265
271
  end
266
272
 
267
273
  def process_batch_with_record_tag(batch)
268
- es = {}
274
+ es = {}
269
275
  batch.messages.each { |msg|
270
276
  begin
271
277
  record = @parser_proc.call(msg)
@@ -361,7 +367,7 @@ class Fluent::KafkaGroupInput < Fluent::Input
361
367
  if @tag_source == :record
362
368
  process_batch_with_record_tag(batch)
363
369
  else
364
- process_batch(batch)
370
+ process_batch(batch)
365
371
  end
366
372
  }
367
373
  rescue ForShutdown
@@ -380,6 +386,8 @@ class Fluent::KafkaGroupInput < Fluent::Input
380
386
  retries = 0
381
387
  begin
382
388
  router.emit_stream(tag, es)
389
+ rescue BufferChunkOverflowError => e
390
+ log.warn "Skipped records larger than the buffer chunk limit size", :tag => tag, :error => e.to_s
383
391
  rescue BufferError
384
392
  raise ForShutdown if @consumer.nil?
385
393
 
@@ -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) }