sbmt-outbox 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +440 -0
- data/Rakefile +3 -0
- data/app/interactors/sbmt/outbox/base_create_item.rb +55 -0
- data/app/interactors/sbmt/outbox/create_inbox_item.rb +10 -0
- data/app/interactors/sbmt/outbox/create_outbox_item.rb +10 -0
- data/app/interactors/sbmt/outbox/dry_interactor.rb +16 -0
- data/app/interactors/sbmt/outbox/partition_strategies/hash_partitioning.rb +20 -0
- data/app/interactors/sbmt/outbox/partition_strategies/number_partitioning.rb +26 -0
- data/app/interactors/sbmt/outbox/process_item.rb +269 -0
- data/app/interactors/sbmt/outbox/retry_strategies/compacted_log.rb +41 -0
- data/app/interactors/sbmt/outbox/retry_strategies/exponential_backoff.rb +34 -0
- data/app/jobs/sbmt/outbox/base_delete_stale_items_job.rb +78 -0
- data/app/jobs/sbmt/outbox/delete_stale_inbox_items_job.rb +15 -0
- data/app/jobs/sbmt/outbox/delete_stale_outbox_items_job.rb +15 -0
- data/app/models/sbmt/outbox/base_item.rb +165 -0
- data/app/models/sbmt/outbox/base_item_config.rb +106 -0
- data/app/models/sbmt/outbox/inbox_item.rb +38 -0
- data/app/models/sbmt/outbox/inbox_item_config.rb +13 -0
- data/app/models/sbmt/outbox/outbox_item.rb +52 -0
- data/app/models/sbmt/outbox/outbox_item_config.rb +13 -0
- data/config/initializers/schked.rb +9 -0
- data/config/initializers/yabeda.rb +71 -0
- data/config/schedule.rb +9 -0
- data/exe/outbox +16 -0
- data/lib/generators/helpers/config.rb +46 -0
- data/lib/generators/helpers/initializer.rb +41 -0
- data/lib/generators/helpers/items.rb +17 -0
- data/lib/generators/helpers/migration.rb +73 -0
- data/lib/generators/helpers/paas.rb +17 -0
- data/lib/generators/helpers/values.rb +49 -0
- data/lib/generators/helpers.rb +8 -0
- data/lib/generators/outbox/install/USAGE +10 -0
- data/lib/generators/outbox/install/install_generator.rb +33 -0
- data/lib/generators/outbox/install/templates/Outboxfile +3 -0
- data/lib/generators/outbox/install/templates/outbox.rb +32 -0
- data/lib/generators/outbox/install/templates/outbox.yml +51 -0
- data/lib/generators/outbox/item/USAGE +12 -0
- data/lib/generators/outbox/item/item_generator.rb +94 -0
- data/lib/generators/outbox/item/templates/inbox_item.rb.tt +7 -0
- data/lib/generators/outbox/item/templates/outbox_item.rb.tt +16 -0
- data/lib/generators/outbox/transport/USAGE +19 -0
- data/lib/generators/outbox/transport/templates/inbox_transport.yml.erb +9 -0
- data/lib/generators/outbox/transport/templates/outbox_transport.yml.erb +10 -0
- data/lib/generators/outbox/transport/transport_generator.rb +60 -0
- data/lib/generators/outbox.rb +23 -0
- data/lib/sbmt/outbox/ascii_art.rb +62 -0
- data/lib/sbmt/outbox/cli.rb +100 -0
- data/lib/sbmt/outbox/database_switcher.rb +15 -0
- data/lib/sbmt/outbox/engine.rb +45 -0
- data/lib/sbmt/outbox/error_tracker.rb +26 -0
- data/lib/sbmt/outbox/errors.rb +14 -0
- data/lib/sbmt/outbox/instrumentation/open_telemetry_loader.rb +34 -0
- data/lib/sbmt/outbox/logger.rb +35 -0
- data/lib/sbmt/outbox/middleware/builder.rb +23 -0
- data/lib/sbmt/outbox/middleware/open_telemetry/tracing_create_item_middleware.rb +42 -0
- data/lib/sbmt/outbox/middleware/open_telemetry/tracing_item_process_middleware.rb +49 -0
- data/lib/sbmt/outbox/middleware/runner.rb +29 -0
- data/lib/sbmt/outbox/middleware/sentry/tracing_batch_process_middleware.rb +48 -0
- data/lib/sbmt/outbox/middleware/sentry/tracing_item_process_middleware.rb +65 -0
- data/lib/sbmt/outbox/middleware/sentry/transaction.rb +28 -0
- data/lib/sbmt/outbox/probes/probe.rb +38 -0
- data/lib/sbmt/outbox/redis_client_factory.rb +36 -0
- data/lib/sbmt/outbox/tasks/delete_failed_items.rake +17 -0
- data/lib/sbmt/outbox/tasks/retry_failed_items.rake +20 -0
- data/lib/sbmt/outbox/thread_pool.rb +108 -0
- data/lib/sbmt/outbox/throttler.rb +52 -0
- data/lib/sbmt/outbox/version.rb +7 -0
- data/lib/sbmt/outbox/worker.rb +233 -0
- data/lib/sbmt/outbox.rb +136 -0
- data/lib/sbmt-outbox.rb +3 -0
- metadata +594 -0
@@ -0,0 +1,269 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Outbox
|
5
|
+
class ProcessItem < Sbmt::Outbox::DryInteractor
|
6
|
+
param :item_class, reader: :private
|
7
|
+
param :item_id, reader: :private
|
8
|
+
|
9
|
+
METRICS_COUNTERS = %i[error_counter retry_counter sent_counter fetch_error_counter discarded_counter].freeze
|
10
|
+
|
11
|
+
delegate :log_success, :log_failure, to: "Sbmt::Outbox.logger"
|
12
|
+
delegate :item_process_middlewares, to: "Sbmt::Outbox"
|
13
|
+
delegate :box_type, :box_name, :owner, to: :item_class
|
14
|
+
|
15
|
+
attr_accessor :process_latency
|
16
|
+
|
17
|
+
def call
|
18
|
+
log_success(
|
19
|
+
"Start processing #{box_type} item.\n" \
|
20
|
+
"Record: #{item_class.name}##{item_id}"
|
21
|
+
)
|
22
|
+
|
23
|
+
item = nil
|
24
|
+
|
25
|
+
item_class.transaction do
|
26
|
+
item = yield fetch_item
|
27
|
+
|
28
|
+
if item.processed_at?
|
29
|
+
item.config.retry_strategies.each do |retry_strategy|
|
30
|
+
yield check_retry_strategy(item, retry_strategy)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
self.process_latency = Time.current - item.created_at
|
34
|
+
end
|
35
|
+
|
36
|
+
middlewares = Middleware::Builder.new(item_process_middlewares)
|
37
|
+
payload = yield build_payload(item)
|
38
|
+
transports = yield fetch_transports(item)
|
39
|
+
|
40
|
+
middlewares.call(item) do
|
41
|
+
transports.each do |transport|
|
42
|
+
yield process_item(transport, item, payload)
|
43
|
+
end
|
44
|
+
|
45
|
+
track_successed(item)
|
46
|
+
|
47
|
+
Success(item)
|
48
|
+
end
|
49
|
+
rescue Dry::Monads::Do::Halt => e
|
50
|
+
e.result
|
51
|
+
rescue => e
|
52
|
+
track_failed(e, item)
|
53
|
+
Failure(e.message)
|
54
|
+
end
|
55
|
+
ensure
|
56
|
+
report_metrics(item)
|
57
|
+
end
|
58
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def fetch_item
|
63
|
+
item = item_class
|
64
|
+
.lock("FOR UPDATE")
|
65
|
+
.find_by(id: item_id)
|
66
|
+
|
67
|
+
unless item
|
68
|
+
track_failed("not found")
|
69
|
+
return Failure(:not_found)
|
70
|
+
end
|
71
|
+
|
72
|
+
unless item.for_processing?
|
73
|
+
log_error("already processed")
|
74
|
+
counters[:fetch_error_counter] += 1
|
75
|
+
return Failure(:already_processed)
|
76
|
+
end
|
77
|
+
|
78
|
+
Success(item)
|
79
|
+
end
|
80
|
+
|
81
|
+
def check_retry_strategy(item, retry_strategy)
|
82
|
+
result = retry_strategy.call(item)
|
83
|
+
|
84
|
+
return Success() if result.success?
|
85
|
+
|
86
|
+
case result.failure
|
87
|
+
when :skip_processing
|
88
|
+
Failure(:skip_processing)
|
89
|
+
when :discard_item
|
90
|
+
track_discarded(item)
|
91
|
+
Failure(:discard_item)
|
92
|
+
else
|
93
|
+
track_failed("retry stratagy returned unknown failure: #{result.failure}")
|
94
|
+
Failure(:retry_strategy_failure)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_payload(item)
|
99
|
+
builder = item.payload_builder
|
100
|
+
|
101
|
+
if builder
|
102
|
+
payload = item.payload_builder.call(item)
|
103
|
+
return payload if payload.success?
|
104
|
+
|
105
|
+
track_failed("payload builder returned failure: #{payload.failure}", item)
|
106
|
+
Failure(:payload_failure)
|
107
|
+
else
|
108
|
+
Success(item.payload)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def fetch_transports(item)
|
113
|
+
transports = item.transports
|
114
|
+
return Success(transports) if transports.present?
|
115
|
+
|
116
|
+
track_failed("missing transports", item)
|
117
|
+
Failure(:missing_transports)
|
118
|
+
end
|
119
|
+
|
120
|
+
# rubocop:disable Metrics/MethodLength
|
121
|
+
def process_item(transport, item, payload)
|
122
|
+
transport_error = nil
|
123
|
+
|
124
|
+
result = item_class.transaction(requires_new: true) do
|
125
|
+
transport.call(item, payload)
|
126
|
+
rescue => e
|
127
|
+
transport_error = e
|
128
|
+
raise ActiveRecord::Rollback
|
129
|
+
end
|
130
|
+
|
131
|
+
if transport_error
|
132
|
+
track_failed(transport_error, item)
|
133
|
+
return Failure(:transport_failure)
|
134
|
+
end
|
135
|
+
|
136
|
+
case result
|
137
|
+
when Dry::Monads::Result
|
138
|
+
if result.failure?
|
139
|
+
track_failed("transport #{transport} returned failure: #{result.failure}", item)
|
140
|
+
Failure(:transport_failure)
|
141
|
+
else
|
142
|
+
Success()
|
143
|
+
end
|
144
|
+
when false
|
145
|
+
track_failed("transport #{transport} returned #{result.inspect}", item)
|
146
|
+
Failure(:transport_failure)
|
147
|
+
else
|
148
|
+
Success()
|
149
|
+
end
|
150
|
+
end
|
151
|
+
# rubocop:enable Metrics/MethodLength
|
152
|
+
|
153
|
+
def track_failed(ex_or_msg, item = nil)
|
154
|
+
log_error(ex_or_msg, item)
|
155
|
+
|
156
|
+
item&.touch_processed_at
|
157
|
+
item&.add_error(ex_or_msg)
|
158
|
+
|
159
|
+
if item.nil?
|
160
|
+
report_error(ex_or_msg)
|
161
|
+
counters[:fetch_error_counter] += 1
|
162
|
+
elsif item.max_retries_exceeded?
|
163
|
+
report_error(ex_or_msg, item)
|
164
|
+
counters[:error_counter] += 1
|
165
|
+
item.failed!
|
166
|
+
else
|
167
|
+
counters[:retry_counter] += 1
|
168
|
+
item.pending!
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def track_successed(item)
|
173
|
+
msg = "Successfully delivered #{box_type} item.\n" \
|
174
|
+
"Record: #{item_class.name}##{item_id}.\n" \
|
175
|
+
"#{item.log_details.to_json}"
|
176
|
+
log_success(msg)
|
177
|
+
|
178
|
+
item.touch_processed_at
|
179
|
+
item.delivered!
|
180
|
+
|
181
|
+
counters[:sent_counter] += 1
|
182
|
+
end
|
183
|
+
|
184
|
+
def track_discarded(item)
|
185
|
+
msg = "Skipped and discarded #{box_type} item.\n" \
|
186
|
+
"Record: #{item_class.name}##{item_id}.\n" \
|
187
|
+
"#{item.log_details.to_json}"
|
188
|
+
log_success(msg)
|
189
|
+
|
190
|
+
item.touch_processed_at
|
191
|
+
item.discarded!
|
192
|
+
|
193
|
+
counters[:discarded_counter] += 1
|
194
|
+
end
|
195
|
+
|
196
|
+
def log_error(ex_or_msg, item = nil)
|
197
|
+
text = format_exception_error(ex_or_msg)
|
198
|
+
|
199
|
+
msg = "Failed processing #{box_type} item with error: #{text}.\n" \
|
200
|
+
"Record: #{item_class.name}##{item_id}.\n" \
|
201
|
+
"#{item&.log_details&.to_json}"
|
202
|
+
|
203
|
+
log_failure(msg, backtrace: format_backtrace(ex_or_msg))
|
204
|
+
end
|
205
|
+
|
206
|
+
def format_exception_error(e)
|
207
|
+
text = if e.respond_to?(:cause) && !e.cause.nil?
|
208
|
+
"#{format_exception_error(e.cause)}. "
|
209
|
+
else
|
210
|
+
""
|
211
|
+
end
|
212
|
+
|
213
|
+
if e.respond_to?(:message)
|
214
|
+
"#{text}#{e.class.name} #{e.message}"
|
215
|
+
else
|
216
|
+
"#{text}#{e}"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def format_backtrace(e)
|
221
|
+
if e.respond_to?(:backtrace) && !e.backtrace.nil?
|
222
|
+
e.backtrace.join("\n")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def report_error(ex_or_msg, item = nil)
|
227
|
+
Outbox.error_tracker.error(
|
228
|
+
ex_or_msg,
|
229
|
+
box_name: item_class.box_name,
|
230
|
+
item_class: item_class.name,
|
231
|
+
item_id: item_id,
|
232
|
+
item_details: item&.log_details&.to_json
|
233
|
+
)
|
234
|
+
end
|
235
|
+
|
236
|
+
def report_metrics(item)
|
237
|
+
labels = labels_for(item)
|
238
|
+
|
239
|
+
METRICS_COUNTERS.each do |counter_name|
|
240
|
+
Yabeda
|
241
|
+
.outbox
|
242
|
+
.send(counter_name)
|
243
|
+
.increment(labels, by: counters[counter_name])
|
244
|
+
end
|
245
|
+
|
246
|
+
track_process_latency(labels) if process_latency
|
247
|
+
|
248
|
+
return unless counters[:sent_counter].positive?
|
249
|
+
|
250
|
+
Yabeda
|
251
|
+
.outbox
|
252
|
+
.last_sent_event_id
|
253
|
+
.set(labels, item_id)
|
254
|
+
end
|
255
|
+
|
256
|
+
def labels_for(item)
|
257
|
+
{type: box_type, name: box_name, owner: owner, partition: item&.partition}
|
258
|
+
end
|
259
|
+
|
260
|
+
def counters
|
261
|
+
@counters ||= Hash.new(0)
|
262
|
+
end
|
263
|
+
|
264
|
+
def track_process_latency(labels)
|
265
|
+
Yabeda.outbox.process_latency.measure(labels, process_latency.round(3))
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Outbox
|
5
|
+
module RetryStrategies
|
6
|
+
class CompactedLog < Outbox::DryInteractor
|
7
|
+
param :outbox_item
|
8
|
+
|
9
|
+
def call
|
10
|
+
unless outbox_item.has_attribute?(:event_key)
|
11
|
+
return Failure(:missing_event_key)
|
12
|
+
end
|
13
|
+
|
14
|
+
if outbox_item.event_key.nil?
|
15
|
+
return Failure(:empty_event_key)
|
16
|
+
end
|
17
|
+
|
18
|
+
if delivered_later?
|
19
|
+
Failure(:discard_item)
|
20
|
+
else
|
21
|
+
Success()
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def delivered_later?
|
28
|
+
scope = outbox_item.class
|
29
|
+
.where("id > ?", outbox_item)
|
30
|
+
.where(event_key: outbox_item.event_key, status: Sbmt::Outbox::BaseItem.statuses[:delivered])
|
31
|
+
|
32
|
+
if outbox_item.has_attribute?(:event_name) && outbox_item.event_name.present?
|
33
|
+
scope = scope.where(event_name: outbox_item.event_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
scope.exists?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Outbox
|
5
|
+
module RetryStrategies
|
6
|
+
class ExponentialBackoff < Outbox::DryInteractor
|
7
|
+
param :outbox_item
|
8
|
+
|
9
|
+
def call
|
10
|
+
delay = backoff(outbox_item.config).interval_at(outbox_item.errors_count - 1)
|
11
|
+
|
12
|
+
still_early = outbox_item.processed_at + delay.seconds > Time.current
|
13
|
+
|
14
|
+
if still_early
|
15
|
+
Failure(:skip_processing)
|
16
|
+
else
|
17
|
+
Success()
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def backoff(config)
|
24
|
+
@backoff ||= ::ExponentialBackoff.new([
|
25
|
+
config.minimal_retry_interval,
|
26
|
+
config.maximal_retry_interval
|
27
|
+
]).tap do |x|
|
28
|
+
x.multiplier = config.multiplier_retry_interval
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redlock"
|
4
|
+
|
5
|
+
module Sbmt
|
6
|
+
module Outbox
|
7
|
+
class BaseDeleteStaleItemsJob < Outbox.active_job_base_class
|
8
|
+
MIN_RETENTION_PERIOD = 1.day
|
9
|
+
LOCK_TTL = 10_800_000
|
10
|
+
BATCH_SIZE = 1000
|
11
|
+
SLEEP_TIME = 1
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def enqueue
|
15
|
+
item_classes.each do |item_class|
|
16
|
+
perform_later(item_class.to_s)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def item_classes
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
delegate :config, :logger, to: "Sbmt::Outbox"
|
26
|
+
delegate :box_type, :box_name, to: :item_class
|
27
|
+
|
28
|
+
attr_accessor :item_class
|
29
|
+
|
30
|
+
def perform(item_class_name)
|
31
|
+
self.item_class = item_class_name.constantize
|
32
|
+
|
33
|
+
client = if Gem::Version.new(Redlock::VERSION) >= Gem::Version.new("2.0.0")
|
34
|
+
RedisClientFactory.build(config.redis)
|
35
|
+
else
|
36
|
+
Redis.new(config.redis)
|
37
|
+
end
|
38
|
+
|
39
|
+
lock_manager = Redlock::Client.new([client], retry_count: 0)
|
40
|
+
|
41
|
+
lock_manager.lock("#{self.class.name}:#{item_class_name}:lock", LOCK_TTL) do |locked|
|
42
|
+
if locked
|
43
|
+
duration = item_class.config.retention
|
44
|
+
|
45
|
+
validate_retention!(duration)
|
46
|
+
|
47
|
+
logger.with_tags(box_type: box_type, box_name: box_name) do
|
48
|
+
delete_stale_items(Time.current - duration)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
logger.log_info("Failed to acquire lock #{self.class.name}:#{item_class_name}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def validate_retention!(duration)
|
59
|
+
return if duration >= MIN_RETENTION_PERIOD
|
60
|
+
|
61
|
+
raise "Retention period for #{box_name} must be longer than #{MIN_RETENTION_PERIOD.inspect}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete_stale_items(waterline)
|
65
|
+
logger.log_info("Start deleting #{box_type} items for #{box_name} older than #{waterline}")
|
66
|
+
|
67
|
+
scope = item_class.where("created_at < ?", waterline)
|
68
|
+
|
69
|
+
while (ids = scope.limit(BATCH_SIZE).ids).present?
|
70
|
+
item_class.where(id: ids).delete_all
|
71
|
+
sleep SLEEP_TIME
|
72
|
+
end
|
73
|
+
|
74
|
+
logger.log_info("Successfully deleted #{box_type} items for #{box_name} older than #{waterline}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sbmt
|
4
|
+
module Outbox
|
5
|
+
class BaseItem < Outbox.active_record_base_class
|
6
|
+
self.abstract_class = true
|
7
|
+
|
8
|
+
class << self
|
9
|
+
delegate :owner, to: :config
|
10
|
+
|
11
|
+
def box_type
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def box_name
|
16
|
+
@box_name ||= name.underscore
|
17
|
+
end
|
18
|
+
|
19
|
+
def config
|
20
|
+
@config ||= lookup_config.new(box_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def partition_buckets
|
24
|
+
@partition_buckets ||=
|
25
|
+
(0...config.partition_size)
|
26
|
+
.to_a
|
27
|
+
.each_with_object({}) do |x, m|
|
28
|
+
m[x] = (0...config.bucket_size)
|
29
|
+
.to_a
|
30
|
+
.select { |p| p % config.partition_size == x }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def bucket_partitions
|
35
|
+
@bucket_partitions ||=
|
36
|
+
partition_buckets.each_with_object({}) do |(partition, buckets), m|
|
37
|
+
buckets.each do |bucket|
|
38
|
+
m[bucket] = partition
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
enum status: {
|
45
|
+
pending: 0,
|
46
|
+
failed: 1,
|
47
|
+
delivered: 2,
|
48
|
+
discarded: 3
|
49
|
+
}
|
50
|
+
|
51
|
+
scope :for_processing, -> { where(status: :pending) }
|
52
|
+
|
53
|
+
validates :uuid, :event_key, :bucket, :payload, presence: true
|
54
|
+
|
55
|
+
delegate :box_name, :config, to: "self.class"
|
56
|
+
|
57
|
+
after_initialize do
|
58
|
+
self.uuid ||= SecureRandom.uuid if has_attribute?(:uuid)
|
59
|
+
end
|
60
|
+
|
61
|
+
def payload
|
62
|
+
if has_attribute?(:proto_payload)
|
63
|
+
proto_payload
|
64
|
+
else
|
65
|
+
self[:payload]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def payload=(value)
|
70
|
+
if has_attribute?(:proto_payload)
|
71
|
+
self.proto_payload = value
|
72
|
+
else
|
73
|
+
self[:payload] = value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def for_processing?
|
78
|
+
pending?
|
79
|
+
end
|
80
|
+
|
81
|
+
def options
|
82
|
+
options = (self[:options] || {}).symbolize_keys
|
83
|
+
options = default_options.deep_merge(extra_options).deep_merge(options)
|
84
|
+
options.symbolize_keys
|
85
|
+
end
|
86
|
+
|
87
|
+
def transports
|
88
|
+
if config.transports.empty?
|
89
|
+
raise Error, "Transports are not defined"
|
90
|
+
end
|
91
|
+
|
92
|
+
if has_attribute?(:event_name)
|
93
|
+
config.transports.fetch(event_name)
|
94
|
+
else
|
95
|
+
config.transports.fetch(:_all_)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def log_details
|
100
|
+
default_log_details.deep_merge(extra_log_details)
|
101
|
+
end
|
102
|
+
|
103
|
+
def payload_builder
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def touch_processed_at
|
108
|
+
self.processed_at = Time.current
|
109
|
+
end
|
110
|
+
|
111
|
+
def retriable?
|
112
|
+
config.max_retries > 0
|
113
|
+
end
|
114
|
+
|
115
|
+
def max_retries_exceeded?
|
116
|
+
return true unless retriable?
|
117
|
+
|
118
|
+
errors_count > config.max_retries
|
119
|
+
end
|
120
|
+
|
121
|
+
def increment_errors_counter
|
122
|
+
increment(:errors_count)
|
123
|
+
end
|
124
|
+
|
125
|
+
def add_error(ex_or_msg)
|
126
|
+
increment_errors_counter
|
127
|
+
|
128
|
+
return unless has_attribute?(:error_log)
|
129
|
+
|
130
|
+
self.error_log = "-----\n#{Time.zone.now} \n #{ex_or_msg}\n #{add_backtrace(ex_or_msg)}"
|
131
|
+
end
|
132
|
+
|
133
|
+
def partition
|
134
|
+
self.class.bucket_partitions.fetch(bucket)
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def default_options
|
140
|
+
raise NotImplementedError
|
141
|
+
end
|
142
|
+
|
143
|
+
# Override in descendants
|
144
|
+
def extra_options
|
145
|
+
{}
|
146
|
+
end
|
147
|
+
|
148
|
+
def default_log_details
|
149
|
+
raise NotImplementedError
|
150
|
+
end
|
151
|
+
|
152
|
+
# Override in descendants
|
153
|
+
def extra_log_details
|
154
|
+
{}
|
155
|
+
end
|
156
|
+
|
157
|
+
def add_backtrace(ex)
|
158
|
+
return unless ex.respond_to?(:backtrace)
|
159
|
+
return if ex.backtrace.nil?
|
160
|
+
|
161
|
+
ex.backtrace.first(30).join("\n")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|