waterdrop 2.3.1 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +8 -49
- data/README.md +47 -8
- data/config/errors.yml +28 -5
- data/lib/waterdrop/config.rb +4 -2
- data/lib/waterdrop/contracts/config.rb +25 -15
- data/lib/waterdrop/contracts/message.rb +40 -20
- data/lib/waterdrop/instrumentation/monitor.rb +12 -35
- data/lib/waterdrop/instrumentation/notifications.rb +38 -0
- data/lib/waterdrop/instrumentation/vendors/datadog/listener.rb +44 -31
- data/lib/waterdrop/version.rb +1 -1
- data/lib/waterdrop.rb +2 -4
- data/waterdrop.gemspec +1 -4
- data.tar.gz.sig +0 -0
- metadata +7 -49
- metadata.gz.sig +5 -1
- data/lib/waterdrop/contracts/base.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0bd7288d6c0c7a6f356f050af309899cad41e6fcf202c945229231d7058194e1
|
4
|
+
data.tar.gz: 727738b727dccf8e2d7b4eb7c3f25b1c7ed761eb958eae4ba818c7402839a6c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95f69c8cd00d33e04747f1447ca9fe40de88941869925662116427b14da7f0eb0a04c7eada0e00ea6c226dd8be90484b7e97e6d34f9c7f0a11333bfede37e03c
|
7
|
+
data.tar.gz: fa43e25469180c9d9e65f31b4d79554b35c090e79d8812586d614a77acafe04bb873f24ed58f739c4523696282bef8dfa05e0fa0358ca808bfd2df038bf3d55b
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# WaterDrop changelog
|
2
2
|
|
3
|
+
## 2.4.0 (2022-07-28)
|
4
|
+
- Small refactor of the DataDog/Statsd listener to align for future extraction to `karafka-common`.
|
5
|
+
- Replace `dry-monitor` with home-brew notification layer (API compatible) and allow for usage with `ActiveSupport::Notifications`.
|
6
|
+
- Remove all the common code into `karafka-core` and add it as a dependency.
|
7
|
+
|
8
|
+
## 2.3.3 (2022-07-18)
|
9
|
+
- Replace `dry-validation` with home-brew validation layer and drop direct dependency on `dry-validation`.
|
10
|
+
- Remove indirect dependency on dry-configurable from DataDog listener (no changes required).
|
11
|
+
|
12
|
+
## 2.3.2 (2022-07-17)
|
13
|
+
- Replace `dry-configurable` with home-brew config and drop direct dependency on `dry-configurable`.
|
14
|
+
|
3
15
|
## 2.3.1 (2022-06-17)
|
4
16
|
- Update rdkafka patches to align with `0.12.0` and `0.11.1` support.
|
5
17
|
|
data/Gemfile.lock
CHANGED
@@ -1,18 +1,15 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
waterdrop (2.
|
5
|
-
|
6
|
-
dry-configurable (~> 0.13)
|
7
|
-
dry-monitor (~> 0.5)
|
8
|
-
dry-validation (~> 1.7)
|
4
|
+
waterdrop (2.4.0)
|
5
|
+
karafka-core (~> 2.0)
|
9
6
|
rdkafka (>= 0.10)
|
10
7
|
zeitwerk (~> 2.3)
|
11
8
|
|
12
9
|
GEM
|
13
10
|
remote: https://rubygems.org/
|
14
11
|
specs:
|
15
|
-
activesupport (7.0.3)
|
12
|
+
activesupport (7.0.3.1)
|
16
13
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
14
|
i18n (>= 1.6, < 2)
|
18
15
|
minitest (>= 5.1)
|
@@ -21,52 +18,15 @@ GEM
|
|
21
18
|
concurrent-ruby (1.1.10)
|
22
19
|
diff-lcs (1.5.0)
|
23
20
|
docile (1.4.0)
|
24
|
-
dry-configurable (0.15.0)
|
25
|
-
concurrent-ruby (~> 1.0)
|
26
|
-
dry-core (~> 0.6)
|
27
|
-
dry-container (0.9.0)
|
28
|
-
concurrent-ruby (~> 1.0)
|
29
|
-
dry-configurable (~> 0.13, >= 0.13.0)
|
30
|
-
dry-core (0.7.1)
|
31
|
-
concurrent-ruby (~> 1.0)
|
32
|
-
dry-events (0.3.0)
|
33
|
-
concurrent-ruby (~> 1.0)
|
34
|
-
dry-core (~> 0.5, >= 0.5)
|
35
|
-
dry-inflector (0.2.1)
|
36
|
-
dry-initializer (3.1.1)
|
37
|
-
dry-logic (1.2.0)
|
38
|
-
concurrent-ruby (~> 1.0)
|
39
|
-
dry-core (~> 0.5, >= 0.5)
|
40
|
-
dry-monitor (0.5.0)
|
41
|
-
dry-configurable (~> 0.13, >= 0.13.0)
|
42
|
-
dry-core (~> 0.5, >= 0.5)
|
43
|
-
dry-events (~> 0.2)
|
44
|
-
dry-schema (1.9.2)
|
45
|
-
concurrent-ruby (~> 1.0)
|
46
|
-
dry-configurable (~> 0.13, >= 0.13.0)
|
47
|
-
dry-core (~> 0.5, >= 0.5)
|
48
|
-
dry-initializer (~> 3.0)
|
49
|
-
dry-logic (~> 1.0)
|
50
|
-
dry-types (~> 1.5)
|
51
|
-
dry-types (1.5.1)
|
52
|
-
concurrent-ruby (~> 1.0)
|
53
|
-
dry-container (~> 0.3)
|
54
|
-
dry-core (~> 0.5, >= 0.5)
|
55
|
-
dry-inflector (~> 0.1, >= 0.1.2)
|
56
|
-
dry-logic (~> 1.0, >= 1.0.2)
|
57
|
-
dry-validation (1.8.1)
|
58
|
-
concurrent-ruby (~> 1.0)
|
59
|
-
dry-container (~> 0.7, >= 0.7.1)
|
60
|
-
dry-core (~> 0.5, >= 0.5)
|
61
|
-
dry-initializer (~> 3.0)
|
62
|
-
dry-schema (~> 1.8, >= 1.8.0)
|
63
21
|
factory_bot (6.2.1)
|
64
22
|
activesupport (>= 5.0.0)
|
65
23
|
ffi (1.15.5)
|
66
|
-
i18n (1.
|
24
|
+
i18n (1.12.0)
|
67
25
|
concurrent-ruby (~> 1.0)
|
26
|
+
karafka-core (2.0.0)
|
27
|
+
concurrent-ruby (>= 1.1)
|
68
28
|
mini_portile2 (2.8.0)
|
69
|
-
minitest (5.16.
|
29
|
+
minitest (5.16.2)
|
70
30
|
rake (13.0.6)
|
71
31
|
rdkafka (0.12.0)
|
72
32
|
ffi (~> 1.15)
|
@@ -91,12 +51,11 @@ GEM
|
|
91
51
|
simplecov_json_formatter (~> 0.1)
|
92
52
|
simplecov-html (0.12.3)
|
93
53
|
simplecov_json_formatter (0.1.4)
|
94
|
-
tzinfo (2.0.
|
54
|
+
tzinfo (2.0.5)
|
95
55
|
concurrent-ruby (~> 1.0)
|
96
56
|
zeitwerk (2.6.0)
|
97
57
|
|
98
58
|
PLATFORMS
|
99
|
-
arm64-darwin-21
|
100
59
|
x86_64-linux
|
101
60
|
|
102
61
|
DEPENDENCIES
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# WaterDrop
|
2
2
|
|
3
|
-
**Note**: Documentation presented here refers to WaterDrop `2.
|
3
|
+
**Note**: Documentation presented here refers to WaterDrop `2.x`.
|
4
4
|
|
5
|
-
WaterDrop `2.
|
5
|
+
WaterDrop `2.x` works with Karafka `2.*` and aims to either work as a standalone producer or as a part of the Karafka `2.*`.
|
6
6
|
|
7
7
|
Please refer to [this](https://github.com/karafka/waterdrop/tree/1.4) branch and its documentation for details about WaterDrop `1.*` usage.
|
8
8
|
|
@@ -10,17 +10,17 @@ Please refer to [this](https://github.com/karafka/waterdrop/tree/1.4) branch and
|
|
10
10
|
[![Gem Version](https://badge.fury.io/rb/waterdrop.svg)](http://badge.fury.io/rb/waterdrop)
|
11
11
|
[![Join the chat at https://slack.karafka.io](https://raw.githubusercontent.com/karafka/misc/master/slack.svg)](https://slack.karafka.io)
|
12
12
|
|
13
|
-
|
13
|
+
A gem to send messages to Kafka easily with an extra validation layer. It is a part of the [Karafka](https://github.com/karafka/karafka) ecosystem.
|
14
14
|
|
15
15
|
It:
|
16
16
|
|
17
|
-
- Is thread
|
17
|
+
- Is thread-safe
|
18
18
|
- Supports sync producing
|
19
19
|
- Supports async producing
|
20
20
|
- Supports buffering
|
21
21
|
- Supports producing messages to multiple clusters
|
22
22
|
- Supports multiple delivery policies
|
23
|
-
- Works with Kafka 1.0
|
23
|
+
- Works with Kafka `1.0+` and Ruby `2.7+`
|
24
24
|
|
25
25
|
## Table of contents
|
26
26
|
|
@@ -30,6 +30,8 @@ It:
|
|
30
30
|
* [Kafka configuration options](#kafka-configuration-options)
|
31
31
|
- [Usage](#usage)
|
32
32
|
* [Basic usage](#basic-usage)
|
33
|
+
* [Using WaterDrop across the application and with Ruby on Rails](#using-waterdrop-across-the-application-and-with-ruby-on-rails)
|
34
|
+
* [Using WaterDrop with a connection-pool](#using-waterdrop-with-a-connection-pool)
|
33
35
|
* [Buffering](#buffering)
|
34
36
|
+ [Using WaterDrop to buffer messages based on the application logic](#using-waterdrop-to-buffer-messages-based-on-the-application-logic)
|
35
37
|
+ [Using WaterDrop with rdkafka buffers to achieve periodic auto-flushing](#using-waterdrop-with-rdkafka-buffers-to-achieve-periodic-auto-flushing)
|
@@ -42,6 +44,8 @@ It:
|
|
42
44
|
|
43
45
|
## Installation
|
44
46
|
|
47
|
+
**Note**: If you want to both produce and consume messages, please use [Karafka](https://github.com/karafka/karafka/). It integrates WaterDrop automatically.
|
48
|
+
|
45
49
|
Add this to your Gemfile:
|
46
50
|
|
47
51
|
```ruby
|
@@ -56,7 +60,7 @@ bundle install
|
|
56
60
|
|
57
61
|
## Setup
|
58
62
|
|
59
|
-
WaterDrop is a complex tool
|
63
|
+
WaterDrop is a complex tool that contains multiple configuration options. To keep everything organized, all the configuration options were divided into two groups:
|
60
64
|
|
61
65
|
- WaterDrop options - options directly related to WaterDrop and its components
|
62
66
|
- Kafka driver options - options related to `rdkafka`
|
@@ -103,8 +107,6 @@ You can create producers with different `kafka` settings. Documentation of the a
|
|
103
107
|
|
104
108
|
## Usage
|
105
109
|
|
106
|
-
Please refer to the [documentation](https://www.rubydoc.info/gems/waterdrop) in case you're interested in the more advanced API.
|
107
|
-
|
108
110
|
### Basic usage
|
109
111
|
|
110
112
|
To send Kafka messages, just create a producer and use it:
|
@@ -157,6 +159,41 @@ Here are all the things you can provide in the message hash:
|
|
157
159
|
|
158
160
|
Keep in mind, that message you want to send should be either binary or stringified (to_s, to_json, etc).
|
159
161
|
|
162
|
+
### Using WaterDrop across the application and with Ruby on Rails
|
163
|
+
|
164
|
+
If you plan to both produce and consume messages using Kafka, you should install and use [Karafka](https://github.com/karafka/karafka). It integrates automatically with Ruby on Rails applications and auto-configures WaterDrop producer to make it accessible via `Karafka#producer` method.
|
165
|
+
|
166
|
+
If you want to only produce messages from within your application, since WaterDrop is thread-safe you can create a single instance in an initializer like so:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
KAFKA_PRODUCER = WaterDrop::Producer.new
|
170
|
+
|
171
|
+
KAFKA_PRODUCER.setup do |config|
|
172
|
+
config.kafka = { 'bootstrap.servers': 'localhost:9092' }
|
173
|
+
end
|
174
|
+
|
175
|
+
# And just dispatch messages
|
176
|
+
KAFKA_PRODUCER.produce_sync(topic: 'my-topic', payload: 'my message')
|
177
|
+
```
|
178
|
+
|
179
|
+
### Using WaterDrop with a connection-pool
|
180
|
+
|
181
|
+
While WaterDrop is thread-safe, there is no problem in using it with a connection pool inside high-intensity applications. The only thing worth keeping in mind, is that WaterDrop instances should be shutdown before the application is closed.
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
KAFKA_PRODUCERS_CP = ConnectionPool.new do
|
185
|
+
WaterDrop::Producer.new do |config|
|
186
|
+
config.kafka = { 'bootstrap.servers': 'localhost:9092' }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
KAFKA_PRODUCERS_CP.with do |producer|
|
191
|
+
producer.produce_async(topic: 'my-topic', payload: 'my message')
|
192
|
+
end
|
193
|
+
|
194
|
+
KAFKA_PRODUCERS_CP.shutdown { |producer| producer.close }
|
195
|
+
```
|
196
|
+
|
160
197
|
### Buffering
|
161
198
|
|
162
199
|
WaterDrop producers support buffering messages in their internal buffers and on the `rdkafka` level via `queue.buffering.*` set of settings.
|
@@ -316,6 +353,8 @@ producer.monitor.subscribe(listener)
|
|
316
353
|
|
317
354
|
You can also find [here](https://github.com/karafka/waterdrop/blob/master/lib/waterdrop/instrumentation/vendors/datadog/dashboard.json) a ready to import DataDog dashboard configuration file that you can use to monitor all of your producers.
|
318
355
|
|
356
|
+
![Example WaterDrop DD dashboard](https://raw.githubusercontent.com/karafka/misc/master/printscreens/waterdrop_dd_dashboard_example.png)
|
357
|
+
|
319
358
|
### Error notifications
|
320
359
|
|
321
360
|
WaterDrop allows you to listen to all errors that occur while producing messages and in its internal background threads. Things like reconnecting to Kafka upon network errors and others unrelated to publishing messages are all available under `error.occurred` notification key. You can subscribe to this event to ensure your setup is healthy and without any problems that would otherwise go unnoticed as long as messages are delivered.
|
data/config/errors.yml
CHANGED
@@ -1,7 +1,30 @@
|
|
1
1
|
en:
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
validations:
|
3
|
+
config:
|
4
|
+
missing: must be present
|
5
|
+
logger_format: must be present
|
6
|
+
deliver_format: must be boolean
|
7
|
+
id_format: must be a non-empty string
|
8
|
+
max_payload_size_format: must be an integer that is equal or bigger than 1
|
9
|
+
wait_timeout_format: must be a numeric that is bigger than 0
|
10
|
+
max_wait_timeout_format: must be an integer that is equal or bigger than 0
|
11
|
+
kafka_format: must be a hash with symbol based keys
|
7
12
|
kafka_key_must_be_a_symbol: All keys under the kafka settings scope need to be symbols
|
13
|
+
|
14
|
+
message:
|
15
|
+
missing: must be present
|
16
|
+
partition_format: must be an integer greater or equal to -1
|
17
|
+
topic_format: 'does not match the topic allowed format'
|
18
|
+
partition_key_format: must be a non-empty string
|
19
|
+
timestamp_format: must be either time or integer
|
20
|
+
payload_format: must be string
|
21
|
+
headers_format: must be a hash
|
22
|
+
key_format: must be a non-empty string
|
23
|
+
payload_max_size: is more than `max_payload_size` config value
|
24
|
+
headers_invalid_key_type: all headers keys need to be of type String
|
25
|
+
headers_invalid_value_type: all headers values need to be of type String
|
26
|
+
|
27
|
+
test:
|
28
|
+
missing: must be present
|
29
|
+
nested.id_format: 'is invalid'
|
30
|
+
nested.id2_format: 'is invalid'
|
data/lib/waterdrop/config.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
module WaterDrop
|
6
6
|
# Configuration object for setting up all options required by WaterDrop
|
7
7
|
class Config
|
8
|
-
include
|
8
|
+
include ::Karafka::Core::Configurable
|
9
9
|
|
10
10
|
# Defaults for kafka settings, that will be overwritten only if not present already
|
11
11
|
KAFKA_DEFAULTS = {
|
@@ -68,6 +68,8 @@ module WaterDrop
|
|
68
68
|
|
69
69
|
::Rdkafka::Config.logger = config.logger
|
70
70
|
end
|
71
|
+
|
72
|
+
self
|
71
73
|
end
|
72
74
|
|
73
75
|
private
|
@@ -75,7 +77,7 @@ module WaterDrop
|
|
75
77
|
# Propagates the kafka setting defaults unless they are already present
|
76
78
|
# This makes it easier to set some values that users usually don't change but still allows them
|
77
79
|
# to overwrite the whole hash if they want to
|
78
|
-
# @param config [
|
80
|
+
# @param config [WaterDrop::Configurable::Node] config of this producer
|
79
81
|
def merge_kafka_defaults!(config)
|
80
82
|
KAFKA_DEFAULTS.each do |key, value|
|
81
83
|
next if config.kafka.key?(key)
|
@@ -3,27 +3,37 @@
|
|
3
3
|
module WaterDrop
|
4
4
|
module Contracts
|
5
5
|
# Contract with validation rules for WaterDrop configuration details
|
6
|
-
class Config <
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
required(:wait_timeout).filled(:number?, gt?: 0)
|
14
|
-
required(:kafka).filled(:hash?)
|
6
|
+
class Config < ::Karafka::Core::Contractable::Contract
|
7
|
+
configure do |config|
|
8
|
+
config.error_messages = YAML.safe_load(
|
9
|
+
File.read(
|
10
|
+
File.join(WaterDrop.gem_root, 'config', 'errors.yml')
|
11
|
+
)
|
12
|
+
).fetch('en').fetch('validations').fetch('config')
|
15
13
|
end
|
16
14
|
|
15
|
+
required(:id) { |val| val.is_a?(String) && !val.empty? }
|
16
|
+
required(:logger) { |val| !val.nil? }
|
17
|
+
required(:deliver) { |val| [true, false].include?(val) }
|
18
|
+
required(:max_payload_size) { |val| val.is_a?(Integer) && val >= 1 }
|
19
|
+
required(:max_wait_timeout) { |val| val.is_a?(Numeric) && val >= 0 }
|
20
|
+
required(:wait_timeout) { |val| val.is_a?(Numeric) && val.positive? }
|
21
|
+
required(:kafka) { |val| val.is_a?(Hash) && !val.empty? }
|
22
|
+
|
17
23
|
# rdkafka allows both symbols and strings as keys for config but then casts them to strings
|
18
24
|
# This can be confusing, so we expect all keys to be symbolized
|
19
|
-
|
20
|
-
next unless
|
25
|
+
virtual do |config, errors|
|
26
|
+
next true unless errors.empty?
|
27
|
+
|
28
|
+
errors = []
|
21
29
|
|
22
|
-
|
23
|
-
|
30
|
+
config
|
31
|
+
.fetch(:kafka)
|
32
|
+
.keys
|
33
|
+
.reject { |key| key.is_a?(Symbol) }
|
34
|
+
.each { |key| errors << [[:kafka, key], :kafka_key_must_be_a_symbol] }
|
24
35
|
|
25
|
-
|
26
|
-
end
|
36
|
+
errors
|
27
37
|
end
|
28
38
|
end
|
29
39
|
end
|
@@ -4,36 +4,56 @@ module WaterDrop
|
|
4
4
|
module Contracts
|
5
5
|
# Contract with validation rules for validating that all the message options that
|
6
6
|
# we provide to producer ale valid and usable
|
7
|
-
class Message <
|
7
|
+
class Message < ::Karafka::Core::Contractable::Contract
|
8
|
+
configure do |config|
|
9
|
+
config.error_messages = YAML.safe_load(
|
10
|
+
File.read(
|
11
|
+
File.join(WaterDrop.gem_root, 'config', 'errors.yml')
|
12
|
+
)
|
13
|
+
).fetch('en').fetch('validations').fetch('message')
|
14
|
+
end
|
15
|
+
|
8
16
|
# Regex to check that topic has a valid format
|
9
17
|
TOPIC_REGEXP = /\A(\w|-|\.)+\z/
|
10
18
|
|
11
|
-
|
12
|
-
STRING_ASSERTION = ->(value) { value.is_a?(String) }.to_proc
|
19
|
+
private_constant :TOPIC_REGEXP
|
13
20
|
|
14
|
-
|
21
|
+
attr_reader :max_payload_size
|
15
22
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
required(:payload).filled(:str?)
|
21
|
-
optional(:key).maybe(:str?, :filled?)
|
22
|
-
optional(:partition).filled(:int?, gteq?: -1)
|
23
|
-
optional(:partition_key).maybe(:str?, :filled?)
|
24
|
-
optional(:timestamp).maybe { time? | int? }
|
25
|
-
optional(:headers).maybe(:hash?)
|
23
|
+
# @param max_payload_size [Integer] max payload size
|
24
|
+
def initialize(max_payload_size:)
|
25
|
+
super()
|
26
|
+
@max_payload_size = max_payload_size
|
26
27
|
end
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
required(:topic) { |val| val.is_a?(String) && TOPIC_REGEXP.match?(val) }
|
30
|
+
required(:payload) { |val| val.is_a?(String) }
|
31
|
+
optional(:key) { |val| val.nil? || (val.is_a?(String) && !val.empty?) }
|
32
|
+
optional(:partition) { |val| val.is_a?(Integer) && val >= -1 }
|
33
|
+
optional(:partition_key) { |val| val.nil? || (val.is_a?(String) && !val.empty?) }
|
34
|
+
optional(:timestamp) { |val| val.nil? || (val.is_a?(Time) || val.is_a?(Integer)) }
|
35
|
+
optional(:headers) { |val| val.nil? || val.is_a?(Hash) }
|
36
|
+
|
37
|
+
virtual do |config, errors|
|
38
|
+
next true unless errors.empty?
|
39
|
+
next true unless config.key?(:headers)
|
40
|
+
next true if config[:headers].nil?
|
30
41
|
|
31
|
-
|
32
|
-
|
42
|
+
errors = []
|
43
|
+
|
44
|
+
config.fetch(:headers).each do |key, value|
|
45
|
+
errors << [%i[headers], :invalid_key_type] unless key.is_a?(String)
|
46
|
+
errors << [%i[headers], :invalid_value_type] unless value.is_a?(String)
|
47
|
+
end
|
48
|
+
|
49
|
+
errors
|
33
50
|
end
|
34
51
|
|
35
|
-
|
36
|
-
|
52
|
+
virtual do |config, errors, validator|
|
53
|
+
next true unless errors.empty?
|
54
|
+
next true if config[:payload].bytesize <= validator.max_payload_size
|
55
|
+
|
56
|
+
[[%i[payload], :max_size]]
|
37
57
|
end
|
38
58
|
end
|
39
59
|
end
|
@@ -2,41 +2,18 @@
|
|
2
2
|
|
3
3
|
module WaterDrop
|
4
4
|
module Instrumentation
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
message.produced_async
|
18
|
-
message.produced_sync
|
19
|
-
message.acknowledged
|
20
|
-
message.buffered
|
21
|
-
|
22
|
-
messages.produced_async
|
23
|
-
messages.produced_sync
|
24
|
-
messages.buffered
|
25
|
-
|
26
|
-
buffer.flushed_async
|
27
|
-
buffer.flushed_sync
|
28
|
-
|
29
|
-
statistics.emitted
|
30
|
-
|
31
|
-
error.occurred
|
32
|
-
].freeze
|
33
|
-
|
34
|
-
private_constant :EVENTS
|
35
|
-
|
36
|
-
# @return [WaterDrop::Instrumentation::Monitor] monitor instance for system instrumentation
|
37
|
-
def initialize
|
38
|
-
super(:waterdrop)
|
39
|
-
EVENTS.each(&method(:register_event))
|
5
|
+
# WaterDrop instrumentation monitor that we use to publish events
|
6
|
+
# By default uses our internal notifications bus but can be used with
|
7
|
+
# `ActiveSupport::Notifications` as well
|
8
|
+
class Monitor < ::Karafka::Core::Monitoring::Monitor
|
9
|
+
# @param notifications_bus [Object] either our internal notifications bus or
|
10
|
+
# `ActiveSupport::Notifications`
|
11
|
+
# @param namespace [String, nil] namespace for events or nil if no namespace
|
12
|
+
def initialize(
|
13
|
+
notifications_bus = WaterDrop::Instrumentation::Notifications.new,
|
14
|
+
namespace = nil
|
15
|
+
)
|
16
|
+
super(notifications_bus, namespace)
|
40
17
|
end
|
41
18
|
end
|
42
19
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WaterDrop
|
4
|
+
module Instrumentation
|
5
|
+
# Instrumented is used to hookup external monitoring services to monitor how WaterDrop works
|
6
|
+
class Notifications < ::Karafka::Core::Monitoring::Notifications
|
7
|
+
# List of events that we support in the system and to which a monitor client can hook up
|
8
|
+
# @note The non-error once support timestamp benchmarking
|
9
|
+
EVENTS = %w[
|
10
|
+
producer.closed
|
11
|
+
|
12
|
+
message.produced_async
|
13
|
+
message.produced_sync
|
14
|
+
message.acknowledged
|
15
|
+
message.buffered
|
16
|
+
|
17
|
+
messages.produced_async
|
18
|
+
messages.produced_sync
|
19
|
+
messages.buffered
|
20
|
+
|
21
|
+
buffer.flushed_async
|
22
|
+
buffer.flushed_sync
|
23
|
+
|
24
|
+
statistics.emitted
|
25
|
+
|
26
|
+
error.occurred
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
private_constant :EVENTS
|
30
|
+
|
31
|
+
# @return [WaterDrop::Instrumentation::Monitor] monitor instance for system instrumentation
|
32
|
+
def initialize
|
33
|
+
super
|
34
|
+
EVENTS.each { |event| register_event(event) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -11,26 +11,29 @@ module WaterDrop
|
|
11
11
|
#
|
12
12
|
# @note You need to setup the `dogstatsd-ruby` client and assign it
|
13
13
|
class Listener
|
14
|
-
include
|
14
|
+
include ::Karafka::Core::Configurable
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
def_delegators :config, :client, :rd_kafka_metrics, :namespace, :default_tags
|
15
18
|
|
16
19
|
# Value object for storing a single rdkafka metric publishing details
|
17
20
|
RdKafkaMetric = Struct.new(:type, :scope, :name, :key_location)
|
18
21
|
|
19
22
|
# Namespace under which the DD metrics should be published
|
20
|
-
setting :namespace, default: 'waterdrop'
|
23
|
+
setting :namespace, default: 'waterdrop'
|
21
24
|
|
22
25
|
# Datadog client that we should use to publish the metrics
|
23
|
-
setting :client
|
26
|
+
setting :client
|
24
27
|
|
25
28
|
# Default tags we want to publish (for example hostname)
|
26
29
|
# Format as followed (example for hostname): `["host:#{Socket.gethostname}"]`
|
27
|
-
setting :default_tags, default: []
|
30
|
+
setting :default_tags, default: []
|
28
31
|
|
29
32
|
# All the rdkafka metrics we want to publish
|
30
33
|
#
|
31
34
|
# By default we publish quite a lot so this can be tuned
|
32
35
|
# Note, that the once with `_d` come from WaterDrop, not rdkafka or Kafka
|
33
|
-
setting :rd_kafka_metrics,
|
36
|
+
setting :rd_kafka_metrics, default: [
|
34
37
|
# Client metrics
|
35
38
|
RdKafkaMetric.new(:count, :root, 'calls', 'tx_d'),
|
36
39
|
RdKafkaMetric.new(:histogram, :root, 'queue.size', 'msg_cnt_d'),
|
@@ -47,8 +50,11 @@ module WaterDrop
|
|
47
50
|
RdKafkaMetric.new(:gauge, :brokers, 'network.latency.p99', %w[rtt p99])
|
48
51
|
].freeze
|
49
52
|
|
53
|
+
configure
|
54
|
+
|
50
55
|
# @param block [Proc] configuration block
|
51
56
|
def initialize(&block)
|
57
|
+
configure
|
52
58
|
setup(&block) if block
|
53
59
|
end
|
54
60
|
|
@@ -60,7 +66,7 @@ module WaterDrop
|
|
60
66
|
|
61
67
|
# Hooks up to WaterDrop instrumentation for emitted statistics
|
62
68
|
#
|
63
|
-
# @param event [
|
69
|
+
# @param event [WaterDrop::Monitor::Event]
|
64
70
|
def on_statistics_emitted(event)
|
65
71
|
statistics = event[:statistics]
|
66
72
|
|
@@ -71,22 +77,15 @@ module WaterDrop
|
|
71
77
|
|
72
78
|
# Increases the errors count by 1
|
73
79
|
#
|
74
|
-
# @param _event [
|
80
|
+
# @param _event [WaterDrop::Monitor::Event]
|
75
81
|
def on_error_occurred(_event)
|
76
|
-
|
77
|
-
namespaced_metric('error_occurred'),
|
78
|
-
1,
|
79
|
-
tags: default_tags
|
80
|
-
)
|
82
|
+
count('error_occurred', 1, tags: default_tags)
|
81
83
|
end
|
82
84
|
|
83
85
|
# Increases acknowledged messages counter
|
84
|
-
# @param _event [
|
86
|
+
# @param _event [WaterDrop::Monitor::Event]
|
85
87
|
def on_message_acknowledged(_event)
|
86
|
-
|
87
|
-
namespaced_metric('acknowledged'),
|
88
|
-
tags: default_tags
|
89
|
-
)
|
88
|
+
increment('acknowledged', tags: default_tags)
|
90
89
|
end
|
91
90
|
|
92
91
|
%i[
|
@@ -94,12 +93,12 @@ module WaterDrop
|
|
94
93
|
produced_async
|
95
94
|
].each do |event_scope|
|
96
95
|
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
97
|
-
# @param event [
|
96
|
+
# @param event [WaterDrop::Monitor::Event]
|
98
97
|
def on_message_#{event_scope}(event)
|
99
98
|
report_message(event[:message][:topic], :#{event_scope})
|
100
99
|
end
|
101
100
|
|
102
|
-
# @param event [
|
101
|
+
# @param event [WaterDrop::Monitor::Event]
|
103
102
|
def on_messages_#{event_scope}(event)
|
104
103
|
event[:messages].each do |message|
|
105
104
|
report_message(message[:topic], :#{event_scope})
|
@@ -114,10 +113,10 @@ module WaterDrop
|
|
114
113
|
messages_buffered
|
115
114
|
].each do |event_scope|
|
116
115
|
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
117
|
-
# @param event [
|
116
|
+
# @param event [WaterDrop::Monitor::Event]
|
118
117
|
def on_#{event_scope}(event)
|
119
|
-
|
120
|
-
|
118
|
+
histogram(
|
119
|
+
'buffer.size',
|
121
120
|
event[:buffer].size,
|
122
121
|
tags: default_tags
|
123
122
|
)
|
@@ -132,7 +131,7 @@ module WaterDrop
|
|
132
131
|
flushed_async
|
133
132
|
].each do |event_scope|
|
134
133
|
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
135
|
-
# @param event [
|
134
|
+
# @param event [WaterDrop::Monitor::Event]
|
136
135
|
def on_buffer_#{event_scope}(event)
|
137
136
|
event[:messages].each do |message|
|
138
137
|
report_message(message[:topic], :#{event_scope})
|
@@ -143,14 +142,28 @@ module WaterDrop
|
|
143
142
|
|
144
143
|
private
|
145
144
|
|
145
|
+
%i[
|
146
|
+
count
|
147
|
+
gauge
|
148
|
+
histogram
|
149
|
+
increment
|
150
|
+
decrement
|
151
|
+
].each do |metric_type|
|
152
|
+
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
153
|
+
def #{metric_type}(key, *args)
|
154
|
+
client.#{metric_type}(
|
155
|
+
namespaced_metric(key),
|
156
|
+
*args
|
157
|
+
)
|
158
|
+
end
|
159
|
+
METHODS
|
160
|
+
end
|
161
|
+
|
146
162
|
# Report that a message has been produced to a topic.
|
147
163
|
# @param topic [String] Kafka topic
|
148
164
|
# @param method_name [Symbol] method from which this message operation comes
|
149
165
|
def report_message(topic, method_name)
|
150
|
-
|
151
|
-
namespaced_metric(method_name),
|
152
|
-
tags: default_tags + ["topic:#{topic}"]
|
153
|
-
)
|
166
|
+
increment(method_name, tags: default_tags + ["topic:#{topic}"])
|
154
167
|
end
|
155
168
|
|
156
169
|
# Wraps metric name in listener's namespace
|
@@ -166,9 +179,9 @@ module WaterDrop
|
|
166
179
|
def report_metric(metric, statistics)
|
167
180
|
case metric.scope
|
168
181
|
when :root
|
169
|
-
|
182
|
+
public_send(
|
170
183
|
metric.type,
|
171
|
-
|
184
|
+
metric.name,
|
172
185
|
statistics.fetch(*metric.key_location),
|
173
186
|
tags: default_tags
|
174
187
|
)
|
@@ -179,9 +192,9 @@ module WaterDrop
|
|
179
192
|
# node ids
|
180
193
|
next if broker_statistics['nodeid'] == -1
|
181
194
|
|
182
|
-
|
195
|
+
public_send(
|
183
196
|
metric.type,
|
184
|
-
|
197
|
+
metric.name,
|
185
198
|
broker_statistics.dig(*metric.key_location),
|
186
199
|
tags: default_tags + ["broker:#{broker_statistics['nodename']}"]
|
187
200
|
)
|
data/lib/waterdrop/version.rb
CHANGED
data/lib/waterdrop.rb
CHANGED
@@ -3,10 +3,8 @@
|
|
3
3
|
# External components
|
4
4
|
# delegate should be removed because we don't need it, we just add it because of ruby-kafka
|
5
5
|
%w[
|
6
|
-
|
7
|
-
|
8
|
-
dry/monitor/notifications
|
9
|
-
dry-validation
|
6
|
+
karafka-core
|
7
|
+
forwardable
|
10
8
|
rdkafka
|
11
9
|
json
|
12
10
|
zeitwerk
|
data/waterdrop.gemspec
CHANGED
@@ -16,10 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.description = spec.summary
|
17
17
|
spec.license = 'MIT'
|
18
18
|
|
19
|
-
spec.add_dependency '
|
20
|
-
spec.add_dependency 'dry-configurable', '~> 0.13'
|
21
|
-
spec.add_dependency 'dry-monitor', '~> 0.5'
|
22
|
-
spec.add_dependency 'dry-validation', '~> 1.7'
|
19
|
+
spec.add_dependency 'karafka-core', '~> 2.0'
|
23
20
|
spec.add_dependency 'rdkafka', '>= 0.10'
|
24
21
|
spec.add_dependency 'zeitwerk', '~> 2.3'
|
25
22
|
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: waterdrop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maciej Mensfeld
|
@@ -34,64 +34,22 @@ cert_chain:
|
|
34
34
|
R2P11bWoCtr70BsccVrN8jEhzwXngMyI2gVt750Y+dbTu1KgRqZKp/ECe7ZzPzXj
|
35
35
|
pIy9vHxTANKYVyI4qj8OrFdEM5BQNu8oQpL0iQ==
|
36
36
|
-----END CERTIFICATE-----
|
37
|
-
date: 2022-
|
37
|
+
date: 2022-07-28 00:00:00.000000000 Z
|
38
38
|
dependencies:
|
39
39
|
- !ruby/object:Gem::Dependency
|
40
|
-
name:
|
41
|
-
requirement: !ruby/object:Gem::Requirement
|
42
|
-
requirements:
|
43
|
-
- - ">="
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
version: '1.1'
|
46
|
-
type: :runtime
|
47
|
-
prerelease: false
|
48
|
-
version_requirements: !ruby/object:Gem::Requirement
|
49
|
-
requirements:
|
50
|
-
- - ">="
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: '1.1'
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: dry-configurable
|
55
|
-
requirement: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - "~>"
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: '0.13'
|
60
|
-
type: :runtime
|
61
|
-
prerelease: false
|
62
|
-
version_requirements: !ruby/object:Gem::Requirement
|
63
|
-
requirements:
|
64
|
-
- - "~>"
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: '0.13'
|
67
|
-
- !ruby/object:Gem::Dependency
|
68
|
-
name: dry-monitor
|
69
|
-
requirement: !ruby/object:Gem::Requirement
|
70
|
-
requirements:
|
71
|
-
- - "~>"
|
72
|
-
- !ruby/object:Gem::Version
|
73
|
-
version: '0.5'
|
74
|
-
type: :runtime
|
75
|
-
prerelease: false
|
76
|
-
version_requirements: !ruby/object:Gem::Requirement
|
77
|
-
requirements:
|
78
|
-
- - "~>"
|
79
|
-
- !ruby/object:Gem::Version
|
80
|
-
version: '0.5'
|
81
|
-
- !ruby/object:Gem::Dependency
|
82
|
-
name: dry-validation
|
40
|
+
name: karafka-core
|
83
41
|
requirement: !ruby/object:Gem::Requirement
|
84
42
|
requirements:
|
85
43
|
- - "~>"
|
86
44
|
- !ruby/object:Gem::Version
|
87
|
-
version: '
|
45
|
+
version: '2.0'
|
88
46
|
type: :runtime
|
89
47
|
prerelease: false
|
90
48
|
version_requirements: !ruby/object:Gem::Requirement
|
91
49
|
requirements:
|
92
50
|
- - "~>"
|
93
51
|
- !ruby/object:Gem::Version
|
94
|
-
version: '
|
52
|
+
version: '2.0'
|
95
53
|
- !ruby/object:Gem::Dependency
|
96
54
|
name: rdkafka
|
97
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,7 +103,6 @@ files:
|
|
145
103
|
- lib/waterdrop.rb
|
146
104
|
- lib/waterdrop/config.rb
|
147
105
|
- lib/waterdrop/contracts.rb
|
148
|
-
- lib/waterdrop/contracts/base.rb
|
149
106
|
- lib/waterdrop/contracts/config.rb
|
150
107
|
- lib/waterdrop/contracts/message.rb
|
151
108
|
- lib/waterdrop/errors.rb
|
@@ -157,6 +114,7 @@ files:
|
|
157
114
|
- lib/waterdrop/instrumentation/callbacks_manager.rb
|
158
115
|
- lib/waterdrop/instrumentation/logger_listener.rb
|
159
116
|
- lib/waterdrop/instrumentation/monitor.rb
|
117
|
+
- lib/waterdrop/instrumentation/notifications.rb
|
160
118
|
- lib/waterdrop/instrumentation/vendors/datadog/dashboard.json
|
161
119
|
- lib/waterdrop/instrumentation/vendors/datadog/listener.rb
|
162
120
|
- lib/waterdrop/patches/rdkafka/bindings.rb
|
@@ -192,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
192
150
|
- !ruby/object:Gem::Version
|
193
151
|
version: '0'
|
194
152
|
requirements: []
|
195
|
-
rubygems_version: 3.
|
153
|
+
rubygems_version: 3.3.7
|
196
154
|
signing_key:
|
197
155
|
specification_version: 4
|
198
156
|
summary: Kafka messaging made easy!
|
metadata.gz.sig
CHANGED
@@ -1 +1,5 @@
|
|
1
|
-
|
1
|
+
�D�T�[_g4r0Z���r�-(�� �d����$��HM��]��2�0��9�� D;�98@�.���\$ߝ0�Kn&�l��]��%�
|
2
|
+
#9xg�j��OZ�tCp�4��ҥ��[��r�
|
3
|
+
@~�5��/9��_'������0k��'-@G���������c�[":�jh�9]����^��i]�8�x�'tR���z^�@�f�n��O�!��ؗ����
|
4
|
+
B��$Il�J�ۺE'����,���=��>ǡ��k�j�Pڪ'�[\���]��;̧��ԏ
|
5
|
+
iB&U��z�1_���k���B�*�}])
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WaterDrop
|
4
|
-
module Contracts
|
5
|
-
# Base for all the contracts in WaterDrop
|
6
|
-
class Base < Dry::Validation::Contract
|
7
|
-
config.messages.load_paths << File.join(WaterDrop.gem_root, 'config', 'errors.yml')
|
8
|
-
|
9
|
-
# @param data [Hash] data for validation
|
10
|
-
# @param error_class [Class] error class that should be used when validation fails
|
11
|
-
# @return [Boolean] true
|
12
|
-
# @raise [StandardError] any error provided in the error_class that inherits from the
|
13
|
-
# standard error
|
14
|
-
def validate!(data, error_class)
|
15
|
-
result = call(data)
|
16
|
-
|
17
|
-
return true if result.success?
|
18
|
-
|
19
|
-
raise error_class, result.errors.to_h
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|