sbmt-outbox 5.0.0
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 +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
|