waterdrop 2.0.7 → 2.6.11
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/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +22 -11
- data/.ruby-version +1 -1
- data/CHANGELOG.md +200 -0
- data/Gemfile +0 -2
- data/Gemfile.lock +32 -75
- data/README.md +22 -275
- data/certs/cert_chain.pem +26 -0
- data/config/locales/errors.yml +33 -0
- data/docker-compose.yml +19 -12
- data/lib/waterdrop/clients/buffered.rb +90 -0
- data/lib/waterdrop/clients/dummy.rb +69 -0
- data/lib/waterdrop/clients/rdkafka.rb +34 -0
- data/lib/{water_drop → waterdrop}/config.rb +39 -16
- data/lib/waterdrop/contracts/config.rb +43 -0
- data/lib/waterdrop/contracts/message.rb +64 -0
- data/lib/{water_drop → waterdrop}/errors.rb +14 -7
- data/lib/waterdrop/instrumentation/callbacks/delivery.rb +102 -0
- data/lib/{water_drop → waterdrop}/instrumentation/callbacks/error.rb +6 -2
- data/lib/{water_drop → waterdrop}/instrumentation/callbacks/statistics.rb +1 -1
- data/lib/{water_drop/instrumentation/stdout_listener.rb → waterdrop/instrumentation/logger_listener.rb} +66 -21
- data/lib/waterdrop/instrumentation/monitor.rb +20 -0
- data/lib/{water_drop/instrumentation/monitor.rb → waterdrop/instrumentation/notifications.rb} +12 -14
- data/lib/waterdrop/instrumentation/vendors/datadog/dashboard.json +1 -0
- data/lib/waterdrop/instrumentation/vendors/datadog/metrics_listener.rb +210 -0
- data/lib/waterdrop/middleware.rb +50 -0
- data/lib/{water_drop → waterdrop}/producer/async.rb +40 -4
- data/lib/{water_drop → waterdrop}/producer/buffer.rb +12 -30
- data/lib/{water_drop → waterdrop}/producer/builder.rb +6 -11
- data/lib/{water_drop → waterdrop}/producer/sync.rb +44 -15
- data/lib/waterdrop/producer/transactions.rb +170 -0
- data/lib/waterdrop/producer.rb +308 -0
- data/lib/{water_drop → waterdrop}/version.rb +1 -1
- data/lib/waterdrop.rb +28 -2
- data/renovate.json +6 -0
- data/waterdrop.gemspec +14 -11
- data.tar.gz.sig +0 -0
- metadata +71 -111
- metadata.gz.sig +0 -0
- data/certs/mensfeld.pem +0 -25
- data/config/errors.yml +0 -6
- data/lib/water_drop/contracts/config.rb +0 -26
- data/lib/water_drop/contracts/message.rb +0 -42
- data/lib/water_drop/instrumentation/callbacks/delivery.rb +0 -30
- data/lib/water_drop/instrumentation/callbacks/statistics_decorator.rb +0 -77
- data/lib/water_drop/instrumentation/callbacks_manager.rb +0 -39
- data/lib/water_drop/instrumentation.rb +0 -20
- data/lib/water_drop/patches/rdkafka/bindings.rb +0 -42
- data/lib/water_drop/patches/rdkafka/producer.rb +0 -20
- data/lib/water_drop/producer/dummy_client.rb +0 -32
- data/lib/water_drop/producer.rb +0 -162
- data/lib/water_drop.rb +0 -36
- /data/lib/{water_drop → waterdrop}/contracts.rb +0 -0
- /data/lib/{water_drop → waterdrop}/producer/status.rb +0 -0
@@ -0,0 +1,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WaterDrop
|
4
|
+
module Instrumentation
|
5
|
+
# Namespace for vendor specific instrumentation
|
6
|
+
module Vendors
|
7
|
+
# Datadog specific instrumentation
|
8
|
+
module Datadog
|
9
|
+
# Listener that can be used to subscribe to WaterDrop producer to receive stats via StatsD
|
10
|
+
# and/or Datadog
|
11
|
+
#
|
12
|
+
# @note You need to setup the `dogstatsd-ruby` client and assign it
|
13
|
+
class MetricsListener
|
14
|
+
include ::Karafka::Core::Configurable
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
def_delegators :config, :client, :rd_kafka_metrics, :namespace, :default_tags
|
18
|
+
|
19
|
+
# Value object for storing a single rdkafka metric publishing details
|
20
|
+
RdKafkaMetric = Struct.new(:type, :scope, :name, :key_location)
|
21
|
+
|
22
|
+
# Namespace under which the DD metrics should be published
|
23
|
+
setting :namespace, default: 'waterdrop'
|
24
|
+
|
25
|
+
# Datadog client that we should use to publish the metrics
|
26
|
+
setting :client
|
27
|
+
|
28
|
+
# Default tags we want to publish (for example hostname)
|
29
|
+
# Format as followed (example for hostname): `["host:#{Socket.gethostname}"]`
|
30
|
+
setting :default_tags, default: []
|
31
|
+
|
32
|
+
# All the rdkafka metrics we want to publish
|
33
|
+
#
|
34
|
+
# By default we publish quite a lot so this can be tuned
|
35
|
+
# Note, that the once with `_d` come from WaterDrop, not rdkafka or Kafka
|
36
|
+
setting :rd_kafka_metrics, default: [
|
37
|
+
# Client metrics
|
38
|
+
RdKafkaMetric.new(:count, :root, 'calls', 'tx_d'),
|
39
|
+
RdKafkaMetric.new(:histogram, :root, 'queue.size', 'msg_cnt_d'),
|
40
|
+
|
41
|
+
# Broker metrics
|
42
|
+
RdKafkaMetric.new(:count, :brokers, 'deliver.attempts', 'txretries_d'),
|
43
|
+
RdKafkaMetric.new(:count, :brokers, 'deliver.errors', 'txerrs_d'),
|
44
|
+
RdKafkaMetric.new(:count, :brokers, 'receive.errors', 'rxerrs_d'),
|
45
|
+
RdKafkaMetric.new(:gauge, :brokers, 'queue.latency.avg', %w[outbuf_latency avg]),
|
46
|
+
RdKafkaMetric.new(:gauge, :brokers, 'queue.latency.p95', %w[outbuf_latency p95]),
|
47
|
+
RdKafkaMetric.new(:gauge, :brokers, 'queue.latency.p99', %w[outbuf_latency p99]),
|
48
|
+
RdKafkaMetric.new(:gauge, :brokers, 'network.latency.avg', %w[rtt avg]),
|
49
|
+
RdKafkaMetric.new(:gauge, :brokers, 'network.latency.p95', %w[rtt p95]),
|
50
|
+
RdKafkaMetric.new(:gauge, :brokers, 'network.latency.p99', %w[rtt p99])
|
51
|
+
].freeze
|
52
|
+
|
53
|
+
configure
|
54
|
+
|
55
|
+
# @param block [Proc] configuration block
|
56
|
+
def initialize(&block)
|
57
|
+
configure
|
58
|
+
setup(&block) if block
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param block [Proc] configuration block
|
62
|
+
# @note We define this alias to be consistent with `WaterDrop#setup`
|
63
|
+
def setup(&block)
|
64
|
+
configure(&block)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Hooks up to WaterDrop instrumentation for emitted statistics
|
68
|
+
#
|
69
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
70
|
+
def on_statistics_emitted(event)
|
71
|
+
statistics = event[:statistics]
|
72
|
+
|
73
|
+
rd_kafka_metrics.each do |metric|
|
74
|
+
report_metric(metric, statistics)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Increases the errors count by 1
|
79
|
+
#
|
80
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
81
|
+
def on_error_occurred(_event)
|
82
|
+
count('error_occurred', 1, tags: default_tags)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Increases acknowledged messages counter
|
86
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
87
|
+
def on_message_acknowledged(_event)
|
88
|
+
increment('acknowledged', tags: default_tags)
|
89
|
+
end
|
90
|
+
|
91
|
+
%i[
|
92
|
+
produced_sync
|
93
|
+
produced_async
|
94
|
+
].each do |event_scope|
|
95
|
+
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
96
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
97
|
+
def on_message_#{event_scope}(event)
|
98
|
+
report_message(event[:message][:topic], :#{event_scope})
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
102
|
+
def on_messages_#{event_scope}(event)
|
103
|
+
event[:messages].each do |message|
|
104
|
+
report_message(message[:topic], :#{event_scope})
|
105
|
+
end
|
106
|
+
end
|
107
|
+
METHODS
|
108
|
+
end
|
109
|
+
|
110
|
+
# Reports the buffer usage when anything is added to the buffer
|
111
|
+
%i[
|
112
|
+
message_buffered
|
113
|
+
messages_buffered
|
114
|
+
].each do |event_scope|
|
115
|
+
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
116
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
117
|
+
def on_#{event_scope}(event)
|
118
|
+
histogram(
|
119
|
+
'buffer.size',
|
120
|
+
event[:buffer].size,
|
121
|
+
tags: default_tags
|
122
|
+
)
|
123
|
+
end
|
124
|
+
METHODS
|
125
|
+
end
|
126
|
+
|
127
|
+
# Events that support many messages only
|
128
|
+
# Reports data flushing operation (production from the buffer)
|
129
|
+
%i[
|
130
|
+
flushed_sync
|
131
|
+
flushed_async
|
132
|
+
].each do |event_scope|
|
133
|
+
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
134
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
135
|
+
def on_buffer_#{event_scope}(event)
|
136
|
+
event[:messages].each do |message|
|
137
|
+
report_message(message[:topic], :#{event_scope})
|
138
|
+
end
|
139
|
+
end
|
140
|
+
METHODS
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
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
|
+
|
162
|
+
# Report that a message has been produced to a topic.
|
163
|
+
# @param topic [String] Kafka topic
|
164
|
+
# @param method_name [Symbol] method from which this message operation comes
|
165
|
+
def report_message(topic, method_name)
|
166
|
+
increment(method_name, tags: default_tags + ["topic:#{topic}"])
|
167
|
+
end
|
168
|
+
|
169
|
+
# Wraps metric name in listener's namespace
|
170
|
+
# @param metric_name [String] RdKafkaMetric name
|
171
|
+
# @return [String]
|
172
|
+
def namespaced_metric(metric_name)
|
173
|
+
"#{namespace}.#{metric_name}"
|
174
|
+
end
|
175
|
+
|
176
|
+
# Reports a given metric statistics to Datadog
|
177
|
+
# @param metric [RdKafkaMetric] metric value object
|
178
|
+
# @param statistics [Hash] hash with all the statistics emitted
|
179
|
+
def report_metric(metric, statistics)
|
180
|
+
case metric.scope
|
181
|
+
when :root
|
182
|
+
public_send(
|
183
|
+
metric.type,
|
184
|
+
metric.name,
|
185
|
+
statistics.fetch(*metric.key_location),
|
186
|
+
tags: default_tags
|
187
|
+
)
|
188
|
+
when :brokers
|
189
|
+
statistics.fetch('brokers').each_value do |broker_statistics|
|
190
|
+
# Skip bootstrap nodes
|
191
|
+
# Bootstrap nodes have nodeid -1, other nodes have positive
|
192
|
+
# node ids
|
193
|
+
next if broker_statistics['nodeid'] == -1
|
194
|
+
|
195
|
+
public_send(
|
196
|
+
metric.type,
|
197
|
+
metric.name,
|
198
|
+
broker_statistics.dig(*metric.key_location),
|
199
|
+
tags: default_tags + ["broker:#{broker_statistics['nodename']}"]
|
200
|
+
)
|
201
|
+
end
|
202
|
+
else
|
203
|
+
raise ArgumentError, metric.scope
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WaterDrop
|
4
|
+
# Simple middleware layer for manipulating messages prior to their validation
|
5
|
+
class Middleware
|
6
|
+
def initialize
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@steps = []
|
9
|
+
end
|
10
|
+
|
11
|
+
# Runs middleware on a single message prior to validation
|
12
|
+
#
|
13
|
+
# @param message [Hash] message hash
|
14
|
+
# @return [Hash] message hash. Either the same if transformed in place, or a copy if modified
|
15
|
+
# into a new object.
|
16
|
+
# @note You need to decide yourself whether you don't use the message hash data anywhere else
|
17
|
+
# and you want to save on memory by modifying it in place or do you want to do a deep copy
|
18
|
+
def run(message)
|
19
|
+
@steps.each do |step|
|
20
|
+
message = step.call(message)
|
21
|
+
end
|
22
|
+
|
23
|
+
message
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param messages [Array<Hash>] messages on which we want to run middlewares
|
27
|
+
# @return [Array<Hash>] transformed messages
|
28
|
+
def run_many(messages)
|
29
|
+
messages.map do |message|
|
30
|
+
run(message)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Register given middleware as the first one in the chain
|
35
|
+
# @param step [#call] step that needs to return the message
|
36
|
+
def prepend(step)
|
37
|
+
@mutex.synchronize do
|
38
|
+
@steps.prepend step
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Register given middleware as the last one in the chain
|
43
|
+
# @param step [#call] step that needs to return the message
|
44
|
+
def append(step)
|
45
|
+
@mutex.synchronize do
|
46
|
+
@steps.append step
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -14,14 +14,30 @@ module WaterDrop
|
|
14
14
|
# @raise [Errors::MessageInvalidError] When provided message details are invalid and the
|
15
15
|
# message could not be sent to Kafka
|
16
16
|
def produce_async(message)
|
17
|
-
|
17
|
+
message = middleware.run(message)
|
18
18
|
validate_message!(message)
|
19
19
|
|
20
20
|
@monitor.instrument(
|
21
21
|
'message.produced_async',
|
22
22
|
producer_id: id,
|
23
23
|
message: message
|
24
|
-
) {
|
24
|
+
) { produce(message) }
|
25
|
+
rescue *SUPPORTED_FLOW_ERRORS => e
|
26
|
+
# We use this syntax here because we want to preserve the original `#cause` when we
|
27
|
+
# instrument the error and there is no way to manually assign `#cause` value
|
28
|
+
begin
|
29
|
+
raise Errors::ProduceError, e.inspect
|
30
|
+
rescue Errors::ProduceError => ex
|
31
|
+
@monitor.instrument(
|
32
|
+
'error.occurred',
|
33
|
+
producer_id: id,
|
34
|
+
message: message,
|
35
|
+
error: ex,
|
36
|
+
type: 'message.produce_async'
|
37
|
+
)
|
38
|
+
|
39
|
+
raise ex
|
40
|
+
end
|
25
41
|
end
|
26
42
|
|
27
43
|
# Produces many messages to Kafka and does not wait for them to be delivered
|
@@ -35,7 +51,8 @@ module WaterDrop
|
|
35
51
|
# @raise [Errors::MessageInvalidError] When any of the provided messages details are invalid
|
36
52
|
# and the message could not be sent to Kafka
|
37
53
|
def produce_many_async(messages)
|
38
|
-
|
54
|
+
dispatched = []
|
55
|
+
messages = middleware.run_many(messages)
|
39
56
|
messages.each { |message| validate_message!(message) }
|
40
57
|
|
41
58
|
@monitor.instrument(
|
@@ -43,8 +60,27 @@ module WaterDrop
|
|
43
60
|
producer_id: id,
|
44
61
|
messages: messages
|
45
62
|
) do
|
46
|
-
|
63
|
+
with_transaction_if_transactional do
|
64
|
+
messages.each do |message|
|
65
|
+
dispatched << produce(message)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
dispatched
|
47
70
|
end
|
71
|
+
rescue *SUPPORTED_FLOW_ERRORS => e
|
72
|
+
re_raised = Errors::ProduceManyError.new(dispatched, e.inspect)
|
73
|
+
|
74
|
+
@monitor.instrument(
|
75
|
+
'error.occurred',
|
76
|
+
producer_id: id,
|
77
|
+
messages: messages,
|
78
|
+
dispatched: dispatched,
|
79
|
+
error: re_raised,
|
80
|
+
type: 'messages.produce_many_async'
|
81
|
+
)
|
82
|
+
|
83
|
+
raise re_raised
|
48
84
|
end
|
49
85
|
end
|
50
86
|
end
|
@@ -4,14 +4,6 @@ module WaterDrop
|
|
4
4
|
class Producer
|
5
5
|
# Component for buffered operations
|
6
6
|
module Buffer
|
7
|
-
# Exceptions we catch when dispatching messages from a buffer
|
8
|
-
RESCUED_ERRORS = [
|
9
|
-
Rdkafka::RdkafkaError,
|
10
|
-
Rdkafka::Producer::DeliveryHandle::WaitTimeoutError
|
11
|
-
].freeze
|
12
|
-
|
13
|
-
private_constant :RESCUED_ERRORS
|
14
|
-
|
15
7
|
# Adds given message into the internal producer buffer without flushing it to Kafka
|
16
8
|
#
|
17
9
|
# @param message [Hash] hash that complies with the {Contracts::Message} contract
|
@@ -19,12 +11,15 @@ module WaterDrop
|
|
19
11
|
# message could not be sent to Kafka
|
20
12
|
def buffer(message)
|
21
13
|
ensure_active!
|
14
|
+
|
15
|
+
message = middleware.run(message)
|
22
16
|
validate_message!(message)
|
23
17
|
|
24
18
|
@monitor.instrument(
|
25
19
|
'message.buffered',
|
26
20
|
producer_id: id,
|
27
|
-
message: message
|
21
|
+
message: message,
|
22
|
+
buffer: @messages
|
28
23
|
) { @messages << message }
|
29
24
|
end
|
30
25
|
|
@@ -36,12 +31,15 @@ module WaterDrop
|
|
36
31
|
# and the message could not be sent to Kafka
|
37
32
|
def buffer_many(messages)
|
38
33
|
ensure_active!
|
34
|
+
|
35
|
+
messages = middleware.run_many(messages)
|
39
36
|
messages.each { |message| validate_message!(message) }
|
40
37
|
|
41
38
|
@monitor.instrument(
|
42
39
|
'messages.buffered',
|
43
40
|
producer_id: id,
|
44
|
-
messages: messages
|
41
|
+
messages: messages,
|
42
|
+
buffer: @messages
|
45
43
|
) do
|
46
44
|
messages.each { |message| @messages << message }
|
47
45
|
messages
|
@@ -52,8 +50,6 @@ module WaterDrop
|
|
52
50
|
# @return [Array<Rdkafka::Producer::DeliveryHandle>] delivery handles for messages that were
|
53
51
|
# flushed
|
54
52
|
def flush_async
|
55
|
-
ensure_active!
|
56
|
-
|
57
53
|
@monitor.instrument(
|
58
54
|
'buffer.flushed_async',
|
59
55
|
producer_id: id,
|
@@ -65,8 +61,6 @@ module WaterDrop
|
|
65
61
|
# @return [Array<Rdkafka::Producer::DeliveryReport>] delivery reports for messages that were
|
66
62
|
# flushed
|
67
63
|
def flush_sync
|
68
|
-
ensure_active!
|
69
|
-
|
70
64
|
@monitor.instrument(
|
71
65
|
'buffer.flushed_sync',
|
72
66
|
producer_id: id,
|
@@ -80,33 +74,21 @@ module WaterDrop
|
|
80
74
|
# @param sync [Boolean] should it flush in a sync way
|
81
75
|
# @return [Array<Rdkafka::Producer::DeliveryHandle, Rdkafka::Producer::DeliveryReport>]
|
82
76
|
# delivery handles for async or delivery reports for sync
|
83
|
-
# @raise [Errors::
|
77
|
+
# @raise [Errors::ProduceManyError] when there was a failure in flushing
|
84
78
|
# @note We use this method underneath to provide a different instrumentation for sync and
|
85
79
|
# async flushing within the public API
|
86
80
|
def flush(sync)
|
87
81
|
data_for_dispatch = nil
|
88
|
-
dispatched = []
|
89
82
|
|
90
83
|
@buffer_mutex.synchronize do
|
91
84
|
data_for_dispatch = @messages
|
92
85
|
@messages = Concurrent::Array.new
|
93
86
|
end
|
94
87
|
|
95
|
-
|
96
|
-
|
97
|
-
return dispatched unless sync
|
98
|
-
|
99
|
-
dispatched.map do |handler|
|
100
|
-
handler.wait(
|
101
|
-
max_wait_timeout: @config.max_wait_timeout,
|
102
|
-
wait_timeout: @config.wait_timeout
|
103
|
-
)
|
104
|
-
end
|
105
|
-
rescue *RESCUED_ERRORS => e
|
106
|
-
key = sync ? 'buffer.flushed_sync.error' : 'buffer.flush_async.error'
|
107
|
-
@monitor.instrument(key, producer_id: id, error: e, dispatched: dispatched)
|
88
|
+
# Do nothing if nothing to flush
|
89
|
+
return data_for_dispatch if data_for_dispatch.empty?
|
108
90
|
|
109
|
-
|
91
|
+
sync ? produce_many_sync(data_for_dispatch) : produce_many_async(data_for_dispatch)
|
110
92
|
end
|
111
93
|
end
|
112
94
|
end
|
@@ -10,18 +10,13 @@ module WaterDrop
|
|
10
10
|
# @return [Rdkafka::Producer, Producer::DummyClient] raw rdkafka producer or a dummy producer
|
11
11
|
# when we don't want to dispatch any messages
|
12
12
|
def call(producer, config)
|
13
|
-
|
13
|
+
klass = config.client_class
|
14
|
+
# This allows us to have backwards compatibility.
|
15
|
+
# If it is the default client and delivery is set to false, we use dummy as we used to
|
16
|
+
# before `client_class` was introduced
|
17
|
+
klass = Clients::Dummy if klass == Clients::Rdkafka && !config.deliver
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
# This callback is not global and is per client, thus we do not have to wrap it with a
|
18
|
-
# callbacks manager to make it work
|
19
|
-
client.delivery_callback = Instrumentation::Callbacks::Delivery.new(
|
20
|
-
producer.id,
|
21
|
-
config.monitor
|
22
|
-
)
|
23
|
-
|
24
|
-
client
|
19
|
+
klass.new(producer)
|
25
20
|
end
|
26
21
|
end
|
27
22
|
end
|
@@ -16,7 +16,7 @@ module WaterDrop
|
|
16
16
|
# @raise [Errors::MessageInvalidError] When provided message details are invalid and the
|
17
17
|
# message could not be sent to Kafka
|
18
18
|
def produce_sync(message)
|
19
|
-
|
19
|
+
message = middleware.run(message)
|
20
20
|
validate_message!(message)
|
21
21
|
|
22
22
|
@monitor.instrument(
|
@@ -24,12 +24,23 @@ module WaterDrop
|
|
24
24
|
producer_id: id,
|
25
25
|
message: message
|
26
26
|
) do
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
wait(produce(message))
|
28
|
+
end
|
29
|
+
rescue *SUPPORTED_FLOW_ERRORS => e
|
30
|
+
# We use this syntax here because we want to preserve the original `#cause` when we
|
31
|
+
# instrument the error and there is no way to manually assign `#cause` value
|
32
|
+
begin
|
33
|
+
raise Errors::ProduceError, e.inspect
|
34
|
+
rescue Errors::ProduceError => ex
|
35
|
+
@monitor.instrument(
|
36
|
+
'error.occurred',
|
37
|
+
producer_id: id,
|
38
|
+
message: message,
|
39
|
+
error: ex,
|
40
|
+
type: 'message.produce_sync'
|
41
|
+
)
|
42
|
+
|
43
|
+
raise ex
|
33
44
|
end
|
34
45
|
end
|
35
46
|
|
@@ -46,19 +57,37 @@ module WaterDrop
|
|
46
57
|
# @raise [Errors::MessageInvalidError] When any of the provided messages details are invalid
|
47
58
|
# and the message could not be sent to Kafka
|
48
59
|
def produce_many_sync(messages)
|
49
|
-
|
60
|
+
messages = middleware.run_many(messages)
|
50
61
|
messages.each { |message| validate_message!(message) }
|
51
62
|
|
63
|
+
dispatched = []
|
64
|
+
|
52
65
|
@monitor.instrument('messages.produced_sync', producer_id: id, messages: messages) do
|
53
|
-
|
54
|
-
.
|
55
|
-
|
56
|
-
handler.wait(
|
57
|
-
max_wait_timeout: @config.max_wait_timeout,
|
58
|
-
wait_timeout: @config.wait_timeout
|
59
|
-
)
|
66
|
+
with_transaction_if_transactional do
|
67
|
+
messages.each do |message|
|
68
|
+
dispatched << produce(message)
|
60
69
|
end
|
70
|
+
end
|
71
|
+
|
72
|
+
dispatched.map! do |handler|
|
73
|
+
wait(handler)
|
74
|
+
end
|
75
|
+
|
76
|
+
dispatched
|
61
77
|
end
|
78
|
+
rescue *SUPPORTED_FLOW_ERRORS => e
|
79
|
+
re_raised = Errors::ProduceManyError.new(dispatched, e.inspect)
|
80
|
+
|
81
|
+
@monitor.instrument(
|
82
|
+
'error.occurred',
|
83
|
+
producer_id: id,
|
84
|
+
messages: messages,
|
85
|
+
dispatched: dispatched,
|
86
|
+
error: re_raised,
|
87
|
+
type: 'messages.produce_many_sync'
|
88
|
+
)
|
89
|
+
|
90
|
+
raise re_raised
|
62
91
|
end
|
63
92
|
end
|
64
93
|
end
|