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 +4 -4
- data/.github/dependabot.yml +17 -0
- data/.github/workflows/linux.yml +3 -16
- data/.github/workflows/stale-actions.yml +1 -1
- data/ChangeLog +75 -0
- data/README.md +53 -3
- data/fluent-plugin-kafka.gemspec +1 -1
- data/lib/fluent/plugin/in_kafka.rb +30 -13
- data/lib/fluent/plugin/in_kafka_group.rb +12 -4
- data/lib/fluent/plugin/in_rdkafka_group.rb +15 -3
- data/lib/fluent/plugin/kafka_plugin_util.rb +106 -1
- data/lib/fluent/plugin/out_kafka.rb +3 -3
- data/lib/fluent/plugin/out_kafka2.rb +37 -8
- data/lib/fluent/plugin/out_kafka_buffered.rb +3 -3
- data/lib/fluent/plugin/out_rdkafka.rb +6 -4
- data/lib/fluent/plugin/out_rdkafka2.rb +21 -48
- data/lib/fluent/plugin/rdkafka_patch/0_13_0.rb +43 -0
- data/lib/fluent/plugin/rdkafka_patch/0_16_0.rb +3 -2
- data/test/plugin/test_in_kafka.rb +153 -0
- data/test/plugin/test_in_kafka_group.rb +13 -0
- data/test/plugin/test_in_rdkafka_group.rb +198 -0
- data/test/plugin/test_kafka_plugin_util.rb +26 -0
- data/test/plugin/test_out_kafka2.rb +172 -0
- data/test/plugin/test_out_rdkafka.rb +105 -0
- data/test/plugin/test_out_rdkafka2.rb +341 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2eeb3b37a48db8cde6fda0eb9e413f507076c231abd06456a35540a10eed5667
|
|
4
|
+
data.tar.gz: 90b95493d3445829123ee569af66f581b109dcea7f91cf3d9aee6b2b6b9caeab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3abf77244f8a7b239e92f63c798f0dc70b0d078a3b75de8f17ddc9e7cb17a8108088cc4f4bc3e44005496c68c89e960ca727def67cb7418f8e001bb713c938b4
|
|
7
|
+
data.tar.gz: 892d0c09e810b9fe78f87917a8468ac392b206991e99c4080f755e80953db79d8637e41717ef3ae0acbf13baa26e2fd1fe032bef7650ca70bc62df44b1dead7a
|
data/.github/dependabot.yml
CHANGED
|
@@ -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
|
data/.github/workflows/linux.yml
CHANGED
|
@@ -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.
|
|
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@
|
|
50
|
-
- uses: ruby/setup-ruby@
|
|
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@
|
|
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
|
|
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
|
|
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
|
-
#
|
|
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
|
data/fluent-plugin-kafka.gemspec
CHANGED
|
@@ -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.
|
|
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 '
|
|
152
|
+
require 'json'
|
|
147
153
|
Proc.new { |msg, te|
|
|
148
|
-
r =
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
@
|
|
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
|
-
|
|
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 '
|
|
164
|
-
Proc.new { |msg|
|
|
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 '
|
|
141
|
-
Proc.new { |msg|
|
|
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(@
|
|
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, :
|
|
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 =
|
|
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 '
|
|
196
|
-
Proc.new { |tag, time, 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) }
|